/**
 * Copyright 2007-2016, Kaazing Corporation. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.mina.proxy.utils;

import java.security.DigestException;
import java.security.MessageDigestSpi;

/**
 * MD4.java - An implementation of Ron Rivest's MD4 message digest algorithm.
 * The MD4 algorithm is designed to be quite fast on 32-bit machines. In
 * addition, the MD4 algorithm does not require any large substitution
 * tables.
 *
 * @see The <a href="http://www.ietf.org/rfc/rfc1320.txt">MD4</a> Message-
 *    Digest Algorithm by R. Rivest.
 *
 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
 * @since MINA 2.0.0-M3
 */
public class MD4 extends MessageDigestSpi {

    /**
     * The MD4 algorithm message digest length is 16 bytes wide.
     */
    public static final int BYTE_DIGEST_LENGTH = 16;

    /**
     * The MD4 algorithm block length is 64 bytes wide.
     */
    public static final int BYTE_BLOCK_LENGTH = 64;

    /**
     * The initial values of the four registers. RFC gives the values 
     * in LE so we converted it as JAVA uses BE endianness.
     */
    private static final int A = 0x67452301;

    private static final int B = 0xefcdab89;

    private static final int C = 0x98badcfe;

    private static final int D = 0x10325476;

    /**
     * The four registers initialized with the above IVs.
     */
    private int a = A;

    private int b = B;

    private int c = C;

    private int d = D;

    /**
     * Counts the total length of the data being digested.
     */
    private long msgLength;

    /**
     * The internal buffer is {@link BLOCK_LENGTH} wide.
     */
    private final byte[] buffer = new byte[BYTE_BLOCK_LENGTH];

    /**
     * Default constructor.
     */
    public MD4() {
        // Do nothing
    }

    /**
     * Returns the digest length in bytes.
     *
     * @return the digest length in bytes.
     */
    protected int engineGetDigestLength() {
        return BYTE_DIGEST_LENGTH;
    }

    /**
     * {@inheritDoc}
     */
    protected void engineUpdate(byte b) {
        int pos = (int) (msgLength % BYTE_BLOCK_LENGTH);
        buffer[pos] = b;
        msgLength++;

        // If buffer contains enough data then process it.
        if (pos == (BYTE_BLOCK_LENGTH - 1)) {
            process(buffer, 0);
        }
    }

    /**
     * {@inheritDoc}
     */
    protected void engineUpdate(byte[] b, int offset, int len) {
        int pos = (int) (msgLength % BYTE_BLOCK_LENGTH);
        int nbOfCharsToFillBuf = BYTE_BLOCK_LENGTH - pos;
        int blkStart = 0;

        msgLength += len;

        // Process each full block
        if (len >= nbOfCharsToFillBuf) {
            System.arraycopy(b, offset, buffer, pos, nbOfCharsToFillBuf);
            process(buffer, 0);
            for (blkStart = nbOfCharsToFillBuf; blkStart + BYTE_BLOCK_LENGTH
                    - 1 < len; blkStart += BYTE_BLOCK_LENGTH) {
                process(b, offset + blkStart);
            }
            pos = 0;
        }

        // Fill buffer with the remaining data
        if (blkStart < len) {
            System.arraycopy(b, offset + blkStart, buffer, pos, len - blkStart);
        }
    }

    /**
     * {@inheritDoc}
     */
    protected byte[] engineDigest() {
        byte[] p = pad();
        engineUpdate(p, 0, p.length);
        byte[] digest = { (byte) a, (byte) (a >>> 8), (byte) (a >>> 16),
                (byte) (a >>> 24), (byte) b, (byte) (b >>> 8),
                (byte) (b >>> 16), (byte) (b >>> 24), (byte) c,
                (byte) (c >>> 8), (byte) (c >>> 16), (byte) (c >>> 24),
                (byte) d, (byte) (d >>> 8), (byte) (d >>> 16),
                (byte) (d >>> 24) };

        engineReset();

        return digest;
    }

    /**
     * {@inheritDoc}
     */
    protected int engineDigest(byte[] buf, int offset, int len)
            throws DigestException {
        if (offset < 0 || offset + len >= buf.length) {
            throw new DigestException(
                    "Wrong offset or not enough space to store the digest");
        }
        int destLength = Math.min(len, BYTE_DIGEST_LENGTH);
        System.arraycopy(engineDigest(), 0, buf, offset, destLength);
        return destLength;
    }

