/*
 * OTP hash generation.
 *
 * Copyright (c) 2006 Andrey Panin <pazke@donpac.ru>
 *
 * This software is released under the MIT license.
 */

#include "lib.h"
#include "md4.h"
#include "md5.h"
#include "sha1.h"

#include "otp.h"

struct digest {
	const char *name;
	void (*init)(void *ctx);
	void (*update)(void *ctx, const void *data, const size_t size);
	void (*final)(void *ctx, void *res);
	void (*otp_final)(void *ctx, void *res);
};

struct digest_context {
	const struct digest *digest;
	union {
		struct md4_context md4_ctx;
		struct md5_context md5_ctx;
		struct sha1_ctxt sha1_ctx;
	} ctx;
};

static void md4_fold(struct md4_context *ctx, void *res)
{
	uint32_t tmp[4], *p = res;

	md4_final(ctx, (unsigned char *) tmp);

	*p++ = tmp[0] ^ tmp[2];
	*p = tmp[1] ^ tmp[3];
}

static void md5_fold(struct md5_context *ctx, void *res)
{
	uint32_t tmp[4], *p = res;

	md5_final(ctx, (unsigned char *) tmp);

	*p++ = tmp[0] ^ tmp[2];
	*p = tmp[1] ^ tmp[3];
}

/*
 * Sometimes I simply can't look at code generated by gcc.
 */
static inline uint32_t swab_uint32(uint32_t val)
{
#if defined(__GNUC__) && defined(__i386__)
	asm("xchgb %b0, %h0\n"
	    "rorl $16, %0\n"
	    "xchgb %b0, %h0\n"
	    :"=q" (val)
	    : "0" (val));
#else
	val = ((val & 0xff) << 24) | ((val & 0xff00) << 8) |
		((val & 0xff0000) >> 8) | ((val >> 24) & 0xff);
#endif
	return val;
}

static void sha1_fold(struct sha1_ctxt *ctx, void *res)
{
	uint32_t tmp[5], *p = res;

	sha1_result(ctx, tmp);

	*p++ = swab_uint32(tmp[0] ^ tmp[2] ^ tmp[4]);
	*p = swab_uint32(tmp[1] ^ tmp[3]);
}


#define F_INIT(name) ((void (*)(void *)) (name))
#define F_UPDATE(name) ((void (*)(void *, const void *, size_t)) (name))
#define F_FINAL(name) ((void (*)(void *, void *)) (name))
#define F_FOLD(name) ((void (*)(void *, void *)) (name))

static const struct digest digests[] = {
	{ "md4",  F_INIT(md4_init),  F_UPDATE(md4_update), F_FINAL(md4_final),   F_FOLD(md4_fold) },
	{ "md5",  F_INIT(md5_init),  F_UPDATE(md5_update), F_FINAL(md5_final),   F_FOLD(md5_fold) },
	{ "sha1", F_INIT(sha1_init), F_UPDATE(sha1_loop),  F_FINAL(sha1_result), F_FOLD(sha1_fold) },
};

#undef F_INIT
#undef F_UPDATE
#undef F_FINAL
#undef F_FOLD

const char *digest_name(unsigned int algo)
{
	i_assert(algo < N_ELEMENTS(digests));

	return digests[algo].name;
}

int digest_find(const char *name)
{
	unsigned int i;

	for (i = 0; i < N_ELEMENTS(digests); i++)
		if (strcmp(name, digests[i].name) == 0)
			return i;

	return -1;
}

void digest_init(struct digest_context *ctx, const unsigned int algo)
{
	i_assert(algo < N_ELEMENTS(digests));

	ctx->digest = digests + algo;
	ctx->digest->init(&ctx->ctx);
}

void digest_update(struct digest_context *ctx, const void *data,
		 const size_t size)
{
	ctx->digest->update(&ctx->ctx, data, size);
}

void digest_final(struct digest_context *ctx, unsigned char *result)
{
	ctx->digest->final(&ctx->ctx, result);
}

void digest_otp_final(struct digest_context *ctx, unsigned char *result)
{
	ctx->digest->otp_final(&ctx->ctx, result);
}

void otp_hash(unsigned int algo, const char *seed, const char *passphrase,
	      unsigned int step, unsigned char *result)
{
	struct digest_context ctx;

	digest_init(&ctx, algo);
	digest_update(&ctx, seed, strlen(seed));
	digest_update(&ctx, passphrase, strlen(passphrase));
	digest_otp_final(&ctx, result);

	for (unsigned int i = 0; i < step; i++) {
		digest_init(&ctx, algo);
		digest_update(&ctx, result, OTP_HASH_SIZE);
		digest_otp_final(&ctx, result);
	}
}

void otp_next_hash(unsigned int algo, const unsigned char *prev,
		   unsigned char *result)
{
	struct digest_context ctx;

	digest_init(&ctx, algo);
	digest_update(&ctx, prev, OTP_HASH_SIZE);
	digest_otp_final(&ctx, result);
}
