/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * GXSNMP -- An snmp mangament application
 *
 * This code implements the MD4 message-digest algorithm.
 * The algorithm is due to Ron Rivest.  This code was
 * written by Colin Plumb in 1993, no copyright is claimed.
 * This code is in the public domain; do with it what you wish.
 *
 * Equivalent code is available from RSA Data Security, Inc.
 * This code has been tested against that, and is equivalent,
 * except that you don't need to include two pages of legalese
 * with every copy.
 *
 * To compute the message digest of a chunk of bytes, declare an
 * MD4Context structure, pass it to MD4Init, call MD4Update as
 * needed on buffers full of bytes, and then call MD4Final, which
 * will fill a supplied 16-byte array with the digest.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <glib.h>
#include <gobject/gobject.h>
#include "gxsnmp_hash.h"
#include "gxsnmp_digest.h"
#include "gxsnmp_md4.h"

static void MD4Init(guint32 digest[4]);
static void MD4Transform(guint32 digest[4], guint32 const data[16]);

static void gxsnmp_md4_init        (GxsnmpMd4       *md4);
static void gxsnmp_md4_class_init  (GxsnmpMd4Class  *klass);
static void gxsnmp_md4_finalize    (GObject         *object);

static void gxsnmp_md4_transform   (GxsnmpDigest    *digest);
static void gxsnmp_md4_reset       (GxsnmpHash      *hash);

static gpointer parent_class = NULL;

GType
gxsnmp_md4_get_type (void)
{
  static GType object_type = 0;

  if (!object_type)
    {
      static const GTypeInfo object_info =
        {
          sizeof (GxsnmpMd4Class),
          (GBaseInitFunc) NULL,
          (GBaseFinalizeFunc) NULL,
          (GClassInitFunc) gxsnmp_md4_class_init,
          NULL,           /* class_finalize */
          NULL,           /* class_data */
          sizeof (GxsnmpMd4),
          0,              /* n_preallocs */
          (GInstanceInitFunc) gxsnmp_md4_init,
        };

        object_type = g_type_register_static (gxsnmp_digest_get_type(),
                                              "GxsnmpMd4",
                                              &object_info, 0);
    }
  return object_type;
}

static void
gxsnmp_md4_init (GxsnmpMd4 *object)
{
  GxsnmpHash   *hash   = GXSNMP_HASH(object);
  GxsnmpDigest *digest = GXSNMP_DIGEST(object);

  hash->result    = g_malloc(16);
  hash->hashlen   = 16;
  hash->hashname  = "MD4";
  digest->endianess = FALSE;
  if (hash->result)
    MD4Init(hash->result);
}

static void
gxsnmp_md4_class_init (GxsnmpMd4Class *klass)
{
  GObjectClass      *object_class  = G_OBJECT_CLASS (klass);
  GxsnmpDigestClass *digest_class  = GXSNMP_DIGEST_CLASS (klass);
  GxsnmpHashClass   *hash_class    = GXSNMP_HASH_CLASS (klass);
  parent_class = g_type_class_peek_parent (klass);

  digest_class->transform = gxsnmp_md4_transform;
  hash_class->reset       = gxsnmp_md4_reset;
  object_class->finalize  = gxsnmp_md4_finalize;
}

static void
gxsnmp_md4_finalize (GObject *object)
{
  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gxsnmp_md4_reset (GxsnmpHash *hash)
{
  GXSNMP_HASH_CLASS (parent_class)->reset (hash);
  
  if (hash->result)
    MD4Init(hash->result);
}

static void
gxsnmp_md4_transform (GxsnmpDigest *digest)
{
  GxsnmpHash *hash = GXSNMP_HASH (digest);

  if (hash->result)
    MD4Transform(hash->result, digest->data);
}

/*
 * Start MD4 accumulation.  Set bit count to 0 and buffer to mysterious
 * initialization constants.
 */
static void 
MD4Init(guint32 digest[4])
{
  digest[0] = 0x67452301;
  digest[1] = 0xefcdab89;
  digest[2] = 0x98badcfe;
  digest[3] = 0x10325476;
}

/* Constants for MD4Transform routine. */
#define S11 3
#define S12 7
#define S13 11
#define S14 19
#define S21 3
#define S22 5
#define S23 9
#define S24 13
#define S31 3
#define S32 9
#define S33 11
#define S34 15

static unsigned char PADDING[64] = {
  0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

/* F, G and H are basic MD4 functions. */
#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) 
#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))

/* ROTATE_LEFT rotates x left n bits. */
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))

/* FF, GG and HH are transformations for rounds 1, 2 and 3 */
/* Rotation is separate from addition to prevent recomputation */

