/* sha2.c -- SHA-2 digest algorithms native implementations  */
/* Copyright (c) 2015 Alexei Lozovsky.  All rights reserved. */
/* BSD-style license: http://synthcode.com/license.txt       */

#if !(SEXP_UINT8_DEFINED && SEXP_UINT32_DEFINED)
# error SHA-2 requires exact 8-bit and 32-bit integers to be available
#endif

/*
 * SHA-2 algorithms are described in RFC 6234:
 *
 *    http://tools.ietf.org/html/rfc6234
 */

/* Initial hash vector for SHA-224 */
static const sexp_uint32_t h224[8] = {
  0xC1059ED8UL, 0x367CD507UL, 0x3070DD17UL, 0xF70E5939UL,
  0xFFC00B31UL, 0x68581511UL, 0x64F98FA7UL, 0xBEFA4FA4UL,
};

/* Initial hash vector for SHA-256 */
static const sexp_uint32_t h256[8] = {
  0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL,
  0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL,
};

/* Round constants for SHA-224/256 */
static const sexp_uint32_t k256[64] = {
  0x428A2F98UL, 0x71374491UL, 0xB5C0FBCFUL, 0xE9B5DBA5UL,
  0x3956C25BUL, 0x59F111F1UL, 0x923F82A4UL, 0xAB1C5ED5UL,
  0xD807AA98UL, 0x12835B01UL, 0x243185BEUL, 0x550C7DC3UL,
  0x72BE5D74UL, 0x80DEB1FEUL, 0x9BDC06A7UL, 0xC19BF174UL,
  0xE49B69C1UL, 0xEFBE4786UL, 0x0FC19DC6UL, 0x240CA1CCUL,
  0x2DE92C6FUL, 0x4A7484AAUL, 0x5CB0A9DCUL, 0x76F988DAUL,
  0x983E5152UL, 0xA831C66DUL, 0xB00327C8UL, 0xBF597FC7UL,
  0xC6E00BF3UL, 0xD5A79147UL, 0x06CA6351UL, 0x14292967UL,
  0x27B70A85UL, 0x2E1B2138UL, 0x4D2C6DFCUL, 0x53380D13UL,
  0x650A7354UL, 0x766A0ABBUL, 0x81C2C92EUL, 0x92722C85UL,
  0xA2BFE8A1UL, 0xA81A664BUL, 0xC24B8B70UL, 0xC76C51A3UL,
  0xD192E819UL, 0xD6990624UL, 0xF40E3585UL, 0x106AA070UL,
  0x19A4C116UL, 0x1E376C08UL, 0x2748774CUL, 0x34B0BCB5UL,
  0x391C0CB3UL, 0x4ED8AA4AUL, 0x5B9CCA4FUL, 0x682E6FF3UL,
  0x748F82EEUL, 0x78A5636FUL, 0x84C87814UL, 0x8CC70208UL,
  0x90BEFFFAUL, 0xA4506CEBUL, 0xBEF9A3F7UL, 0xC67178F2UL,
};

/* Supported digest types */
enum sha_type {
  SHA_TYPE_224,
  SHA_TYPE_256,
  SHA_TYPE_MAX
};

/* Intermediate digest computation state */
struct sha_context {
  enum sha_type type;
  char sealed;
  sexp_uint_t len;
  sexp_uint32_t hash256[8];
  sexp_uint8_t buffer[128]; /* enough for all SHA-2 */
};

/* = SHA-224/256 implementation ===================================== */

#define ror32(v, a) (((v) >> (a)) | ((v) << (32 - (a))))

static void sha_224_256_round (const sexp_uint8_t chunk[64],
                               sexp_uint32_t hash[8]) {
  int i;
  sexp_uint32_t w[64];
  sexp_uint32_t tmp1, tmp2;
  sexp_uint32_t a, b, c, d, e, f, g, h;
  /* Initialize schedule array */
  for (i = 0; i < 16; i++) {
    w[i] = (chunk[4*i + 0] << 24)
         | (chunk[4*i + 1] << 16)
         | (chunk[4*i + 2] <<  8)
         | (chunk[4*i + 3] <<  0);
  }
  for (i = 16; i < 64; i++) {
    w[i] = w[i - 16]
         + (ror32(w[i-15], 7) ^ ror32(w[i-15], 18) ^ (w[i-15] >> 3))
         + w[i - 7]
         + (ror32(w[i-2], 17) ^ ror32(w[i-2], 19) ^ (w[i-2] >> 10));
  }
  /* Initialize working variables */
  a = hash[0]; b = hash[1]; c = hash[2]; d = hash[3];
  e = hash[4]; f = hash[5]; g = hash[6]; h = hash[7];
  /* Main loop */
  for (i = 0; i < 64; i++) {
    tmp1 = h
         + (ror32(e, 6) ^ ror32(e, 11) ^ ror32(e, 25))
         + ((e & f) ^ ((~e) & g))
         + k256[i]
         + w[i];
    tmp2 = (ror32(a, 2) ^ ror32(a, 13) ^ ror32(a, 22))
         + ((a & b) ^ (a & c) ^ (b & c));
    h = g; g = f; f = e; e = d + tmp1;
    d = c; c = b; b = a; a = tmp1 + tmp2;
  }
  /* Update hash values */
  hash[0] += a; hash[1] += b; hash[2] += c; hash[3] += d;
  hash[4] += e; hash[5] += f; hash[6] += g; hash[7] += h;
}

