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

package org.bouncycastle.crypto.generators;

import org.bouncycastle.util.Arrays;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.util.Longs;
import org.bouncycastle.crypto.digests.Blake2bDigest;
import org.bouncycastle.util.Pack;
import org.bouncycastle.crypto.params.Argon2Parameters;

public class Argon2BytesGenerator
{
    private static final int ARGON2_BLOCK_SIZE = 1024;
    private static final int ARGON2_QWORDS_IN_BLOCK = 128;
    private static final int ARGON2_ADDRESSES_IN_BLOCK = 128;
    private static final int ARGON2_PREHASH_DIGEST_LENGTH = 64;
    private static final int ARGON2_PREHASH_SEED_LENGTH = 72;
    private static final int ARGON2_SYNC_POINTS = 4;
    private static final int MIN_PARALLELISM = 1;
    private static final int MAX_PARALLELISM = 16777215;
    private static final int MIN_OUTLEN = 4;
    private static final int MIN_ITERATIONS = 1;
    private static final long M32L = 4294967295L;
    private static final byte[] ZERO_BYTES;
    private Argon2Parameters parameters;
    private Block[] memory;
    private int segmentLength;
    private int laneLength;
    
    public void init(final Argon2Parameters parameters) {
        if (parameters.getVersion() != 16 && parameters.getVersion() != 19) {
            throw new UnsupportedOperationException("unknown Argon2 version");
        }
        if (parameters.getType() != 0 && parameters.getType() != 1 && parameters.getType() != 2) {
            throw new UnsupportedOperationException("unknown Argon2 type");
        }
        if (parameters.getLanes() < 1) {
            throw new IllegalStateException("lanes must be at least 1");
        }
        if (parameters.getLanes() > 16777215) {
            throw new IllegalStateException("lanes must be at most 16777215");
        }
        if (parameters.getIterations() < 1) {
            throw new IllegalStateException("iterations is less than: 1");
        }
        this.parameters = parameters;
        this.segmentLength = Math.max(parameters.getMemory(), 8 * parameters.getLanes()) / (4 * parameters.getLanes());
        this.laneLength = this.segmentLength * 4;
        this.memory = new Block[parameters.getLanes() * this.laneLength];
        for (int i = 0; i < this.memory.length; ++i) {
            this.memory[i] = new Block();
        }
    }
    
    public int generateBytes(final char[] array, final byte[] array2) {
        return this.generateBytes(this.parameters.getCharToByteConverter().convert(array), array2);
    }
    
    public int generateBytes(final char[] array, final byte[] array2, final int n, final int n2) {
        return this.generateBytes(this.parameters.getCharToByteConverter().convert(array), array2, n, n2);
    }
    
    public int generateBytes(final byte[] array, final byte[] array2) {
        return this.generateBytes(array, array2, 0, array2.length);
    }
    
    public int generateBytes(final byte[] array, final byte[] array2, final int n, final int n2) {
        if (n2 < 4) {
            throw new IllegalStateException("output length less than 4");
        }
        final byte[] array3 = new byte[1024];
        this.initialize(array3, array, n2);
        this.fillMemoryBlocks();
        this.digest(array3, array2, n, n2);
        this.reset();
        return n2;
    }
    
    private void reset() {
        if (null != this.memory) {
            for (int i = 0; i < this.memory.length; ++i) {
                final Block block = this.memory[i];
                if (null != block) {
                    block.clear();
                }
            }
        }
    }
    
    private void fillMemoryBlocks() {
        final FillBlock fillBlock = new FillBlock();
        final Position position = new Position();
        for (int i = 0; i < this.parameters.getIterations(); ++i) {
            position.pass = i;
            for (int j = 0; j < 4; ++j) {
                position.slice = j;
                for (int k = 0; k < this.parameters.getLanes(); ++k) {
                    position.lane = k;
                    this.fillSegment(fillBlock, position);
                }
            }
        }
    }
    
