/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * GXSNMP -- An snmp mangament application
 *
 */

#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_ripemd128.h"


/********************************************************************/

/* ROL(x, n) cyclically rotates x over n bits to the left */
/* x must be of an unsigned 32 bits type and 0 <= n < 32. */
#define ROL(x, n)        (((x) << (n)) | ((x) >> (32-(n))))

/* the four basic functions F(), G() and H() */
#define F(x, y, z)        ((x) ^ (y) ^ (z)) 
#define G(x, y, z)        (((x) & (y)) | (~(x) & (z))) 
#define H(x, y, z)        (((x) | ~(y)) ^ (z))
#define I(x, y, z)        (((x) & (z)) | ((y) & ~(z))) 
  
/* the eight basic operations FF() through III() */
#define FF(a, b, c, d, x, s)        {\
      (a) += F((b), (c), (d)) + (x);\
      (a) = ROL((a), (s));\
   }
#define GG(a, b, c, d, x, s)        {\
      (a) += G((b), (c), (d)) + (x) + 0x5a827999UL;\
      (a) = ROL((a), (s));\
   }
#define HH(a, b, c, d, x, s)        {\
      (a) += H((b), (c), (d)) + (x) + 0x6ed9eba1UL;\
      (a) = ROL((a), (s));\
   }
#define II(a, b, c, d, x, s)        {\
      (a) += I((b), (c), (d)) + (x) + 0x8f1bbcdcUL;\
      (a) = ROL((a), (s));\
   }
#define FFF(a, b, c, d, x, s)        {\
      (a) += F((b), (c), (d)) + (x);\
      (a) = ROL((a), (s));\
   }
#define GGG(a, b, c, d, x, s)        {\
      (a) += G((b), (c), (d)) + (x) + 0x6d703ef3UL;\
      (a) = ROL((a), (s));\
   }
#define HHH(a, b, c, d, x, s)        {\
      (a) += H((b), (c), (d)) + (x) + 0x5c4dd124UL;\
      (a) = ROL((a), (s));\
   }
#define III(a, b, c, d, x, s)        {\
      (a) += I((b), (c), (d)) + (x) + 0x50a28be6UL;\
      (a) = ROL((a), (s));\
   }

/********************************************************************/

static void RIPEMD128Init(guint32 digest[5]);
static void RIPEMD128Transform(guint32 *digest, guint32 *data);

static void gxsnmp_ripemd128_init        (GxsnmpRipemd128       *ripemd128);
static void gxsnmp_ripemd128_class_init  (GxsnmpRipemd128Class  *klass);
static void gxsnmp_ripemd128_finalize    (GObject               *object);

static void gxsnmp_ripemd128_transform   (GxsnmpDigest          *digest);
static void gxsnmp_ripemd128_reset       (GxsnmpHash            *hash);