    /**
     * {@inheritDoc}
     */
    protected void engineReset() {
        a = A;
        b = B;
        c = C;
        d = D;
        msgLength = 0;
    }

    /**
     * Pads the buffer by appending the byte 0x80, then append as many zero 
     * bytes as necessary to make the buffer length a multiple of 64 bytes.  
     * The last 8 bytes will be filled with the length of the buffer in bits.
     * If there's no room to store the length in bits in the block i.e the block 
     * is larger than 56 bytes then an additionnal 64-bytes block is appended.
     * 
     * @see sections 3.1 & 3.2 of the RFC 1320.
     * 
     * @return the pad byte array
     */
    private byte[] pad() {
        int pos = (int) (msgLength % BYTE_BLOCK_LENGTH);
        int padLength = (pos < 56) ? (64 - pos) : (128 - pos);
        byte[] pad = new byte[padLength];

        // First bit of the padding set to 1
        pad[0] = (byte) 0x80;

        long bits = msgLength << 3;
        int index = padLength - 8;
        for (int i = 0; i < 8; i++) {
            pad[index++] = (byte) (bits >>> (i << 3));
        }

        return pad;
    }

    /** 
     * Process one 64-byte block. Algorithm is constituted by three rounds.
     * Note that F, G and H functions were inlined for improved performance.
     * 
     * @param in the byte array to process
     * @param offset the offset at which the 64-byte block is stored
     */
    private void process(byte[] in, int offset) {
        // Save previous state.
        int aa = a;
        int bb = b;
        int cc = c;
        int dd = d;

        // Copy the block to process into X array
        int[] X = new int[16];
        for (int i = 0; i < 16; i++) {
            X[i] = (in[offset++] & 0xff) | (in[offset++] & 0xff) << 8
                    | (in[offset++] & 0xff) << 16 | (in[offset++] & 0xff) << 24;
        }

        // Round 1
        a += ((b & c) | (~b & d)) + X[0];
        a = a << 3 | a >>> (32 - 3);
        d += ((a & b) | (~a & c)) + X[1];
        d = d << 7 | d >>> (32 - 7);
        c += ((d & a) | (~d & b)) + X[2];
        c = c << 11 | c >>> (32 - 11);
        b += ((c & d) | (~c & a)) + X[3];
        b = b << 19 | b >>> (32 - 19);
        a += ((b & c) | (~b & d)) + X[4];
        a = a << 3 | a >>> (32 - 3);
        d += ((a & b) | (~a & c)) + X[5];
        d = d << 7 | d >>> (32 - 7);
        c += ((d & a) | (~d & b)) + X[6];
        c = c << 11 | c >>> (32 - 11);
        b += ((c & d) | (~c & a)) + X[7];
        b = b << 19 | b >>> (32 - 19);
        a += ((b & c) | (~b & d)) + X[8];
        a = a << 3 | a >>> (32 - 3);
        d += ((a & b) | (~a & c)) + X[9];
        d = d << 7 | d >>> (32 - 7);
        c += ((d & a) | (~d & b)) + X[10];
        c = c << 11 | c >>> (32 - 11);
        b += ((c & d) | (~c & a)) + X[11];
        b = b << 19 | b >>> (32 - 19);
        a += ((b & c) | (~b & d)) + X[12];
        a = a << 3 | a >>> (32 - 3);
        d += ((a & b) | (~a & c)) + X[13];
        d = d << 7 | d >>> (32 - 7);
        c += ((d & a) | (~d & b)) + X[14];
        c = c << 11 | c >>> (32 - 11);
        b += ((c & d) | (~c & a)) + X[15];
        b = b << 19 | b >>> (32 - 19);

        // Round 2
        a += ((b & (c | d)) | (c & d)) + X[0] + 0x5a827999;
        a = a << 3 | a >>> (32 - 3);
        d += ((a & (b | c)) | (b & c)) + X[4] + 0x5a827999;
        d = d << 5 | d >>> (32 - 5);
        c += ((d & (a | b)) | (a & b)) + X[8] + 0x5a827999;
        c = c << 9 | c >>> (32 - 9);
        b += ((c & (d | a)) | (d & a)) + X[12] + 0x5a827999;
        b = b << 13 | b >>> (32 - 13);
        a += ((b & (c | d)) | (c & d)) + X[1] + 0x5a827999;
        a = a << 3 | a >>> (32 - 3);
        d += ((a & (b | c)) | (b & c)) + X[5] + 0x5a827999;
        d = d << 5 | d >>> (32 - 5);
        c += ((d & (a | b)) | (a & b)) + X[9] + 0x5a827999;
        c = c << 9 | c >>> (32 - 9);
        b += ((c & (d | a)) | (d & a)) + X[13] + 0x5a827999;
        b = b << 13 | b >>> (32 - 13);
        a += ((b & (c | d)) | (c & d)) + X[2] + 0x5a827999;
        a = a << 3 | a >>> (32 - 3);
        d += ((a & (b | c)) | (b & c)) + X[6] + 0x5a827999;
        d = d << 5 | d >>> (32 - 5);
        c += ((d & (a | b)) | (a & b)) + X[10] + 0x5a827999;
        c = c << 9 | c >>> (32 - 9);
        b += ((c & (d | a)) | (d & a)) + X[14] + 0x5a827999;
        b = b << 13 | b >>> (32 - 13);
        a += ((b & (c | d)) | (c & d)) + X[3] + 0x5a827999;
        a = a << 3 | a >>> (32 - 3);
        d += ((a & (b | c)) | (b & c)) + X[7] + 0x5a827999;
        d = d << 5 | d >>> (32 - 5);
        c += ((d & (a | b)) | (a & b)) + X[11] + 0x5a827999;
        c = c << 9 | c >>> (32 - 9);
        b += ((c & (d | a)) | (d & a)) + X[15] + 0x5a827999;
        b = b << 13 | b >>> (32 - 13);

        // Round 3
        a += (b ^ c ^ d) + X[0] + 0x6ed9eba1;
        a = a << 3 | a >>> (32 - 3);
        d += (a ^ b ^ c) + X[8] + 0x6ed9eba1;
        d = d << 9 | d >>> (32 - 9);
        c += (d ^ a ^ b) + X[4] + 0x6ed9eba1;
        c = c << 11 | c >>> (32 - 11);
        b += (c ^ d ^ a) + X[12] + 0x6ed9eba1;
        b = b << 15 | b >>> (32 - 15);
        a += (b ^ c ^ d) + X[2] + 0x6ed9eba1;
        a = a << 3 | a >>> (32 - 3);
        d += (a ^ b ^ c) + X[10] + 0x6ed9eba1;
        d = d << 9 | d >>> (32 - 9);
        c += (d ^ a ^ b) + X[6] + 0x6ed9eba1;
        c = c << 11 | c >>> (32 - 11);
        b += (c ^ d ^ a) + X[14] + 0x6ed9eba1;
        b = b << 15 | b >>> (32 - 15);
        a += (b ^ c ^ d) + X[1] + 0x6ed9eba1;
        a = a << 3 | a >>> (32 - 3);
        d += (a ^ b ^ c) + X[9] + 0x6ed9eba1;
        d = d << 9 | d >>> (32 - 9);
        c += (d ^ a ^ b) + X[5] + 0x6ed9eba1;
        c = c << 11 | c >>> (32 - 11);
        b += (c ^ d ^ a) + X[13] + 0x6ed9eba1;
        b = b << 15 | b >>> (32 - 15);
        a += (b ^ c ^ d) + X[3] + 0x6ed9eba1;
        a = a << 3 | a >>> (32 - 3);
        d += (a ^ b ^ c) + X[11] + 0x6ed9eba1;
        d = d << 9 | d >>> (32 - 9);
        c += (d ^ a ^ b) + X[7] + 0x6ed9eba1;
        c = c << 11 | c >>> (32 - 11);
        b += (c ^ d ^ a) + X[15] + 0x6ed9eba1;
        b = b << 15 | b >>> (32 - 15);

        //Update state.
        a += aa;
        b += bb;
        c += cc;
        d += dd;
    }
}
