/*
 * Copyright (C) 2020 Olivier Chéron <olivier.cheron@gmail.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <crypton_hash_prefix.h>

void CRYPTON_HASHED(finalize_prefix)(struct HASHED_LOWER(ctx) *ctx, const uint8_t *data, uint32_t len, uint32_t n, uint8_t *out)
{
	uint64_t bits[HASHED(BITS_ELEMS)];
	uint8_t *p = (uint8_t *) &bits;
	uint32_t index, padidx, padlen, pos, out_mask;
	static const uint32_t cut_off = HASHED(BLOCK_SIZE) - sizeof(bits);

	/* Make sure n <= len */
	n += (len - n) & constant_time_lt(len, n);

	/* Initial index, based on current context state */
	index = CRYPTON_HASHED(get_index)(ctx);

	/* Final size after n bytes */
	CRYPTON_HASHED(incr_sz)(ctx, bits, n);

	/* Padding index and length */
	padidx = CRYPTON_HASHED(get_index)(ctx);
	padlen = HASHED(BLOCK_SIZE) + cut_off - padidx;
	padlen -= HASHED(BLOCK_SIZE) & constant_time_lt(padidx, cut_off);

	/* Initialize buffers because we will XOR into them */
	memset(ctx->buf + index, 0, HASHED(BLOCK_SIZE) - index);
	memset(out, 0, HASHED(DIGEST_SIZE));
	pos = 0;

	/* Iterate based on the full buffer length, regardless of n, and include
	 * the maximum overhead with padding and size bytes
	 */
	while (pos < len + HASHED(BLOCK_SIZE) + sizeof(bits)) {
		uint8_t b;

		/* Take as many bytes from the input buffer as possible */
		if (pos < len)
			b = *(data++) & (uint8_t) constant_time_lt(pos, n);
		else
			b = 0;

		/* First padding byte */
		b |= 0x80 & (uint8_t) constant_time_eq(pos, n);

		/* Size bytes are always at the end of a block */
		if (index >= cut_off)
			b |= p[index - cut_off] & (uint8_t) constant_time_ge(pos, n + padlen);

		/* Store this byte into the buffer */
		ctx->buf[index++] ^= b;
		pos++;

		/* Process a full block, at a boundary which is independent from n */
		if (index >= HASHED(BLOCK_SIZE)) {
			index = 0;
			HASHED_LOWER(do_chunk)(ctx, (void *) ctx->buf);
			memset(ctx->buf, 0, HASHED(BLOCK_SIZE));

			/* Try to store the result: this is a no-op except when we reach the
			 * actual size based on n, more iterations may continue after that
			 * when len is really larger
			 */
			out_mask = constant_time_eq(pos, n + padlen + sizeof(bits));
			CRYPTON_HASHED(select_digest)(ctx, out, out_mask);
		}
	}
}
