/* TMMH16.java -- 
   Copyright (C) 2001, 2002, 2006 Free Software Foundation, Inc.

This file is a part of GNU Classpath.

GNU Classpath 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; either version 2 of the License, or (at
your option) any later version.

GNU Classpath 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 GNU Classpath; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
USA

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version.  */


package gnu.javax.crypto.mac;

import gnu.java.security.Registry;
import gnu.java.security.prng.IRandom;
import gnu.java.security.prng.LimitReachedException;

import java.security.InvalidKeyException;
import java.util.Map;

/**
 * <p><i>TMMH</i> is a <i>universal</i> hash function suitable for message
 * authentication in the Wegman-Carter paradigm, as in the Stream Cipher
 * Security Transform. It is simple, quick, and especially appropriate for
 * Digital Signal Processors and other processors with a fast multiply
 * operation, though a straightforward implementation requires storage equal in
 * length to the largest message to be hashed.</p>
 *
 * <p><i>TMMH</i> is a simple hash function which maps a key and a message to a
 * hash value. There are two versions of TMMH: TMMH/16 and TMMH/32. <i>TMMH</i>
 * can be used as a message authentication code, as described in Section 5 (see
 * References).</p>
 *
 * <p>The key, message, and hash value are all octet strings, and the lengths of
 * these quantities are denoted as <code>KEY_LENGTH</code>,
 * <code>MESSAGE_LENGTH</code>, and <code>TAG_LENGTH</code>, respectively. The
 * values of <code>KEY_LENGTH</code> and <code>TAG_LENGTH</code>
 * <bold>MUST</bold> be fixed for any particular fixed value of the key, and
 * must obey the alignment restrictions described below.</p>
 *
 * <p>The parameter <code>MAX_HASH_LENGTH</code>, which denotes the maximum
 * value which <code>MESSAGE_LENGTH</code> may take, is equal to
 * <code>KEY_LENGTH - TAG_LENGTH</code>.</p>
 *
 * <p>References:</p>
 *
 * <ol>
 *    <li><a
 href="http://www.ietf.org/internet-drafts/draft-mcgrew-saag-tmmh-01.txt">
 *    The Truncated Multi-Modular Hash Function (TMMH)</a>, David A. McGrew.</li>
 * </ol>
 */
public class TMMH16 extends BaseMac implements Cloneable
{

  // Constants and variables
  // -------------------------------------------------------------------------

  public static final String TAG_LENGTH = "gnu.crypto.mac.tmmh.tag.length";

  public static final String KEYSTREAM = "gnu.crypto.mac.tmmh.keystream";

  public static final String PREFIX = "gnu.crypto.mac.tmmh.prefix";

  private static final int P = (1 << 16) + 1; // the TMMH/16 prime

  /** caches the result of the correctness test, once executed. */
  private static Boolean valid;

  private int tagWords = 0; // the tagLength expressed in words

  private IRandom keystream = null; // the keystream generator

  private byte[] prefix; // mask to use when operating as an authentication f.

  private long keyWords; // key words counter

  private long msgLength; // in bytes

  private long msgWords; // should be = msgLength * WORD_LENGTH

  private int[] context; // the tmmh running context; length == TAG_WORDS

  private int[] K0; // the first TAG_WORDS words of the keystream

  private int[] Ki; // the sliding TAG_WORDS words of the keystream

  private int Mi; // current message word being constructed

  // Constructor(s)
  // -------------------------------------------------------------------------

  /** Trivial 0-arguments constructor. */
  public TMMH16()
  {
    super(Registry.TMMH16);
  }

  // Class methods
  // -------------------------------------------------------------------------

  // Instance methods
  // -------------------------------------------------------------------------

  // gnu.crypto.mac.IMac interface implementation ----------------------------

  public int macSize()
  {
    return tagWords * 2;
  }