    private void fillSegment(final FillBlock fillBlock, final Position position) {
        Block clear = null;
        Block clear2 = null;
        final boolean dataIndependentAddressing = this.isDataIndependentAddressing(position);
        final int startingIndex = getStartingIndex(position);
        int n = position.lane * this.laneLength + position.slice * this.segmentLength + startingIndex;
        int prevOffset = this.getPrevOffset(n);
        if (dataIndependentAddressing) {
            clear = fillBlock.addressBlock.clear();
            clear2 = fillBlock.inputBlock.clear();
            this.initAddressBlocks(fillBlock, position, clear2, clear);
        }
        final boolean withXor = this.isWithXor(position);
        for (int i = startingIndex; i < this.segmentLength; ++i) {
            final long pseudoRandom = this.getPseudoRandom(fillBlock, i, clear, clear2, prevOffset, dataIndependentAddressing);
            final int refLane = this.getRefLane(position, pseudoRandom);
            final int refColumn = this.getRefColumn(position, i, pseudoRandom, refLane == position.lane);
            final Block block = this.memory[prevOffset];
            final Block block2 = this.memory[this.laneLength * refLane + refColumn];
            final Block block3 = this.memory[n];
            if (withXor) {
                fillBlock.fillBlockWithXor(block, block2, block3);
            }
            else {
                fillBlock.fillBlock(block, block2, block3);
            }
            prevOffset = n;
            ++n;
        }
    }
    
    private boolean isDataIndependentAddressing(final Position position) {
        return this.parameters.getType() == 1 || (this.parameters.getType() == 2 && position.pass == 0 && position.slice < 2);
    }
    
    private void initAddressBlocks(final FillBlock fillBlock, final Position position, final Block block, final Block block2) {
        block.v[0] = this.intToLong(position.pass);
        block.v[1] = this.intToLong(position.lane);
        block.v[2] = this.intToLong(position.slice);
        block.v[3] = this.intToLong(this.memory.length);
        block.v[4] = this.intToLong(this.parameters.getIterations());
        block.v[5] = this.intToLong(this.parameters.getType());
        if (position.pass == 0 && position.slice == 0) {
            this.nextAddresses(fillBlock, block, block2);
        }
    }
    
    private boolean isWithXor(final Position position) {
        return position.pass != 0 && this.parameters.getVersion() != 16;
    }
    
    private int getPrevOffset(final int n) {
        if (n % this.laneLength == 0) {
            return n + this.laneLength - 1;
        }
        return n - 1;
    }
    
    private static int getStartingIndex(final Position position) {
        if (position.pass == 0 && position.slice == 0) {
            return 2;
        }
        return 0;
    }
    
    private void nextAddresses(final FillBlock fillBlock, final Block block, final Block block2) {
        final long[] access$400 = block.v;
        final int n = 6;
        ++access$400[n];
        fillBlock.fillBlock(block, block2);
        fillBlock.fillBlock(block2, block2);
    }
    
    private long getPseudoRandom(final FillBlock fillBlock, final int n, final Block block, final Block block2, final int n2, final boolean b) {
        if (b) {
            final int n3 = n % 128;
            if (n3 == 0) {
                this.nextAddresses(fillBlock, block2, block);
            }
            return block.v[n3];
        }
        return this.memory[n2].v[0];
    }
    
    private int getRefLane(final Position position, final long n) {
        int lane = (int)((n >>> 32) % this.parameters.getLanes());
        if (position.pass == 0 && position.slice == 0) {
            lane = position.lane;
        }
        return lane;
    }
    
    private int getRefColumn(final Position position, final int n, final long n2, final boolean b) {
        int n3;
        int n4;
        if (position.pass == 0) {
            n3 = 0;
            if (b) {
                n4 = position.slice * this.segmentLength + n - 1;
            }
            else {
                n4 = position.slice * this.segmentLength + ((n == 0) ? -1 : 0);
            }
        }
        else {
            n3 = (position.slice + 1) * this.segmentLength % this.laneLength;
            if (b) {
                n4 = this.laneLength - this.segmentLength + n - 1;
            }
            else {
                n4 = this.laneLength - this.segmentLength + ((n == 0) ? -1 : 0);
            }
        }
        final long n5 = n2 & 0xFFFFFFFFL;
        return (int)(n3 + (n4 - 1 - (n4 * (n5 * n5 >>> 32) >>> 32))) % this.laneLength;
    }
    
    private void digest(final byte[] array, final byte[] array2, final int n, final int n2) {
        final Block block = this.memory[this.laneLength - 1];
        for (int i = 1; i < this.parameters.getLanes(); ++i) {
            block.xorWith(this.memory[i * this.laneLength + (this.laneLength - 1)]);
        }
        block.toBytes(array);
        this.hash(array, array2, n, n2);
    }
    
