package org.gudy.bouncycastle.crypto.macs;

import org.gudy.bouncycastle.crypto.CipherParameters;
import org.gudy.bouncycastle.crypto.Digest;
import org.gudy.bouncycastle.crypto.Mac;
import org.gudy.bouncycastle.crypto.params.KeyParameter;

/**
 * HMAC implementation based on RFC2104
 *
 * H(K XOR opad, H(K XOR ipad, text))
 */
public class HMac
implements Mac
{
	private final static int BLOCK_LENGTH = 64;

	private final static byte IPAD = (byte)0x36;
	private final static byte OPAD = (byte)0x5C;

	private Digest digest;
	private int digestSize;
    private byte[] inputPad = new byte[BLOCK_LENGTH];
	private byte[] outputPad = new byte[BLOCK_LENGTH];

	public HMac(
		Digest digest)
	{
		this.digest = digest;
		digestSize = digest.getDigestSize();
	}

	@Override
	public String getAlgorithmName()
	{
		return digest.getAlgorithmName() + "/HMAC";
	}

    public Digest getUnderlyingDigest()
    {
        return digest;
    }

	@Override
	public void init(
		CipherParameters params)
	{
		digest.reset();

		byte[] key = ((KeyParameter)params).getKey();

		if (key.length > BLOCK_LENGTH)
		{
			digest.update(key, 0, key.length);
			digest.doFinal(inputPad, 0);
		    for (int i = digestSize; i < inputPad.length; i++)
            {
                inputPad[i] = 0;
            }
		}
		else
		{
			System.arraycopy(key, 0, inputPad, 0, key.length);
		    for (int i = key.length; i < inputPad.length; i++)
            {
                inputPad[i] = 0;
            }
		}

		outputPad = new byte[inputPad.length];
        System.arraycopy(inputPad, 0, outputPad, 0, inputPad.length);

		for (int i = 0; i < inputPad.length; i++)
		{
			inputPad[i] ^= IPAD;
		}

		for (int i = 0; i < outputPad.length; i++)
		{
			outputPad[i] ^= OPAD;
		}

		digest.update(inputPad, 0, inputPad.length);
	}

	@Override
	public int getMacSize()
	{
		return digestSize;
	}

	@Override
	public void update(
		byte in)
	{
		digest.update(in);
	}

	@Override
	public void update(
		byte[] in,
		int inOff,
		int len)
	{
		digest.update(in, inOff, len);
	}

	@Override
	public int doFinal(
		byte[] out,
		int outOff)
	{
		byte[] tmp = new byte[digestSize];
		digest.doFinal(tmp, 0);

		digest.update(outputPad, 0, outputPad.length);
		digest.update(tmp, 0, tmp.length);

        int     len = digest.doFinal(out, outOff);

        reset();

		return len;
	}

	/**
	 * Reset the mac generator.
	 */
	@Override
	public void reset()
	{
		/*
		 * reset the underlying digest.
		 */
		digest.reset();

        /*
         * reinitialize the digest.
         */
		digest.update(inputPad, 0, inputPad.length);
	}
}