  public void init(Map attributes) throws InvalidKeyException,
      IllegalStateException
  {
    int wantTagLength = 0;
    Integer tagLength = (Integer) attributes.get(TAG_LENGTH); // get tag length
    if (tagLength == null)
      {
        if (tagWords == 0)
          { // was never set
            throw new IllegalArgumentException(TAG_LENGTH);
          } // else re-use
      }
    else
      { // check if positive and is divisible by WORD_LENGTH
        wantTagLength = tagLength.intValue();
        if (wantTagLength < 2 || (wantTagLength % 2 != 0))
          {
            throw new IllegalArgumentException(TAG_LENGTH);
          }
        else if (wantTagLength > (512 / 8))
          { // 512-bits is our maximum
            throw new IllegalArgumentException(TAG_LENGTH);
          }

        tagWords = wantTagLength / 2; // init local vars
        K0 = new int[tagWords];
        Ki = new int[tagWords];
        context = new int[tagWords];
      }

    prefix = (byte[]) attributes.get(PREFIX);
    if (prefix == null)
      { // default to all-zeroes
        prefix = new byte[tagWords * 2];
      }
    else
      { // ensure it's as long as it should
        if (prefix.length != tagWords * 2)
          {
            throw new IllegalArgumentException(PREFIX);
          }
      }

    IRandom prng = (IRandom) attributes.get(KEYSTREAM); // get keystream
    if (prng == null)
      {
        if (keystream == null)
          {
            throw new IllegalArgumentException(KEYSTREAM);
          } // else reuse
      }
    else
      {
        keystream = prng;
      }

    reset(); // reset context variables
    for (int i = 0; i < tagWords; i++)
      { // init starting key words
        Ki[i] = K0[i] = getNextKeyWord(keystream);
      }
  }

  // The words of the key are denoted as K[1], K[2], ..., K[KEY_WORDS], and the
  // words of the message (after zero padding, if needed) are denoted as M[1],
  // M[2], ..., M[MSG_WORDS], where MSG_WORDS is the smallest number such that
  // 2 * MSG_WORDS is at least MESSAGE_LENGTH, and KEY_WORDS is KEY_LENGTH / 2.
  //
  // If MESSAGE_LENGTH is greater than MAX_HASH_LENGTH, then the value of
  // TMMH/16 is undefined. Implementations MUST indicate an error if asked to
  // hash a message with such a length.  Otherwise, the hash value is defined
  // to be the length TAG_WORDS sequence of words in which the j-th word in the
  // sequence is defined as
  //
  // [ [ K[j] * MESSAGE_LENGTH +32 K[j+1] * M[1] +32 K[j+2] * M[2]
  //   +32 ... K[j+MSG_WORDS] * M[MSG_WORDS] ] modulo p ] modulo 2^16
  //
  // where j ranges from 1 to TAG_WORDS.
  public void update(byte b)
  {
    this.update(b, keystream);
  }

  public void update(byte[] b, int offset, int len)
  {
    for (int i = 0; i < len; i++)
      {
        this.update(b[offset + i], keystream);
      }
  }

  // For TMMH/16, KEY_LENGTH and TAG_LENGTH MUST be a multiple of two. The key,
  // message, and hash value are treated as a sequence of unsigned sixteen bit
  // integers in network byte order.  (In this section, we call such an integer
  // a word.)  If MESSAGE_LENGTH is odd, then a zero byte is appended to the
  // message to align it on a word boundary, though this process does not
  // change the value of MESSAGE_LENGTH.
  //
  // ...  Otherwise, the hash value is defined to be the length TAG_WORDS
  // sequence of words in which the j-th word in the sequence is defined as
  //
  // [ [ K[j] * MESSAGE_LENGTH +32 K[j+1] * M[1] +32 K[j+2] * M[2]
  //   +32 ... K[j+MSG_WORDS] * M[MSG_WORDS] ] modulo p ] modulo 2^16
  //
  // where j ranges from 1 to TAG_WORDS.
  //
  // Here, TAG_WORDS is equal to TAG_LENGTH / 2, and p is equal to 2^16 + 1.
  // The symbol * denotes multiplication and the symbol +32 denotes addition
  // modulo 2^32.
  public byte[] digest()
  {
    return this.digest(keystream);
  }

