// --- BEGIN COPYRIGHT BLOCK ---
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// (C) 2007 Red Hat, Inc.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---
package org.mozilla.jss.netscape.security.provider;

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

/**
 * This class implements the Secure Hash Algorithm (SHA) developed by
 * the National Institute of Standards and Technology along with the
 * National Security Agency. This is the updated version of SHA
 * fip-180 as superseded by fip-180-1.
 *
 * <p>
 * It implement JavaSecurity MessageDigest, and can be used by in the Java Security framework, as a pluggable
 * implementation, as a filter for the digest stream classes.
 *
 * @version 1.30 97/12/10
 * @author Roger Riggs
 * @author Benjamin Renaud
 */

public class SHA extends MessageDigestSpi implements Cloneable {

    /* This private hookm controlled by the appropriate constructor,
       causes this class to implement the first version of SHA,
       as defined in FIPS 180, as opposed to FIPS 180-1. This was
       useful for DSA testing. */
    private int version = 1;

    private static final int SHA_LENGTH = 20;

    // Buffer of int's and count of characters accumulated
    // 64 bytes are included in each hash block so the low order
    // bits of count are used to know how to pack the bytes into ints
    // and to know when to compute the block and start the next one.
    private int W[] = new int[80];
    private long count = 0;
    private final int countmax = 64;
    private final int countmask = (countmax - 1);

    private int AA, BB, CC, DD, EE;

    SHA(int version) {
        this();
        this.version = version;
    }

    /**
     * Creates a new SHA object.
     */
    public SHA() {
        init();
    }

    /**
     * Return the length of the digest in bytes
     */
    protected int engineGetDigestLength() {
        return (SHA_LENGTH);
    }

    public void engineUpdate(byte b) {
        engineUpdate((int) b);
    }

    /**
     * Update a byte.
     *
     * @param b the byte
     */
    private void engineUpdate(int b) {
        int word;
        int offset;

        /* compute word offset and bit offset within word the low bits
           of count are inverted to make put the bytes in the write
           order */
        word = ((int) count & countmask) >>> 2;
        offset = (~(int) count & 3) << 3;

        W[word] = (W[word] & ~(0xff << offset)) | ((b & 0xff) << offset);

        /* If this is the last byte of a block, compute the partial hash */
        if (((int) count & countmask) == countmask) {
            computeBlock();
        }
        count++;
    }

    /**
     * Update a buffer.
     *
     * @param b the data to be updated.
     * @param off the start offset in the data
     * @param len the number of bytes to be updated.
     */
    public void engineUpdate(byte b[], int off, int len) {
        int word;

        if ((off < 0) || (len < 0) || (off + len > b.length))
            throw new ArrayIndexOutOfBoundsException();

        // Use single writes until integer aligned
        while ((len > 0) &&
                ((int) count & 3) != 0) {
            engineUpdate(b[off]);
            off++;
            len--;
        }

        /* Assemble groups of 4 bytes to be inserted in integer array */
        for (; len >= 4; len -= 4, off += 4) {

            word = ((int) count & countmask) >> 2;

            W[word] = ((b[off] & 0xff) << 24) |
                    ((b[off + 1] & 0xff) << 16) |
                    ((b[off + 2] & 0xff) << 8) |
                    ((b[off + 3] & 0xff));

            count += 4;
            if (((int) count & countmask) == 0) {
                computeBlock();
            }
        }

        /* Use single writes for last few bytes */
        for (; len > 0; len--, off++) {
            engineUpdate(b[off]);
        }
    }

    /**
     * Resets the buffers and hash value to start a new hash.
     */
    public void init() {
        AA = 0x67452301;
        BB = 0xefcdab89;
        CC = 0x98badcfe;
        DD = 0x10325476;
        EE = 0xc3d2e1f0;

        for (int i = 0; i < 80; i++)
            W[i] = 0;
        count = 0;
    }

    /**
     * Resets the buffers and hash value to start a new hash.
     */
    public void engineReset() {
        init();
    }

    /**
     * Computes the final hash and returns the final value as a
     * byte[20] array. The object is reset to be ready for further
     * use, as specified in the JavaSecurity MessageDigest
     * specification.
     */
    public byte[] engineDigest() {
        byte hashvalue[] = new byte[SHA_LENGTH];

        try {
            engineDigest(hashvalue, 0, hashvalue.length);
        } catch (DigestException e) {
            throw new InternalError("");
        }
        return hashvalue;
    }