#define FF(a, b, c, d, x, s) { \
    (a) += F ((b), (c), (d)) + (x); \
    (a) = ROTATE_LEFT ((a), (s)); \
  }
#define GG(a, b, c, d, x, s) { \
    (a) += G ((b), (c), (d)) + (x) + (guint32)0x5a827999; \
    (a) = ROTATE_LEFT ((a), (s)); \
  }
#define HH(a, b, c, d, x, s) { \
    (a) += H ((b), (c), (d)) + (x) + (guint32)0x6ed9eba1; \
    (a) = ROTATE_LEFT ((a), (s)); \
  }

/*
 * The core of the MD4 algorithm, this alters an existing MD4 hash to
 * reflect the addition of 16 longwords of new data.  MD4Update blocks
 * the data and converts bytes into longwords for this routine.
 */
static void 
MD4Transform(guint32 digest[4], guint32 const data[16])
{
  register guint32 a, b, c, d, x[16];

  a = digest[0];
  b = digest[1];
  c = digest[2];
  d = digest[3];

  /* Round 1 */
  FF (a, b, c, d, data[ 0], S11); /* 1 */
  FF (d, a, b, c, data[ 1], S12); /* 2 */
  FF (c, d, a, b, data[ 2], S13); /* 3 */
  FF (b, c, d, a, data[ 3], S14); /* 4 */
  FF (a, b, c, d, data[ 4], S11); /* 5 */
  FF (d, a, b, c, data[ 5], S12); /* 6 */
  FF (c, d, a, b, data[ 6], S13); /* 7 */
  FF (b, c, d, a, data[ 7], S14); /* 8 */
  FF (a, b, c, d, data[ 8], S11); /* 9 */
  FF (d, a, b, c, data[ 9], S12); /* 10 */
  FF (c, d, a, b, data[10], S13); /* 11 */
  FF (b, c, d, a, data[11], S14); /* 12 */
  FF (a, b, c, d, data[12], S11); /* 13 */
  FF (d, a, b, c, data[13], S12); /* 14 */
  FF (c, d, a, b, data[14], S13); /* 15 */
  FF (b, c, d, a, data[15], S14); /* 16 */

  /* Round 2 */
  GG (a, b, c, d, data[ 0], S21); /* 17 */
  GG (d, a, b, c, data[ 4], S22); /* 18 */
  GG (c, d, a, b, data[ 8], S23); /* 19 */
  GG (b, c, d, a, data[12], S24); /* 20 */
  GG (a, b, c, d, data[ 1], S21); /* 21 */
  GG (d, a, b, c, data[ 5], S22); /* 22 */
  GG (c, d, a, b, data[ 9], S23); /* 23 */
  GG (b, c, d, a, data[13], S24); /* 24 */
  GG (a, b, c, d, data[ 2], S21); /* 25 */
  GG (d, a, b, c, data[ 6], S22); /* 26 */
  GG (c, d, a, b, data[10], S23); /* 27 */
  GG (b, c, d, a, data[14], S24); /* 28 */
  GG (a, b, c, d, data[ 3], S21); /* 29 */
  GG (d, a, b, c, data[ 7], S22); /* 30 */
  GG (c, d, a, b, data[11], S23); /* 31 */
  GG (b, c, d, a, data[15], S24); /* 32 */

  /* Round 3 */
  HH (a, b, c, d, data[ 0], S31); /* 33 */
  HH (d, a, b, c, data[ 8], S32); /* 34 */
  HH (c, d, a, b, data[ 4], S33); /* 35 */
  HH (b, c, d, a, data[12], S34); /* 36 */
  HH (a, b, c, d, data[ 2], S31); /* 37 */
  HH (d, a, b, c, data[10], S32); /* 38 */
  HH (c, d, a, b, data[ 6], S33); /* 39 */
  HH (b, c, d, a, data[14], S34); /* 40 */
  HH (a, b, c, d, data[ 1], S31); /* 41 */
  HH (d, a, b, c, data[ 9], S32); /* 42 */
  HH (c, d, a, b, data[ 5], S33); /* 43 */
  HH (b, c, d, a, data[13], S34); /* 44 */
  HH (a, b, c, d, data[ 3], S31); /* 45 */
  HH (d, a, b, c, data[11], S32); /* 46 */
  HH (c, d, a, b, data[ 7], S33); /* 47 */
  HH (b, c, d, a, data[15], S34); /* 48 */

  digest[0] += a;
  digest[1] += b;
  digest[2] += c;
  digest[3] += d;
}

GxsnmpMd4*   gxsnmp_md4_new()
{
  GxsnmpMd4* md4;

  md4 = g_object_new (gxsnmp_md4_get_type (), NULL);

  return md4;
}
