/* tth.c - calculate TTH (Tiger Tree Hash) function.
 *
 * Copyright: 2007-2012 Aleksey Kravchenko <rhash.admin@gmail.com>
 *
 * 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.
 *
 * This program  is  distributed  in  the  hope  that it will be useful,  but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  Use this program  at  your own risk!
 */

#include <string.h>
#include "byte_order.h"
#include "tth.h"

/**
 * Initialize context before calculaing hash.
 *
 * @param ctx context to initialize
 */
void rhash_tth_init(tth_ctx *ctx)
{
	rhash_tiger_init(&ctx->tiger);
	ctx->tiger.message[ ctx->tiger.length++ ] = 0x00;
	ctx->block_count = 0;
}

/**
 * The core transformation.
 *
 * @param ctx algorithm state
 */
static void rhash_tth_process_block(tth_ctx *ctx)
{
	uint64_t it;
	unsigned pos = 0;
	unsigned char msg[24];

	for (it = 1; it & ctx->block_count; it <<= 1) {
		rhash_tiger_final(&ctx->tiger, msg);
		rhash_tiger_init(&ctx->tiger);
		ctx->tiger.message[ctx->tiger.length++] = 0x01;
		rhash_tiger_update(&ctx->tiger, (unsigned char*)(ctx->stack + pos), 24);
		/* note: we can cut this step, if the previous rhash_tiger_final saves directly to ctx->tiger.message+25; */
		rhash_tiger_update(&ctx->tiger, msg, 24);
		pos += 3;
	}
	rhash_tiger_final(&ctx->tiger, (unsigned char*)(ctx->stack + pos));
	ctx->block_count++;
}

/**
 * Calculate message hash.
 * Can be called repeatedly with chunks of the message to be hashed.
 *
 * @param ctx the algorithm context containing current hashing state
 * @param msg message chunk
 * @param size length of the message chunk
 */
void rhash_tth_update(tth_ctx *ctx, const unsigned char* msg, size_t size)
{
	size_t rest = 1025 - (size_t)ctx->tiger.length;
	for (;;) {
		if (size < rest) rest = size;
		rhash_tiger_update(&ctx->tiger, msg, rest);
		msg += rest;
		size -= rest;
		if (ctx->tiger.length < 1025) {
			return;
		}

		/* process block hash */
		rhash_tth_process_block(ctx);

		/* init block hash */
		rhash_tiger_init(&ctx->tiger);
		ctx->tiger.message[ ctx->tiger.length++ ] = 0x00;
		rest = 1024;
	}
}

/**
 * Store calculated hash into the given array.
 *
 * @param ctx the algorithm context containing current hashing state
 * @param result calculated hash in binary form
 */
void rhash_tth_final(tth_ctx *ctx, unsigned char result[24])
{
	uint64_t it = 1;
	unsigned pos = 0;
	unsigned char msg[24];
	const unsigned char* last_message;

	/* process the bytes left in the context buffer */
	if (ctx->tiger.length > 1 || ctx->block_count == 0) {
		rhash_tth_process_block(ctx);
	}

	for (; it < ctx->block_count && (it & ctx->block_count) == 0; it <<= 1) pos += 3;
	last_message = (unsigned char*)(ctx->stack + pos);

	for (it <<= 1; it <= ctx->block_count; it <<= 1) {
		/* merge TTH sums in the tree */
		pos += 3;
		if (it & ctx->block_count) {
			rhash_tiger_init(&ctx->tiger);
			ctx->tiger.message[ ctx->tiger.length++ ] = 0x01;
			rhash_tiger_update(&ctx->tiger, (unsigned char*)(ctx->stack + pos), 24);
			rhash_tiger_update(&ctx->tiger, last_message, 24);

			rhash_tiger_final(&ctx->tiger, msg);
			last_message = msg;
		}
	}

	/* save result hash */
	memcpy(ctx->tiger.hash, last_message, tiger_hash_length);
	if (result) memcpy(result, last_message, tiger_hash_length);
}
