#include "uulib/chacha.h"

/* perl versions broken on some platforms */
#undef U8TO16_LE
#define U8TO16_LE(p) (   \
  ((U16)((p)[0])     ) | \
  ((U16)((p)[1]) << 8)   \
)
#undef U8TO32_LE
#define U8TO32_LE(p) (    \
  ((U32)((p)[0])      ) | \
  ((U32)((p)[1]) <<  8) | \
  ((U32)((p)[2]) << 16) | \
  ((U32)((p)[3]) << 24)   \
)
#undef U8TO64_LE
#define U8TO64_LE(p) (    \
  ((U64)((p)[0])      ) | \
  ((U64)((p)[1]) <<  8) | \
  ((U64)((p)[2]) << 16) | \
  ((U64)((p)[3]) << 24) | \
  ((U64)((p)[4]) << 32) | \
  ((U64)((p)[5]) << 40) | \
  ((U64)((p)[6]) << 48) | \
  ((U64)((p)[7]) << 56)   \
)
#undef U32TO8_LE
#define U32TO8_LE(p, v) do {       \
  U32 _v = v;                      \
  (p)[0] = (((_v)      ) & 0xFFU); \
  (p)[1] = (((_v) >>  8) & 0xFFU); \
  (p)[2] = (((_v) >> 16) & 0xFFU); \
  (p)[3] = (((_v) >> 24) & 0xFFU); \
} while (0)


#define STATESZ  16        /* words: 4 constant, 8 key, 2 counter, 2 nonce */
#define KEYSZ    40        /* bytes of user supplied key+nonce */
#define CORESZ   64        /* bytes output by core */
#define BUFSZ    16*CORESZ /* bytes we get at a time (1024) */
#define ROUNDS   20

typedef struct {
  U32     state[STATESZ];
  UCHAR   buf[BUFSZ];
  U16     have;
} cc_struct_t;

static cc_struct_t cc;

/* perls ROTL32 broken too */
#define rotl32(x,r) ((((U32)(x)) << (r)) | (((U32)(x)) >> (32 - (r))))

#define QROUND(a,b,c,d) \
  a += b; d = rotl32(d ^ a, 16); \
  c += d; b = rotl32(b ^ c, 12); \
  a += b; d = rotl32(d ^ a,  8); \
  c += d; b = rotl32(b ^ c,  7);


static void cc_init(const UCHAR *seed, IV init_buffer) {
  U32 *x = (U32*)&cc.state;

  x[ 0] = 0x61707865;
  x[ 1] = 0x3320646e;
  x[ 2] = 0x79622d32;
  x[ 3] = 0x6b206574;
  x[ 4] = U8TO32_LE(seed +  0);
  x[ 5] = U8TO32_LE(seed +  4);
  x[ 6] = U8TO32_LE(seed +  8);
  x[ 7] = U8TO32_LE(seed + 12);
  x[ 8] = U8TO32_LE(seed + 16);
  x[ 9] = U8TO32_LE(seed + 20);
  x[10] = U8TO32_LE(seed + 24);
  x[11] = U8TO32_LE(seed + 28);
  x[12] = 0;
  x[13] = 0;
  x[14] = U8TO32_LE(seed + 32);
  x[15] = U8TO32_LE(seed + 36);

  if (init_buffer) {
    memset(cc.buf, 0, BUFSZ);
    cc.have = 0;
  }
}

static void cc_core(UCHAR* buf) {
  U32 *s = cc.state;
  U32 i, x[16];

  memcpy(x, s, 16*sizeof(U32));

  for (i = 0; i<ROUNDS; i+=2) {
    QROUND( x[ 0], x[ 4], x[ 8], x[12] );
    QROUND( x[ 1], x[ 5], x[ 9], x[13] );
    QROUND( x[ 2], x[ 6], x[10], x[14] );
    QROUND( x[ 3], x[ 7], x[11], x[15] );
    QROUND( x[ 0], x[ 5], x[10], x[15] );
    QROUND( x[ 1], x[ 6], x[11], x[12] );
    QROUND( x[ 2], x[ 7], x[ 8], x[13] );
    QROUND( x[ 3], x[ 4], x[ 9], x[14] );
  }

  for (i = 0; i < 16; i++)
    x[i] += s[i];

  for (i = 0; i < 16; i++)
    U32TO8_LE( buf+4*i, x[i] );

  /* inc counter */
  if (++s[12] == 0) s[13]++;
}

static U32 cc_stream(UCHAR* buf, IV n) {
  IV    r = n;
  UCHAR sbuf[CORESZ];

  while (r >= CORESZ) {
    cc_core(buf);
    buf += CORESZ;
    r -= CORESZ;
  }
  if (r > 0) {
    cc_core(sbuf);
    memcpy(buf, sbuf, r);
  }
  return n;
}

static U32 cc_refill() {
  cc.have = cc_stream((UCHAR*)&cc.buf, BUFSZ);
  cc_init(cc.buf, 0);
  memset(cc.buf, 0, KEYSZ);
  cc.have = BUFSZ - KEYSZ;
  return cc.have;
}

/* API */

void cc_seed(const UCHAR *data, UV nbytes) {
  if (nbytes < 40) croak("Need 40 seed bytes\n");
  cc_init(data, 1);
}

void cc_rand16(U16 *out) {
  UCHAR *ptr;

  if (cc.have < 2) cc_refill();
  ptr = cc.buf + BUFSZ - cc.have;
  cc.have -= 2;

  *out = U8TO16_LE(ptr);
}

void cc_rand32(U32 *out) {
  UCHAR *ptr;

  if (cc.have < 4) cc_refill();
  ptr = cc.buf + BUFSZ - cc.have;
  cc.have -= 4;

  *out = U8TO32_LE(ptr);
}

void cc_rand64(U64 *out) {
  UCHAR *ptr;

  if (cc.have < 8) cc_refill();
  ptr = cc.buf + BUFSZ - cc.have;
  cc.have -= 8;

  *out = U8TO64_LE(ptr);
}

void cc_rand128(void *out) {
  U64   a, b;
  UCHAR *ptr;

  if (cc.have < 16) cc_refill();
  ptr = cc.buf + BUFSZ - cc.have;
  cc.have -= 16;

  a = U8TO64_LE(ptr);
  b = U8TO64_LE(ptr);
  *((U64*)out)     = a;
  *(((U64*)out)+8) = b;
}

/* ex:set ts=2 sw=2 itab=spaces: */