static void sha_224_256_remainder (sexp_uint8_t chunk[64], sexp_uint_t offset,
                                   sexp_uint_t len_bits, sexp_uint32_t hash[8]) {
  int i;
  /* Pad with '1' bit and zeros */
  chunk[offset] = 0x80;
  memset(chunk + offset + 1, 0, 64 - offset - 1);
  /* If we can't fit the length, use an additional chunk */
  if (offset >= 56) {
    sha_224_256_round(chunk, hash);
    memset(chunk, 0, 64);
  }
  /* Append the message length in bits as big-endian 64-bit integer */
  for (i = 63; i >= 56; i--) {
    chunk[i] = len_bits & 0xFF;
    len_bits >>= 8;
  }
  sha_224_256_round(chunk, hash);
}

/* = Allocating computation context ================================= */

sexp sexp_start_sha (sexp ctx, sexp self, unsigned type, struct sha_context* v) {
  sexp res;
  struct sha_context *sha;
  sexp_uint_t sha_context_tag;
  if (type >= SHA_TYPE_MAX)
    return sexp_xtype_exception(ctx, self, "SHA-2 digest type not supported",
                                sexp_make_fixnum(type));
  (void)v; /* We receive this phony argument to access the type tag of
      sha_context and still be able to return an error with Chibi FFI */
  sha_context_tag = sexp_unbox_fixnum(sexp_vector_ref(sexp_opcode_argn_type(self), SEXP_ZERO));
  res = sexp_alloc_tagged(ctx, sexp_sizeof(cpointer), sha_context_tag);
  if (sexp_exceptionp(res))
    return res;
  sha = calloc(1, sizeof(*sha));
  sha->type = type;
  switch (type) {
  case SHA_TYPE_224:
    memcpy(sha->hash256, h224, sizeof(h224));
    break;
  case SHA_TYPE_256:
    memcpy(sha->hash256, h256, sizeof(h256));
    break;
  default:
    break;
  }
  sexp_cpointer_value(res) = sha;
  sexp_freep(res) = 1;
  return res;
}

/* = Processing incoming data ======================================= */

static sexp sha_224_256_add_bytes (struct sha_context *sha,
                                   const sexp_uint8_t *src, sexp_uint_t len) {
  sexp_uint_t src_offset, buf_offset;
  /* Realign (src + src_offset) to 64 bytes */
  src_offset = 0;
  buf_offset = sha->len % 64;
  sha->len += len;
  if (buf_offset) {
    while ((buf_offset < 64) && (src_offset < len))
      sha->buffer[buf_offset++] = src[src_offset++];
    if (buf_offset == 64)
      sha_224_256_round(sha->buffer, sha->hash256);
    else
      return SEXP_VOID;
  }
  /* Process whole chunks without copying them */
  if (len >= 64) {
    for ( ; src_offset <= (len - 64); src_offset += 64)
      sha_224_256_round(src + src_offset, sha->hash256);
  }
  /* Copy the remainder into the buffer */
  if (src_offset < len)
    memcpy(sha->buffer + buf_offset, src + src_offset, len - src_offset);
  return SEXP_VOID;
}

static sexp sha_add_bytes (sexp ctx, sexp self, struct sha_context *sha,
                           const char* data, sexp_uint_t len) {
  switch (sha->type) {
  case SHA_TYPE_224:
  case SHA_TYPE_256:
    return sha_224_256_add_bytes(sha, (const sexp_uint8_t*) data, len);
  default:
    return sexp_xtype_exception(ctx, self, "unexpected context type",
                                sexp_make_fixnum(sha->type));
  }
}

sexp sexp_add_sha_data (sexp ctx, sexp self, struct sha_context *sha, sexp data) {
  if (sha->sealed)
    return sexp_xtype_exception(ctx, self, "cannot add to sealed context", data);
  if (sexp_bytesp(data))
    return sha_add_bytes(ctx, self, sha, sexp_bytes_data(data), sexp_bytes_length(data));
  if (sexp_stringp(data))
    return sha_add_bytes(ctx, self, sha, sexp_string_data(data), sexp_string_size(data));
  return sexp_xtype_exception(ctx, self, "data type not supported", data);
}

/* = Extracting computed digest ===================================== */

static const char *hex = "0123456789abcdef";

static sexp sha_224_256_hash_string (sexp ctx, sexp self,
                                     const sexp_uint32_t hash[8], int count) {
  sexp res;
  int i, j;
  sexp_uint32_t next_word;
  /* Allocate a string of target length */
  res = sexp_make_string(ctx, sexp_make_fixnum(count * 8), SEXP_VOID);
  if (sexp_exceptionp(res))
    return res;
  /* Write 32-bit words as nibbles in big-endian order */
  for (i = 0; i < count; i++) {
    next_word = hash[i];
    for (j = 7; j >= 0; j--) {
      sexp_string_data(res)[8*i + j] = hex[next_word & 0xF];
      next_word >>= 4;
    }
  }
  return res;
}

sexp sexp_get_sha (sexp ctx, sexp self, struct sha_context *sha) {
  if (!sha->sealed) {
    sha->sealed = 1;
    switch (sha->type) {
    case SHA_TYPE_224:
    case SHA_TYPE_256:
      sha_224_256_remainder(sha->buffer, sha->len % 64,
                            sha->len * 8, sha->hash256);
      break;
    default:
      break;
    }
  }
  switch (sha->type) {
  case SHA_TYPE_224:
    return sha_224_256_hash_string(ctx, self, sha->hash256, 7);
  case SHA_TYPE_256:
    return sha_224_256_hash_string(ctx, self, sha->hash256, 8);
  default:
    return sexp_xtype_exception(ctx, self, "unexpected context type",
                                sexp_make_fixnum(sha->type));
  }
}