package org.bouncycastle.crypto.digests;

import org.bouncycastle.crypto.CryptoServiceProperties;
import org.bouncycastle.crypto.CryptoServicePurpose;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.util.Pack;


/**
 * FIPS 180-2 implementation of SHA-384.
 *
 * <pre>
 *         block  word  digest
 * SHA-1   512    32    160
 * SHA-256 512    32    256
 * SHA-384 1024   64    384
 * SHA-512 1024   64    512
 * </pre>
 */
public class SHA384Digest
    extends LongDigest
{
    private static final int    DIGEST_LENGTH = 48;

    /**
     * Standard constructor
     */
    public SHA384Digest()
    {
        this(CryptoServicePurpose.ANY);
    }

    /**
     * Standard constructor, with purpose
     */
    public SHA384Digest(CryptoServicePurpose purpose)
    {
        super(purpose);

        CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties());

        reset();
    }

    /**
     * Copy constructor.  This will copy the state of the provided
     * message digest.
     */
    public SHA384Digest(SHA384Digest t)
    {
        super(t);

        CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties());
    }

    /**
     * State constructor - create a digest initialised with the state of a previous one.
     *
     * @param encodedState the encoded state from the originating digest.
     */
    public SHA384Digest(byte[] encodedState)
    {
        super(CryptoServicePurpose.values()[encodedState[encodedState.length - 1]]);

        restoreState(encodedState);

        CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties());
    }

    public String getAlgorithmName()
    {
        return "SHA-384";
    }

    public int getDigestSize()
    {
        return DIGEST_LENGTH;
    }

    public int doFinal(
        byte[]  out,
        int     outOff)
    {
        finish();

        Pack.longToBigEndian(H1, out, outOff);
        Pack.longToBigEndian(H2, out, outOff + 8);
        Pack.longToBigEndian(H3, out, outOff + 16);
        Pack.longToBigEndian(H4, out, outOff + 24);
        Pack.longToBigEndian(H5, out, outOff + 32);
        Pack.longToBigEndian(H6, out, outOff + 40);

        reset();

        return DIGEST_LENGTH;
    }

    /**
     * reset the chaining variables
     */
    public void reset()
    {
        super.reset();

        /* SHA-384 initial hash value
         * The first 64 bits of the fractional parts of the square roots
         * of the 9th through 16th prime numbers
         */
        H1 = 0xcbbb9d5dc1059ed8l;
        H2 = 0x629a292a367cd507l;
        H3 = 0x9159015a3070dd17l;
        H4 = 0x152fecd8f70e5939l;
        H5 = 0x67332667ffc00b31l;
        H6 = 0x8eb44a8768581511l;
        H7 = 0xdb0c2e0d64f98fa7l;
        H8 = 0x47b5481dbefa4fa4l;
    }

    public Memoable copy()
    {
        return new SHA384Digest(this);
    }

    public void reset(Memoable other)
    {
        SHA384Digest d = (SHA384Digest)other;

        super.copyIn(d);
    }

    public byte[] getEncodedState()
    {
        byte[] encoded = new byte[getEncodedStateSize() + 1];
        super.populateState(encoded);

        encoded[encoded.length - 1] = (byte)purpose.ordinal();

        return encoded;
    }

    protected CryptoServiceProperties cryptoServiceProperties()
    {
        return Utils.getDefaultProperties(this, 256, purpose);
    }
}