    /**
     * Computes the final hash and returns the final value as a
     * byte[20] array. The object is reset to be ready for further
     * use, as specified in the JavaSecurity MessageDigest
     * specification.
     */
    public int engineDigest(byte[] hashvalue, int offset, int len)
                    throws DigestException {

        if (len < SHA_LENGTH)
            throw new DigestException("partial digests not returned");
        if (hashvalue.length - offset < SHA_LENGTH)
            throw new DigestException("insufficient space in the output " +
                    "buffer to store the digest");

        /* The number of bits before padding occurs */
        long bits = count << 3;

        engineUpdate(0x80);

        /* Pad with zeros until length is a multiple of 448 (the last two
           32 ints are used a holder for bits (see above). */
        while ((int) (count & countmask) != 56) {
            engineUpdate(0);
        }

        W[14] = (int) (bits >>> 32);
        W[15] = (int) (bits & 0xffffffff);

        count += 8;
        computeBlock();

        // Copy out the result
        hashvalue[offset + 0] = (byte) (AA >>> 24);
        hashvalue[offset + 1] = (byte) (AA >>> 16);
        hashvalue[offset + 2] = (byte) (AA >>> 8);
        hashvalue[offset + 3] = (byte) (AA >>> 0);

        hashvalue[offset + 4] = (byte) (BB >>> 24);
        hashvalue[offset + 5] = (byte) (BB >>> 16);
        hashvalue[offset + 6] = (byte) (BB >>> 8);
        hashvalue[offset + 7] = (byte) (BB >>> 0);

        hashvalue[offset + 8] = (byte) (CC >>> 24);
        hashvalue[offset + 9] = (byte) (CC >>> 16);
        hashvalue[offset + 10] = (byte) (CC >>> 8);
        hashvalue[offset + 11] = (byte) (CC >>> 0);

        hashvalue[offset + 12] = (byte) (DD >>> 24);
        hashvalue[offset + 13] = (byte) (DD >>> 16);
        hashvalue[offset + 14] = (byte) (DD >>> 8);
        hashvalue[offset + 15] = (byte) (DD >>> 0);

        hashvalue[offset + 16] = (byte) (EE >>> 24);
        hashvalue[offset + 17] = (byte) (EE >>> 16);
        hashvalue[offset + 18] = (byte) (EE >>> 8);
        hashvalue[offset + 19] = (byte) (EE >>> 0);

        engineReset(); // remove the evidence

        return SHA_LENGTH;
    }

    // Constants for each round
    private final int round1_kt = 0x5a827999;
    private final int round2_kt = 0x6ed9eba1;
    private final int round3_kt = 0x8f1bbcdc;
    private final int round4_kt = 0xca62c1d6;

    /**
     * Compute a the hash for the current block.
     *
     * This is in the same vein as Peter Gutmann's algorithm listed in
     * the back of Applied Cryptography, Compact implementation of
     * "old" NIST Secure Hash Algorithm.
     *
     */
    private void computeBlock() {
        int temp, a, b, c, d, e;

        // The first 16 ints have the byte stream, compute the rest of
        // the buffer
        for (int t = 16; t <= 79; t++) {
            if (version == 0) {
                W[t] = W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16];
            } else {
                temp = W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16];
                W[t] = ((temp << 1) | (temp >>> (32 - 1)));
            }
        }

        a = AA;
        b = BB;
        c = CC;
        d = DD;
        e = EE;

        // Round 1
        for (int i = 0; i < 20; i++) {
            temp = ((a << 5) | (a >>> (32 - 5))) +
                    ((b & c) | ((~b) & d)) + e + W[i] + round1_kt;
            e = d;
            d = c;
            c = ((b << 30) | (b >>> (32 - 30)));
            b = a;
            a = temp;
        }

        // Round 2
        for (int i = 20; i < 40; i++) {
            temp = ((a << 5) | (a >>> (32 - 5))) +
                    (b ^ c ^ d) + e + W[i] + round2_kt;
            e = d;
            d = c;
            c = ((b << 30) | (b >>> (32 - 30)));
            b = a;
            a = temp;
        }

        // Round 3
        for (int i = 40; i < 60; i++) {
            temp = ((a << 5) | (a >>> (32 - 5))) +
                    ((b & c) | (b & d) | (c & d)) + e + W[i] + round3_kt;
            e = d;
            d = c;
            c = ((b << 30) | (b >>> (32 - 30)));
            b = a;
            a = temp;
        }

        // Round 4
        for (int i = 60; i < 80; i++) {
            temp = ((a << 5) | (a >>> (32 - 5))) +
                    (b ^ c ^ d) + e + W[i] + round4_kt;
            e = d;
            d = c;
            c = ((b << 30) | (b >>> (32 - 30)));
            b = a;
            a = temp;
        }
        AA += a;
        BB += b;
        CC += c;
        DD += d;
        EE += e;
    }

    /*
     * Clones this object.
     */
    public Object clone() {
        SHA that = null;
        try {
            that = (SHA) super.clone();
            that.W = new int[80];
            System.arraycopy(this.W, 0, that.W, 0, W.length);
            return that;
        } catch (CloneNotSupportedException e) {
        }
        return that;
    }
}