  public void reset()
  {
    msgLength = msgWords = keyWords = 0L;
    Mi = 0;
    for (int i = 0; i < tagWords; i++)
      {
        context[i] = 0;
      }
  }

  public boolean selfTest()
  {
    if (valid == null)
      {
        // TODO: compute and test equality with one known vector

        valid = Boolean.TRUE;
      }
    return valid.booleanValue();
  }

  // Cloneable interface implementation ---------------------------------------

  public Object clone() throws CloneNotSupportedException
  {
    TMMH16 result = (TMMH16) super.clone();

    if (this.keystream != null)
      result.keystream = (IRandom) this.keystream.clone();

    if (this.prefix != null)
      result.prefix = (byte[]) this.prefix.clone();

    if (this.context != null)
      result.context = (int[]) this.context.clone();

    if (this.K0 != null)
      result.K0 = (int[]) this.K0.clone();

    if (this.Ki != null)
      result.Ki = (int[]) this.Ki.clone();

    return result;
  }

  // own methods -------------------------------------------------------------

  /**
   * <p>Similar to the same method with one argument, but uses the designated
   * random number generator to compute needed keying material.</p>
   *
   * @param b the byte to process.
   * @param prng the source of randomness to use.
   */
  public void update(byte b, IRandom prng)
  {
    Mi <<= 8; // update message buffer
    Mi |= b & 0xFF;
    msgLength++; // update message length (bytes)
    if (msgLength % 2 == 0)
      { // got a full word
        msgWords++; // update message words counter
        System.arraycopy(Ki, 1, Ki, 0, tagWords - 1); // 1. shift Ki up by 1
        Ki[tagWords - 1] = getNextKeyWord(prng); // 2. fill last box of Ki
        long t; // temp var to allow working in modulo 2^32
        for (int i = 0; i < tagWords; i++)
          { // 3. update context
            t = context[i] & 0xFFFFFFFFL;
            t += Ki[i] * Mi;
            context[i] = (int) t;
          }
        Mi = 0; // reset message buffer
      }
  }

  /**
   * <p>Similar to the same method with three arguments, but uses the
   * designated random number generator to compute needed keying material.</p>
   *
   * @param b the byte array to process.
   * @param offset the starting offset in <code>b</code> to start considering
   * the bytes to process.
   * @param len the number of bytes in <code>b</code> starting from
   * <code>offset</code> to process.
   * @param prng the source of randomness to use.
   */
  public void update(byte[] b, int offset, int len, IRandom prng)
  {
    for (int i = 0; i < len; i++)
      {
        this.update(b[offset + i], prng);
      }
  }

  /**
   * <p>Similar to the same method with no arguments, but uses the designated
   * random number generator to compute needed keying material.</p>
   *
   * @param prng the source of randomness to use.
   * @return the final result of the algorithm.
   */
  public byte[] digest(IRandom prng)
  {
    doFinalRound(prng);
    byte[] result = new byte[tagWords * 2];
    for (int i = 0, j = 0; i < tagWords; i++)
      {
        result[j] = (byte) ((context[i] >>> 8) ^ prefix[j]);
        j++;
        result[j] = (byte) (context[i] ^ prefix[j]);
        j++;
      }

    reset();
    return result;
  }

  private int getNextKeyWord(IRandom prng)
  {
    int result = 0;
    try
      {
        result = (prng.nextByte() & 0xFF) << 8 | (prng.nextByte() & 0xFF);
      }
    catch (LimitReachedException x)
      {
        throw new RuntimeException(String.valueOf(x));
      }

    keyWords++; // update key words counter
    return result;
  }

  private void doFinalRound(IRandom prng)
  {
    long limit = msgLength; // formula works on real message length
    while (msgLength % 2 != 0)
      {
        update((byte) 0x00, prng);
      }
    long t;
    for (int i = 0; i < tagWords; i++)
      {
        t = context[i] & 0xFFFFFFFFL;
        t += K0[i] * limit;
        t %= P;
        context[i] = (int) t;
      }
  }
}
