/*
 * fontconfig/src/fchash.c
 *
 * Copyright © 2003 Keith Packard
 * Copyright © 2013 Red Hat, Inc.
 * Red Hat Author(s): Akira TAGOH
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the author(s) not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  The authors make no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "fcint.h"
#include <stdio.h>
#include <string.h>

#define ROTRN(w, v, n)	((((FcChar32)v) >> n) | (((FcChar32)v) << (w - n)))
#define ROTR32(v, n)	ROTRN(32, v, n)
#define SHR(v, n)	(v >> n)
#define Ch(x, y, z)	((x & y) ^ (~x & z))
#define Maj(x, y, z)	((x & y) ^ (x & z) ^ (y & z))
#define SS0(x)		(ROTR32(x, 2) ^ ROTR32(x, 13) ^ ROTR32(x, 22))
#define SS1(x)		(ROTR32(x, 6) ^ ROTR32(x, 11) ^ ROTR32(x, 25))
#define ss0(x)		(ROTR32(x, 7) ^ ROTR32(x, 18) ^ SHR(x, 3))
#define ss1(x)		(ROTR32(x, 17) ^ ROTR32(x, 19) ^ SHR(x, 10))


static FcChar32 *
FcHashInitSHA256Digest (void)
{
    int i;
    static const FcChar32 h[] = {
	0x6a09e667UL, 0xbb67ae85UL, 0x3c6ef372UL, 0xa54ff53aUL,
	0x510e527fUL, 0x9b05688cUL, 0x1f83d9abUL, 0x5be0cd19UL
    };
    FcChar32 *ret = malloc (sizeof (FcChar32) * 8);

    if (!ret)
	return NULL;

    for (i = 0; i < 8; i++)
	ret[i] = h[i];

    return ret;
}

static void
FcHashComputeSHA256Digest (FcChar32   *hash,
			   const char *block)
{
    static const FcChar32 k[] = {
	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
    };
    FcChar32 w[64], i, j, t1, t2;
    FcChar32 a, b, c, d, e, f, g, h;

#define H(n)	(hash[n])

    a = H(0);
    b = H(1);
    c = H(2);
    d = H(3);
    e = H(4);
    f = H(5);
    g = H(6);
    h = H(7);

    for (i = 0; i < 16; i++)
    {
	j =  (block[(i * 4) + 0] & 0xff) << (8 * 3);
	j |= (block[(i * 4) + 1] & 0xff) << (8 * 2);
	j |= (block[(i * 4) + 2] & 0xff) << (8 * 1);
	j |= (block[(i * 4) + 3] & 0xff);
	w[i] = j;
    }
    for (i = 16; i < 64; i++)
	w[i] = ss1(w[i - 2]) + w[i - 7] + ss0(w[i - 15]) + w[i - 16];

    for (i = 0; i < 64; i++)
    {
	t1 = h + SS1(e) + Ch(e, f, g) + k[i] + w[i];
	t2 = SS0(a) + Maj(a, b, c);
	h = g;
	g = f;
	f = e;
	e = d + t1;
	d = c;
	c = b;
	b = a;
	a = t1 + t2;
    }

    H(0) += a;
    H(1) += b;
    H(2) += c;
    H(3) += d;
    H(4) += e;
    H(5) += f;
    H(6) += g;
    H(7) += h;

#undef H
}

static FcChar8 *
FcHashSHA256ToString (FcChar32 *hash)
{
    FcChar8 *ret = NULL;
    static const char hex[] = "0123456789abcdef";
    int i, j;

    if (hash)
    {
	ret = malloc (sizeof (FcChar8) * (8 * 8 + 7 + 1));
	if (!ret)
	    return NULL;
	memcpy (ret, "sha256:", 7);
#define H(n)	hash[n]
	for (i = 0; i < 8; i++)
	{
	    FcChar32 v = H(i);

	    for (j = 0; j < 8; j++)
		ret[7 + (i * 8) + j] = hex[(v >> (28 - j * 4)) & 0xf];
	}
	ret[7 + i * 8] = 0;
#undef H
	free (hash);
    }

    return ret;
}

FcChar8 *
FcHashGetSHA256Digest (const FcChar8 *input_strings,
		       size_t         len)
{
    size_t i, round_len = len / 64;
    char block[64];
    FcChar32 *ret = FcHashInitSHA256Digest ();

    if (!ret)
	return NULL;

    for (i = 0; i < round_len; i++)
    {
	FcHashComputeSHA256Digest (ret, (const char *)&input_strings[i * 64]);
    }
    /* padding */
    if ((len % 64) != 0)
	memcpy (block, &input_strings[len / 64], len % 64);
    memset (&block[len % 64], 0, 64 - (len % 64));
    block[len % 64] = 0x80;
    if ((64 - (len % 64)) < 9)
    {
	/* process a block once */
	FcHashComputeSHA256Digest (ret, block);
	memset (block, 0, 64);
    }
    /* set input size at the end */
    len *= 8;
    block[63 - 0] =  len        & 0xff;
    block[63 - 1] = (len >>  8) & 0xff;
    block[63 - 2] = (len >> 16) & 0xff;
    block[63 - 3] = (len >> 24) & 0xff;
    block[63 - 4] = (len >> 32) & 0xff;
    block[63 - 5] = (len >> 40) & 0xff;
    block[63 - 6] = (len >> 48) & 0xff;
    block[63 - 7] = (len >> 56) & 0xff;
    FcHashComputeSHA256Digest (ret, block);

    return FcHashSHA256ToString (ret);
}

FcChar8 *
FcHashGetSHA256DigestFromFile (const FcChar8 *filename)
{
    FILE *fp = fopen ((const char *)filename, "rb");
    char ibuf[64];
    FcChar32 *ret;
    size_t len;
    struct stat st;

    if (!fp)
	return NULL;

    if (FcStat (filename, &st))
	goto bail0;

    ret = FcHashInitSHA256Digest ();
    if (!ret)
	return NULL;

    while (!feof (fp))
    {
	if ((len = fread (ibuf, sizeof (char), 64, fp)) < 64)
	{
	    long v;

	    /* add a padding */
	    memset (&ibuf[len], 0, 64 - len);
	    ibuf[len] = 0x80;
	    if ((64 - len) < 9)
	    {
		/* process a block once */
		FcHashComputeSHA256Digest (ret, ibuf);
		memset (ibuf, 0, 64);
	    }
	    /* set input size at the end */
	    v = (long)st.st_size * 8;
	    ibuf[63 - 0] =  v        & 0xff;
	    ibuf[63 - 1] = (v >>  8) & 0xff;
	    ibuf[63 - 2] = (v >> 16) & 0xff;
	    ibuf[63 - 3] = (v >> 24) & 0xff;
	    ibuf[63 - 4] = (v >> 32) & 0xff;
	    ibuf[63 - 5] = (v >> 40) & 0xff;
	    ibuf[63 - 6] = (v >> 48) & 0xff;
	    ibuf[63 - 7] = (v >> 56) & 0xff;
	    FcHashComputeSHA256Digest (ret, ibuf);
	    break;
	}
	else
	{
	    FcHashComputeSHA256Digest (ret, ibuf);
	}
    }
    fclose (fp);

    return FcHashSHA256ToString (ret);

bail0:
    fclose (fp);
    return NULL;
}