/* See LICENSE for licensing and NOTICE for copyright. */
package org.cryptacular.generator.sp80038d;

import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.prng.drbg.HashSP800DRBG;
import org.bouncycastle.crypto.prng.drbg.SP80090DRBG;
import org.cryptacular.generator.LimitException;
import org.cryptacular.generator.Nonce;
import org.cryptacular.util.ByteUtil;
import org.cryptacular.util.NonceUtil;

/**
 * RBG-based nonce generation strategy that uses a RBG component to produce values for the invocation field as described
 * in NIST <a href="http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf">SP-800-38D</a>, section 8.2.2.
 *
 * <p><strong>NOTE:</strong> users of this class are responsible for counting number of invocations and enforcing the
 * constraints described in section 8.3; namely the following:</p>
 *
 * <blockquote>The total number of invocations of the authenticated encryption function shall not exceed 2<sup>32</sup>,
 * including all IV lengths and all instances of the authenticated encryption function with the given key.</blockquote>
 *
 * <p>Instances of this class are thread safe.</p>
 *
 * @author  Middleware Services
 */
public class RBGNonce implements Nonce
{

  /** Fixed field value. */
  private final byte[] fixed;

  /** Number of bytes of random data in invocation field. */
  private final int randomLength;

  /** Random bit generator. */
  private final SP80090DRBG rbg;


  /**
   * Creates a new instance that produces 12-bytes (96-bits) of random data; that is, the fixed field of the nonce is
   * null.
   */
  public RBGNonce()
  {
    this(12);
  }


  /**
   * Creates a new instance that produces length bytes of random data; that is, the fixed field of the nonce is null.
   *
   * @param  randomLength  Number of bytes in the random part of the nonce. MUST be at least 12.
   */
  public RBGNonce(final int randomLength)
  {
    this(null, randomLength);
  }


  /**
   * Creates a new instance using the given fixed field value.
   *
   * @param  fixed  User-defined fixed field value.
   * @param  randomLength  Number of bytes in the random part of the nonce. MUST be at least 12.
   */
  public RBGNonce(final String fixed, final int randomLength)
  {
    if (randomLength < 12) {
      throw new IllegalArgumentException("Must specify at least 12 bytes (96 bits) for random part.");
    }
    if (randomLength > NonceUtil.getMaxNonceLength()) {
      throw new IllegalArgumentException(
        randomLength + " is invalid. Length cannot exceed " + NonceUtil.getMaxNonceLength());
    }
    this.randomLength = randomLength;
    if (fixed != null) {
      this.fixed = ByteUtil.toBytes(fixed);
    } else {
      this.fixed = new byte[0];
    }
    this.rbg = newRBG(this.randomLength, this.fixed);
  }


  @Override
  public byte[] generate()
    throws LimitException
  {
    final byte[] random = new byte[randomLength];
    synchronized (rbg) {
      rbg.generate(random, null, false);
    }

    final byte[] value = new byte[getLength()];
    System.arraycopy(fixed, 0, value, 0, fixed.length);
    System.arraycopy(random, 0, value, fixed.length, random.length);
    return value;
  }


  @Override
  public int getLength()
  {
    return fixed.length + randomLength;
  }


  /**
   * Creates a new DRBG instance.
   *
   * @param  length  Length in bits of values produced by DRBG.
   * @param  domain  Domain qualifier.
   *
   * @return  New DRBG instance.
   */
  private static SP80090DRBG newRBG(final int length, final byte[] domain)
  {
    return new HashSP800DRBG(
        new SHA256Digest(), length, NonceUtil.randomEntropySource(length), domain, NonceUtil.timestampNonce(8));
  }
}
