/* $Id: haval_helper.c 218 2010-06-08 17:06:34Z tp $ */
/*
 * Helper code, included (three times !) by HAVAL implementation.
 *
 * TODO: try to merge this with md_helper.c.
 *
 * ==========================(LICENSE BEGIN)============================
 *
 * Copyright (c) 2007-2010  Projet RNRT SAPHIR
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * ===========================(LICENSE END)=============================
 *
 * @author   Thomas Pornin <thomas.pornin@cryptolog.com>
 */

#undef SPH_XCAT
#define SPH_XCAT(a, b)    SPH_XCAT_(a, b)
#undef SPH_XCAT_
#define SPH_XCAT_(a, b)   a ## b

static void
#ifdef SPH_UPTR
SPH_XCAT(SPH_XCAT(haval, PASSES), _short)
#else
SPH_XCAT(haval, PASSES)
#endif
(sph_haval_context *sc, const void *data, size_t len)
{
	unsigned current;

#if SPH_64
	current = (unsigned)sc->count & 127U;
#else
	current = (unsigned)sc->count_low & 127U;
#endif
	while (len > 0) {
		unsigned clen;
#if !SPH_64
		sph_u32 clow, clow2;
#endif

		clen = 128U - current;
		if (clen > len)
			clen = len;
		memcpy(sc->buf + current, data, clen);
		data = (const unsigned char *)data + clen;
		current += clen;
		len -= clen;
		if (current == 128U) {
			DSTATE;
			IN_PREPARE(sc->buf);
			RSTATE;
			SPH_XCAT(CORE, PASSES)(INW);
			WSTATE;
			current = 0;
		}
#if SPH_64
		sc->count += clen;
#else
		clow = sc->count_low;
		clow2 = SPH_T32(clow + clen);
		sc->count_low = clow2;
		if (clow2 < clow)
			sc->count_high ++;
#endif
	}
}

#ifdef SPH_UPTR
static void
SPH_XCAT(haval, PASSES)(sph_haval_context *sc, const void *data, size_t len)
{
	unsigned current;
	size_t orig_len;
#if !SPH_64
	sph_u32 clow, clow2;
#endif
	DSTATE;

	if (len < 256U) {
		SPH_XCAT(SPH_XCAT(haval, PASSES), _short)(sc, data, len);
		return;
	}
#if SPH_64
	current = (unsigned)sc->count & 127U;
#else
	current = (unsigned)sc->count_low & 127U;
#endif
	if (current > 0) {
		unsigned clen;
		clen = 128U - current;
		SPH_XCAT(SPH_XCAT(haval, PASSES), _short)(sc, data, clen);
		data = (const unsigned char *)data + clen;
		len -= clen;
	}
#if !SPH_UNALIGNED
	if (((SPH_UPTR)data & 3U) != 0) {
		SPH_XCAT(SPH_XCAT(haval, PASSES), _short)(sc, data, len);
		return;
	}
#endif
	orig_len = len;
	RSTATE;
	while (len >= 128U) {
		IN_PREPARE(data);
		SPH_XCAT(CORE, PASSES)(INW);
		data = (const unsigned char *)data + 128U;
		len -= 128U;
	}
	WSTATE;
	if (len > 0)
		memcpy(sc->buf, data, len);
#if SPH_64
	sc->count += (sph_u64)orig_len;
#else
	clow = sc->count_low;
	clow2 = SPH_T32(clow + orig_len);
	sc->count_low = clow2;
	if (clow2 < clow)
		sc->count_high ++;
	orig_len >>= 12;
	orig_len >>= 10;
	orig_len >>= 10;
	sc->count_high += orig_len;
#endif
}
#endif

static void
SPH_XCAT(SPH_XCAT(haval, PASSES), _close)(sph_haval_context *sc,
	unsigned ub, unsigned n, void *dst)
{
	unsigned current;
	DSTATE;

#if SPH_64
	current = (unsigned)sc->count & 127U;
#else
	current = (unsigned)sc->count_low & 127U;
#endif
	sc->buf[current ++] = (0x01 << n) | ((ub & 0xFF) >> (8 - n));
	RSTATE;
	if (current > 118U) {
		memset(sc->buf + current, 0, 128U - current);

		do {
			IN_PREPARE(sc->buf);
			SPH_XCAT(CORE, PASSES)(INW);
		} while (0);
		current = 0;
	}
	memset(sc->buf + current, 0, 118U - current);
	sc->buf[118] = 0x01 | (PASSES << 3);
	sc->buf[119] = sc->olen << 3;
#if SPH_64
	sph_enc64le_aligned(sc->buf + 120, SPH_T64(sc->count << 3));
#else
	sph_enc32le_aligned(sc->buf + 120, SPH_T32(sc->count_low << 3));
	sph_enc32le_aligned(sc->buf + 124,
		SPH_T32((sc->count_high << 3) | (sc->count_low >> 29)));
#endif

	do {
		IN_PREPARE(sc->buf);
		SPH_XCAT(CORE, PASSES)(INW);
	} while (0);
	WSTATE;

	haval_out(sc, dst);
	haval_init(sc, sc->olen, sc->passes);
}