package freenet.crypt;

import static org.junit.Assert.*;

import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import org.junit.Before;
import org.junit.Test;

import java.security.Security;
import java.util.Random;

import freenet.support.TestProperty;
import freenet.support.TimeUtil;

public class HMACTest {

  Random random;
  // RFC4868 2.7.2.1 SHA256 Authentication Test Vector
  static byte[] plaintext = "Hi There".getBytes();
  static byte[]
      knownKey =
      Hex.decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
  static byte[]
      knownSHA256 =
      Hex.decode("198a607eb44bfbc69903a0f1cf2bbdc5ba0aa3f3d9ae3c1c7a3b1696a0b68cf7");

  @Before
  public void setUp() throws Exception {
    Security.addProvider(new BouncyCastleProvider());
    random = new Random(0xAAAAAAAA);
  }

  @Test
  public void testAllCipherNames() {
    for (HMAC hmac : HMAC.values()) {
      HMAC.mac(hmac, new byte[hmac.digestSize], plaintext);
    }
  }

  @Test
  public void testSHA256SignVerify() {
    byte[] key = new byte[32];
    random.nextBytes(key);

    byte[] hmac = HMAC.macWithSHA256(key, plaintext);
    assertNotNull(hmac);
    assertTrue(HMAC.verifyWithSHA256(key, plaintext, hmac));
  }

  @Test
  public void testWrongKeySize() {
    byte[] keyTooLong = new byte[31];
    byte[] keyTooShort = new byte[29];
    random.nextBytes(keyTooLong);
    random.nextBytes(keyTooShort);
    try {
      HMAC.macWithSHA256(keyTooLong, plaintext);
      fail();
    } catch (IllegalArgumentException e) {
      // This is expected
    }
    try {
      HMAC.macWithSHA256(keyTooShort, plaintext);
      fail();
    } catch (IllegalArgumentException e) {
      // This is expected
    }
  }

  @Test
  public void testKnownVectors() {
    byte[] hmac = HMAC.macWithSHA256(knownKey, plaintext);
    assertEquals(Hex.toHexString(hmac), Hex.toHexString(knownSHA256));
  }

  // ant -Dtest.skip=false -Dtest.class=freenet.crypt.HMACTest -Dtest.benchmark=true unit
  @Test
  public void testBenchmark() {
    if (!TestProperty.BENCHMARK) {
      return;
    }

    int count = 0;
    System.out.println("We're getting ready to benchmark HMACs");
    Random r = new Random(0xBBBBBBBB);
    for (int len = 8; len <= 32768; len *= 4) {
      byte [] plaintext = new byte[len];
      r.nextBytes(plaintext);
      System.out.println("plaintext len "+len);
      int ITERATIONS = 10000000/len;
      long t1 = System.currentTimeMillis();
      for (int i = 0; i < ITERATIONS; i++) {
        byte[] r1 = HMAC_legacy.macWithSHA256(knownKey, plaintext, 32);
        for (int j = 0; j < r1.length; j++) {
          count += r1[j];
        }
      }
      long legacyLength = System.currentTimeMillis() - t1;

      t1 = System.currentTimeMillis();
      for (int i = 0; i < ITERATIONS; i++) {
        byte[] r1 = HMAC.macWithSHA256(knownKey, plaintext);
        for (int j = 0; j < r1.length; j++) {
          count += r1[j];
        }
      }
      long currentLength = System.currentTimeMillis() - t1;

      t1 = System.currentTimeMillis();
      for (int i = 0; i < ITERATIONS; i++) {
        byte[] r1 = new byte[32];
        HMac hmac = new HMac(new SHA256Digest());
        KeyParameter kp = new KeyParameter(knownKey);
        hmac.init(kp);
        hmac.update(plaintext, 0, plaintext.length);
        hmac.doFinal(r1, 0);
        for (int j = 0; j < r1.length; j++) {
          count += r1[j];
        }
      }
      long BCLength = System.currentTimeMillis() - t1;
      System.out.println("Legacy HMAC took " + TimeUtil.formatTime(legacyLength, 6, true));
      System.out.println("Current HMAC took " + TimeUtil.formatTime(currentLength, 6, true));
      System.out.println("BC HMAC took " + TimeUtil.formatTime(BCLength, 6, true));
    }
  }
}