    private void hash(final byte[] array, final byte[] array2, final int n, final int n2) {
        final byte[] array3 = new byte[4];
        Pack.intToLittleEndian(n2, array3, 0);
        final int n3 = 64;
        if (n2 <= n3) {
            final Blake2bDigest blake2bDigest = new Blake2bDigest(n2 * 8);
            blake2bDigest.update(array3, 0, array3.length);
            blake2bDigest.update(array, 0, array.length);
            blake2bDigest.doFinal(array2, n);
        }
        else {
            final Blake2bDigest blake2bDigest2 = new Blake2bDigest(n3 * 8);
            final byte[] array4 = new byte[n3];
            blake2bDigest2.update(array3, 0, array3.length);
            blake2bDigest2.update(array, 0, array.length);
            blake2bDigest2.doFinal(array4, 0);
            final int n4 = n3 / 2;
            System.arraycopy(array4, 0, array2, n, n4);
            int n5 = n + n4;
            final int n6 = (n2 + 31) / 32 - 2;
            for (int i = 2; i <= n6; ++i, n5 += n4) {
                blake2bDigest2.update(array4, 0, array4.length);
                blake2bDigest2.doFinal(array4, 0);
                System.arraycopy(array4, 0, array2, n5, n4);
            }
            final Blake2bDigest blake2bDigest3 = new Blake2bDigest((n2 - 32 * n6) * 8);
            blake2bDigest3.update(array4, 0, array4.length);
            blake2bDigest3.doFinal(array2, n5);
        }
    }
    
    private static void roundFunction(final Block block, final int n, final int n2, final int n3, final int n4, final int n5, final int n6, final int n7, final int n8, final int n9, final int n10, final int n11, final int n12, final int n13, final int n14, final int n15, final int n16) {
        final long[] access$400 = block.v;
        F(access$400, n, n5, n9, n13);
        F(access$400, n2, n6, n10, n14);
        F(access$400, n3, n7, n11, n15);
        F(access$400, n4, n8, n12, n16);
        F(access$400, n, n6, n11, n16);
        F(access$400, n2, n7, n12, n13);
        F(access$400, n3, n8, n9, n14);
        F(access$400, n4, n5, n10, n15);
    }
    
    private static void F(final long[] array, final int n, final int n2, final int n3, final int n4) {
        quarterRound(array, n, n2, n4, 32);
        quarterRound(array, n3, n4, n2, 24);
        quarterRound(array, n, n2, n4, 16);
        quarterRound(array, n3, n4, n2, 63);
    }
    
    private static void quarterRound(final long[] array, final int n, final int n2, final int n3, final int n4) {
        final long n5 = array[n];
        final long n6 = array[n2];
        final long n7 = array[n3];
        final long n8 = n5 + (n6 + 2L * (n5 & 0xFFFFFFFFL) * (n6 & 0xFFFFFFFFL));
        final long rotateRight = Longs.rotateRight(n7 ^ n8, n4);
        array[n] = n8;
        array[n3] = rotateRight;
    }
    
    private void initialize(final byte[] array, final byte[] array2, final int n) {
        final Blake2bDigest blake2bDigest = new Blake2bDigest(512);
        final int[] array3 = { this.parameters.getLanes(), n, this.parameters.getMemory(), this.parameters.getIterations(), this.parameters.getVersion(), this.parameters.getType() };
        Pack.intToLittleEndian(array3, array, 0);
        blake2bDigest.update(array, 0, array3.length * 4);
        addByteString(array, blake2bDigest, array2);
        addByteString(array, blake2bDigest, this.parameters.getSalt());
        addByteString(array, blake2bDigest, this.parameters.getSecret());
        addByteString(array, blake2bDigest, this.parameters.getAdditional());
        final byte[] array4 = new byte[72];
        blake2bDigest.doFinal(array4, 0);
        this.fillFirstBlocks(array, array4);
    }
    
    private static void addByteString(final byte[] array, final Digest digest, final byte[] array2) {
        if (null == array2) {
            digest.update(Argon2BytesGenerator.ZERO_BYTES, 0, 4);
            return;
        }
        Pack.intToLittleEndian(array2.length, array, 0);
        digest.update(array, 0, 4);
        digest.update(array2, 0, array2.length);
    }
    