static gpointer parent_class = NULL;

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

  if (!object_type)
    {
      static const GTypeInfo object_info =
        {
          sizeof (GxsnmpRipemd128Class),
          (GBaseInitFunc) NULL,
          (GBaseFinalizeFunc) NULL,
          (GClassInitFunc) gxsnmp_ripemd128_class_init,
          NULL,           /* class_finalize */
          NULL,           /* class_data */
          sizeof (GxsnmpRipemd128),
          0,              /* n_preallocs */
          (GInstanceInitFunc) gxsnmp_ripemd128_init,
        };

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

static void
gxsnmp_ripemd128_init (GxsnmpRipemd128 *object)
{
  GxsnmpHash   *hash   = GXSNMP_HASH(object);
  GxsnmpDigest *digest = GXSNMP_DIGEST(object);

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

static void
gxsnmp_ripemd128_class_init (GxsnmpRipemd128Class *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_ripemd128_transform;
  hash_class->reset       = gxsnmp_ripemd128_reset;
  object_class->finalize  = gxsnmp_ripemd128_finalize;
}

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

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

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

static void
gxsnmp_ripemd128_reset (GxsnmpHash *hash)
{
  GXSNMP_HASH_CLASS (parent_class)->reset (hash);

  if (hash->result)
    RIPEMD128Init(hash->result);
}

static void RIPEMD128Init(guint32 digest[5])
{
   digest[0] = 0x67452301UL;
   digest[1] = 0xefcdab89UL;
   digest[2] = 0x98badcfeUL;
   digest[3] = 0x10325476UL;
}

/********************************************************************/

static void
RIPEMD128Transform(guint32 *digest, guint32 *data)
{

   guint32 aa = digest[0],  bb = digest[1],  cc = digest[2],  dd = digest[3];
   guint32 aaa = digest[0], bbb = digest[1], ccc = digest[2], ddd = digest[3];

   /* round 1 */
   FF(aa, bb, cc, dd, data[ 0], 11);
   FF(dd, aa, bb, cc, data[ 1], 14);
   FF(cc, dd, aa, bb, data[ 2], 15);
   FF(bb, cc, dd, aa, data[ 3], 12);
   FF(aa, bb, cc, dd, data[ 4],  5);
   FF(dd, aa, bb, cc, data[ 5],  8);
   FF(cc, dd, aa, bb, data[ 6],  7);
   FF(bb, cc, dd, aa, data[ 7],  9);
   FF(aa, bb, cc, dd, data[ 8], 11);
   FF(dd, aa, bb, cc, data[ 9], 13);
   FF(cc, dd, aa, bb, data[10], 14);
   FF(bb, cc, dd, aa, data[11], 15);
   FF(aa, bb, cc, dd, data[12],  6);
   FF(dd, aa, bb, cc, data[13],  7);
   FF(cc, dd, aa, bb, data[14],  9);
   FF(bb, cc, dd, aa, data[15],  8);
                             
   /* round 2 */
   GG(aa, bb, cc, dd, data[ 7],  7);
   GG(dd, aa, bb, cc, data[ 4],  6);
   GG(cc, dd, aa, bb, data[13],  8);
   GG(bb, cc, dd, aa, data[ 1], 13);
   GG(aa, bb, cc, dd, data[10], 11);
   GG(dd, aa, bb, cc, data[ 6],  9);
   GG(cc, dd, aa, bb, data[15],  7);
   GG(bb, cc, dd, aa, data[ 3], 15);
   GG(aa, bb, cc, dd, data[12],  7);
   GG(dd, aa, bb, cc, data[ 0], 12);
   GG(cc, dd, aa, bb, data[ 9], 15);
   GG(bb, cc, dd, aa, data[ 5],  9);
   GG(aa, bb, cc, dd, data[ 2], 11);
   GG(dd, aa, bb, cc, data[14],  7);
   GG(cc, dd, aa, bb, data[11], 13);
   GG(bb, cc, dd, aa, data[ 8], 12);

   /* round 3 */
   HH(aa, bb, cc, dd, data[ 3], 11);
   HH(dd, aa, bb, cc, data[10], 13);
   HH(cc, dd, aa, bb, data[14],  6);
   HH(bb, cc, dd, aa, data[ 4],  7);
   HH(aa, bb, cc, dd, data[ 9], 14);
   HH(dd, aa, bb, cc, data[15],  9);
   HH(cc, dd, aa, bb, data[ 8], 13);
   HH(bb, cc, dd, aa, data[ 1], 15);
   HH(aa, bb, cc, dd, data[ 2], 14);
   HH(dd, aa, bb, cc, data[ 7],  8);
   HH(cc, dd, aa, bb, data[ 0], 13);
   HH(bb, cc, dd, aa, data[ 6],  6);
   HH(aa, bb, cc, dd, data[13],  5);
   HH(dd, aa, bb, cc, data[11], 12);
   HH(cc, dd, aa, bb, data[ 5],  7);
   HH(bb, cc, dd, aa, data[12],  5);

   /* round 4 */
   II(aa, bb, cc, dd, data[ 1], 11);
   II(dd, aa, bb, cc, data[ 9], 12);
   II(cc, dd, aa, bb, data[11], 14);
   II(bb, cc, dd, aa, data[10], 15);
   II(aa, bb, cc, dd, data[ 0], 14);
   II(dd, aa, bb, cc, data[ 8], 15);
   II(cc, dd, aa, bb, data[12],  9);
   II(bb, cc, dd, aa, data[ 4],  8);
   II(aa, bb, cc, dd, data[13],  9);
   II(dd, aa, bb, cc, data[ 3], 14);
   II(cc, dd, aa, bb, data[ 7],  5);
   II(bb, cc, dd, aa, data[15],  6);
   II(aa, bb, cc, dd, data[14],  8);
   II(dd, aa, bb, cc, data[ 5],  6);
   II(cc, dd, aa, bb, data[ 6],  5);
   II(bb, cc, dd, aa, data[ 2], 12);

   /* parallel round 1 */
   III(aaa, bbb, ccc, ddd, data[ 5],  8); 
   III(ddd, aaa, bbb, ccc, data[14],  9);
   III(ccc, ddd, aaa, bbb, data[ 7],  9);
   III(bbb, ccc, ddd, aaa, data[ 0], 11);
   III(aaa, bbb, ccc, ddd, data[ 9], 13);
   III(ddd, aaa, bbb, ccc, data[ 2], 15);
   III(ccc, ddd, aaa, bbb, data[11], 15);
   III(bbb, ccc, ddd, aaa, data[ 4],  5);
   III(aaa, bbb, ccc, ddd, data[13],  7);
   III(ddd, aaa, bbb, ccc, data[ 6],  7);
   III(ccc, ddd, aaa, bbb, data[15],  8);
   III(bbb, ccc, ddd, aaa, data[ 8], 11);
   III(aaa, bbb, ccc, ddd, data[ 1], 14);
   III(ddd, aaa, bbb, ccc, data[10], 14);
   III(ccc, ddd, aaa, bbb, data[ 3], 12);
   III(bbb, ccc, ddd, aaa, data[12],  6);

   /* parallel round 2 */
   HHH(aaa, bbb, ccc, ddd, data[ 6],  9);
   HHH(ddd, aaa, bbb, ccc, data[11], 13);
   HHH(ccc, ddd, aaa, bbb, data[ 3], 15);
   HHH(bbb, ccc, ddd, aaa, data[ 7],  7);
   HHH(aaa, bbb, ccc, ddd, data[ 0], 12);
   HHH(ddd, aaa, bbb, ccc, data[13],  8);
   HHH(ccc, ddd, aaa, bbb, data[ 5],  9);
   HHH(bbb, ccc, ddd, aaa, data[10], 11);
   HHH(aaa, bbb, ccc, ddd, data[14],  7);
   HHH(ddd, aaa, bbb, ccc, data[15],  7);
   HHH(ccc, ddd, aaa, bbb, data[ 8], 12);
   HHH(bbb, ccc, ddd, aaa, data[12],  7);
   HHH(aaa, bbb, ccc, ddd, data[ 4],  6);
   HHH(ddd, aaa, bbb, ccc, data[ 9], 15);
   HHH(ccc, ddd, aaa, bbb, data[ 1], 13);
   HHH(bbb, ccc, ddd, aaa, data[ 2], 11);

   /* parallel round 3 */   
   GGG(aaa, bbb, ccc, ddd, data[15],  9);
   GGG(ddd, aaa, bbb, ccc, data[ 5],  7);
   GGG(ccc, ddd, aaa, bbb, data[ 1], 15);
   GGG(bbb, ccc, ddd, aaa, data[ 3], 11);
   GGG(aaa, bbb, ccc, ddd, data[ 7],  8);
   GGG(ddd, aaa, bbb, ccc, data[14],  6);
   GGG(ccc, ddd, aaa, bbb, data[ 6],  6);
   GGG(bbb, ccc, ddd, aaa, data[ 9], 14);
   GGG(aaa, bbb, ccc, ddd, data[11], 12);
   GGG(ddd, aaa, bbb, ccc, data[ 8], 13);
   GGG(ccc, ddd, aaa, bbb, data[12],  5);
   GGG(bbb, ccc, ddd, aaa, data[ 2], 14);
   GGG(aaa, bbb, ccc, ddd, data[10], 13);
   GGG(ddd, aaa, bbb, ccc, data[ 0], 13);
   GGG(ccc, ddd, aaa, bbb, data[ 4],  7);
   GGG(bbb, ccc, ddd, aaa, data[13],  5);

   /* parallel round 4 */
   FFF(aaa, bbb, ccc, ddd, data[ 8], 15);
   FFF(ddd, aaa, bbb, ccc, data[ 6],  5);
   FFF(ccc, ddd, aaa, bbb, data[ 4],  8);
   FFF(bbb, ccc, ddd, aaa, data[ 1], 11);
   FFF(aaa, bbb, ccc, ddd, data[ 3], 14);
   FFF(ddd, aaa, bbb, ccc, data[11], 14);
   FFF(ccc, ddd, aaa, bbb, data[15],  6);
   FFF(bbb, ccc, ddd, aaa, data[ 0], 14);
   FFF(aaa, bbb, ccc, ddd, data[ 5],  6);
   FFF(ddd, aaa, bbb, ccc, data[12],  9);
   FFF(ccc, ddd, aaa, bbb, data[ 2], 12);
   FFF(bbb, ccc, ddd, aaa, data[13],  9);
   FFF(aaa, bbb, ccc, ddd, data[ 9], 12);
   FFF(ddd, aaa, bbb, ccc, data[ 7],  5);
   FFF(ccc, ddd, aaa, bbb, data[10], 15);
   FFF(bbb, ccc, ddd, aaa, data[14],  8);

   /* combine results */
   ddd += cc + digest[1];               /* final result for digest[0] */
   digest[1] = digest[2] + dd + aaa;
   digest[2] = digest[3] + aa + bbb;
   digest[3] = digest[0] + bb + ccc;
   digest[0] = ddd;

   return;
}

GxsnmpRipemd128*   gxsnmp_ripemd128_new()
{
  GxsnmpRipemd128* ripemd128;

  ripemd128 = g_object_new (gxsnmp_ripemd128_get_type (), NULL);

  return ripemd128;
}