    private void fillFirstBlocks(final byte[] array, final byte[] array2) {
        final byte[] array3 = new byte[72];
        System.arraycopy(array2, 0, array3, 0, 64);
        array3[64] = 1;
        for (int i = 0; i < this.parameters.getLanes(); ++i) {
            Pack.intToLittleEndian(i, array2, 68);
            Pack.intToLittleEndian(i, array3, 68);
            this.hash(array2, array, 0, 1024);
            this.memory[i * this.laneLength + 0].fromBytes(array);
            this.hash(array3, array, 0, 1024);
            this.memory[i * this.laneLength + 1].fromBytes(array);
        }
    }
    
    private long intToLong(final int n) {
        return (long)n & 0xFFFFFFFFL;
    }
    
    static {
        ZERO_BYTES = new byte[4];
    }
    
    private static class Block
    {
        private static final int SIZE = 128;
        private final long[] v;
        
        private Block() {
            this.v = new long[128];
        }
        
        void fromBytes(final byte[] array) {
            if (array.length < 1024) {
                throw new IllegalArgumentException("input shorter than blocksize");
            }
            Pack.littleEndianToLong(array, 0, this.v);
        }
        
        void toBytes(final byte[] array) {
            if (array.length < 1024) {
                throw new IllegalArgumentException("output shorter than blocksize");
            }
            Pack.longToLittleEndian(this.v, array, 0);
        }
        
        private void copyBlock(final Block block) {
            System.arraycopy(block.v, 0, this.v, 0, 128);
        }
        
        private void xor(final Block block, final Block block2) {
            final long[] v = this.v;
            final long[] v2 = block.v;
            final long[] v3 = block2.v;
            for (int i = 0; i < 128; ++i) {
                v[i] = (v2[i] ^ v3[i]);
            }
        }
        
        private void xorWith(final Block block) {
            final long[] v = this.v;
            final long[] v2 = block.v;
            for (int i = 0; i < 128; ++i) {
                final long[] array = v;
                final int n = i;
                array[n] ^= v2[i];
            }
        }
        
        private void xorWith(final Block block, final Block block2) {
            final long[] v = this.v;
            final long[] v2 = block.v;
            final long[] v3 = block2.v;
            for (int i = 0; i < 128; ++i) {
                final long[] array = v;
                final int n = i;
                array[n] ^= (v2[i] ^ v3[i]);
            }
        }
        
        public Block clear() {
            Arrays.fill(this.v, 0L);
            return this;
        }
    }
    
    private static class FillBlock
    {
        Block R;
        Block Z;
        Block addressBlock;
        Block inputBlock;
        
        private FillBlock() {
            this.R = new Block();
            this.Z = new Block();
            this.addressBlock = new Block();
            this.inputBlock = new Block();
        }
        
        private void applyBlake() {
            for (int i = 0; i < 8; ++i) {
                final int n = 16 * i;
                roundFunction(this.Z, n, n + 1, n + 2, n + 3, n + 4, n + 5, n + 6, n + 7, n + 8, n + 9, n + 10, n + 11, n + 12, n + 13, n + 14, n + 15);
            }
            for (int j = 0; j < 8; ++j) {
                final int n2 = 2 * j;
                roundFunction(this.Z, n2, n2 + 1, n2 + 16, n2 + 17, n2 + 32, n2 + 33, n2 + 48, n2 + 49, n2 + 64, n2 + 65, n2 + 80, n2 + 81, n2 + 96, n2 + 97, n2 + 112, n2 + 113);
            }
        }
        
        private void fillBlock(final Block block, final Block block2) {
            this.Z.copyBlock(block);
            this.applyBlake();
            block2.xor(block, this.Z);
        }
        
        private void fillBlock(final Block block, final Block block2, final Block block3) {
            this.R.xor(block, block2);
            this.Z.copyBlock(this.R);
            this.applyBlake();
            block3.xor(this.R, this.Z);
        }
        
        private void fillBlockWithXor(final Block block, final Block block2, final Block block3) {
            this.R.xor(block, block2);
            this.Z.copyBlock(this.R);
            this.applyBlake();
            block3.xorWith(this.R, this.Z);
        }
    }
    
    private static class Position
    {
        int pass;
        int lane;
        int slice;
        
        Position() {
        }
    }
}
