// tiger.cpp - originally written and placed in the public domain by Wei Dai

#include "pch.h"
#include "config.h"

#include "tiger.h"
#include "misc.h"
#include "cpu.h"

#if defined(CRYPTOPP_DISABLE_TIGER_ASM)
# undef CRYPTOPP_X86_ASM_AVAILABLE
# undef CRYPTOPP_X32_ASM_AVAILABLE
# undef CRYPTOPP_X64_ASM_AVAILABLE
# undef CRYPTOPP_SSE2_ASM_AVAILABLE
#endif

NAMESPACE_BEGIN(CryptoPP)

std::string Tiger::AlgorithmProvider() const
{
#ifndef CRYPTOPP_DISABLE_TIGER_ASM
# if CRYPTOPP_SSE2_ASM_AVAILABLE
	if (HasSSE2())
		return "SSE2";
# endif
#endif
	return "C++";
}

void Tiger::InitState(HashWordType *state)
{
	state[0] = W64LIT(0x0123456789ABCDEF);
	state[1] = W64LIT(0xFEDCBA9876543210);
	state[2] = W64LIT(0xF096A5B4C3B2E187);
}

void Tiger::TruncatedFinal(byte *digest, size_t digestSize)
{
	CRYPTOPP_ASSERT(digest != NULLPTR);
	ThrowIfInvalidTruncatedSize(digestSize);

	PadLastBlock(56, 0x01);
	CorrectEndianess(m_data, m_data, 56);

	m_data[7] = GetBitCountLo();

	Transform(m_state, m_data);
	CorrectEndianess(m_state, m_state, DigestSize());
	std::memcpy(digest, m_state, digestSize);

	Restart();		// reinit for next use
}

void Tiger::Transform (word64 *state, const word64 *data)
{
#if CRYPTOPP_SSE2_ASM_AVAILABLE && CRYPTOPP_BOOL_X86
	if (HasSSE2())
	{
#ifdef __GNUC__
		__asm__ __volatile__
		(
		INTEL_NOPREFIX
		AS_PUSH_IF86(bx)
#else
		AS2(	lea		edx, [table])
		AS2(	mov		eax, state)
		AS2(	mov		esi, data)
#endif
		AS2(	movq	mm0, [eax])
		AS2(	movq	mm1, [eax+1*8])
		AS2(	movq	mm5, mm1)
		AS2(	movq	mm2, [eax+2*8])
		AS2(	movq	mm7, [edx+4*2048+0*8])
		AS2(	movq	mm6, [edx+4*2048+1*8])
		AS2(	mov		ecx, esp)
		AS2(	and		esp, 0xfffffff0)
		AS2(	sub		esp, 8*8)
		AS_PUSH_IF86(cx)

#define SSE2_round(a,b,c,x,mul) \
		AS2(	pxor	c, [x])\
		AS2(	movd	ecx, c)\
		AS2(	movzx	edi, cl)\
		AS2(	movq	mm3, [edx+0*2048+edi*8])\
		AS2(	movzx	edi, ch)\
		AS2(	movq	mm4, [edx+3*2048+edi*8])\
		AS2(	shr		ecx, 16)\
		AS2(	movzx	edi, cl)\
		AS2(	pxor	mm3, [edx+1*2048+edi*8])\
		AS2(	movzx	edi, ch)\
		AS2(	pxor	mm4, [edx+2*2048+edi*8])\
		AS3(	pextrw	ecx, c, 2)\
		AS2(	movzx	edi, cl)\
		AS2(	pxor	mm3, [edx+2*2048+edi*8])\
		AS2(	movzx	edi, ch)\
		AS2(	pxor	mm4, [edx+1*2048+edi*8])\
		AS3(	pextrw	ecx, c, 3)\
		AS2(	movzx	edi, cl)\
		AS2(	pxor	mm3, [edx+3*2048+edi*8])\
		AS2(	psubq	a, mm3)\
		AS2(	movzx	edi, ch)\
		AS2(	pxor	mm4, [edx+0*2048+edi*8])\
		AS2(	paddq	b, mm4)\
		SSE2_mul_##mul(b)

#define SSE2_mul_5(b)	\
		AS2(	movq	mm3, b)\
		AS2(	psllq	b, 2)\
		AS2(	paddq	b, mm3)

#define SSE2_mul_7(b)	\
		AS2(	movq	mm3, b)\
		AS2(	psllq	b, 3)\
		AS2(	psubq	b, mm3)

#define SSE2_mul_9(b)	\
		AS2(	movq	mm3, b)\
		AS2(	psllq	b, 3)\
		AS2(	paddq	b, mm3)

#define label2_5 1
#define label2_7 2
#define label2_9 3

#define SSE2_pass(A,B,C,mul,X)	\
		AS2(	xor		ebx, ebx)\
		ASL(mul)\
		SSE2_round(A,B,C,X+0*8+ebx,mul)\
		SSE2_round(B,C,A,X+1*8+ebx,mul)\
		AS2(	cmp		ebx, 6*8)\
		ASJ(	je,		label2_##mul, f)\
		SSE2_round(C,A,B,X+2*8+ebx,mul)\
		AS2(	add		ebx, 3*8)\
		ASJ(	jmp,	mul, b)\
		ASL(label2_##mul)

#define SSE2_key_schedule(Y,X) \
		AS2(	movq	mm3, [X+7*8])\
		AS2(	pxor	mm3, mm6)\
		AS2(	movq	mm4, [X+0*8])\
		AS2(	psubq	mm4, mm3)\
		AS2(	movq	[Y+0*8], mm4)\
		AS2(	pxor	mm4, [X+1*8])\
		AS2(	movq	mm3, mm4)\
		AS2(	movq	[Y+1*8], mm4)\
		AS2(	paddq	mm4, [X+2*8])\
		AS2(	pxor	mm3, mm7)\
		AS2(	psllq	mm3, 19)\
		AS2(	movq	[Y+2*8], mm4)\
		AS2(	pxor	mm3, mm4)\
		AS2(	movq	mm4, [X+3*8])\
		AS2(	psubq	mm4, mm3)\
		AS2(	movq	[Y+3*8], mm4)\
		AS2(	pxor	mm4, [X+4*8])\
		AS2(	movq	mm3, mm4)\
		AS2(	movq	[Y+4*8], mm4)\
		AS2(	paddq	mm4, [X+5*8])\
		AS2(	pxor	mm3, mm7)\
		AS2(	psrlq	mm3, 23)\
		AS2(	movq	[Y+5*8], mm4)\
		AS2(	pxor	mm3, mm4)\
		AS2(	movq	mm4, [X+6*8])\
		AS2(	psubq	mm4, mm3)\
		AS2(	movq	[Y+6*8], mm4)\
		AS2(	pxor	mm4, [X+7*8])\
		AS2(	movq	mm3, mm4)\
		AS2(	movq	[Y+7*8], mm4)\
		AS2(	paddq	mm4, [Y+0*8])\
		AS2(	pxor	mm3, mm7)\
		AS2(	psllq	mm3, 19)\
		AS2(	movq	[Y+0*8], mm4)\
		AS2(	pxor	mm3, mm4)\
		AS2(	movq	mm4, [Y+1*8])\
		AS2(	psubq	mm4, mm3)\
		AS2(	movq	[Y+1*8], mm4)\
		AS2(	pxor	mm4, [Y+2*8])\
		AS2(	movq	mm3, mm4)\
		AS2(	movq	[Y+2*8], mm4)\
		AS2(	paddq	mm4, [Y+3*8])\
		AS2(	pxor	mm3, mm7)\
		AS2(	psrlq	mm3, 23)\
		AS2(	movq	[Y+3*8], mm4)\
		AS2(	pxor	mm3, mm4)\
		AS2(	movq	mm4, [Y+4*8])\
		AS2(	psubq	mm4, mm3)\
		AS2(	movq	[Y+4*8], mm4)\
		AS2(	pxor	mm4, [Y+5*8])\
		AS2(	movq	[Y+5*8], mm4)\
		AS2(	paddq	mm4, [Y+6*8])\
		AS2(	movq	[Y+6*8], mm4)\
		AS2(	pxor	mm4, [edx+4*2048+2*8])\
		AS2(	movq	mm3, [Y+7*8])\
		AS2(	psubq	mm3, mm4)\
		AS2(	movq	[Y+7*8], mm3)

		SSE2_pass(mm0, mm1, mm2, 5, esi)
		SSE2_key_schedule(esp+4, esi)
		SSE2_pass(mm2, mm0, mm1, 7, esp+4)
		SSE2_key_schedule(esp+4, esp+4)
		SSE2_pass(mm1, mm2, mm0, 9, esp+4)

		AS2(	pxor	mm0, [eax+0*8])
		AS2(	movq	[eax+0*8], mm0)
		AS2(	psubq	mm1, mm5)
		AS2(	movq	[eax+1*8], mm1)
		AS2(	paddq	mm2, [eax+2*8])
		AS2(	movq	[eax+2*8], mm2)

		AS_POP_IF86(sp)
		AS1(	emms)

#ifdef __GNUC__
		AS_POP_IF86(bx)
		ATT_PREFIX
			:
			: "a" (state), "S" (data), "d" (table)
			: "%ecx", "%edi", "memory", "cc"
		);
#endif
	}
	else
#endif
	{
		word64 a = state[0];
		word64 b = state[1];
		word64 c = state[2];
		word64 Y[8];

#define t1 (table)
#define t2 (table+256)
#define t3 (table+256*2)
#define t4 (table+256*3)

#define round(a,b,c,x,mul) \
	c ^= x; \
	a -= t1[GETBYTE(c,0)] ^ t2[GETBYTE(c,2)] ^ t3[GETBYTE(c,4)] ^ t4[GETBYTE(c,6)]; \
	b += t4[GETBYTE(c,1)] ^ t3[GETBYTE(c,3)] ^ t2[GETBYTE(c,5)] ^ t1[GETBYTE(c,7)]; \
	b *= mul

#define pass(a,b,c,mul,X) {\
	int i=0;\
	while (true)\
	{\
		round(a,b,c,X[i+0],mul); \
		round(b,c,a,X[i+1],mul); \
		if (i==6)\
			break;\
		round(c,a,b,X[i+2],mul); \
		i+=3;\
	}}

#define key_schedule(Y,X) \
	Y[0] = X[0] - (X[7]^W64LIT(0xA5A5A5A5A5A5A5A5)); \
	Y[1] = X[1] ^ Y[0]; \
	Y[2] = X[2] + Y[1]; \
	Y[3] = X[3] - (Y[2] ^ ((~Y[1])<<19)); \
	Y[4] = X[4] ^ Y[3]; \
	Y[5] = X[5] + Y[4]; \
	Y[6] = X[6] - (Y[5] ^ ((~Y[4])>>23)); \
	Y[7] = X[7] ^ Y[6]; \
	Y[0] += Y[7]; \
	Y[1] -= Y[0] ^ ((~Y[7])<<19); \
	Y[2] ^= Y[1]; \
	Y[3] += Y[2]; \
	Y[4] -= Y[3] ^ ((~Y[2])>>23); \
	Y[5] ^= Y[4]; \
	Y[6] += Y[5]; \
	Y[7] -= Y[6] ^ W64LIT(0x0123456789ABCDEF)

		pass(a,b,c,5,data);
		key_schedule(Y,data);
		pass(c,a,b,7,Y);
		key_schedule(Y,Y);
		pass(b,c,a,9,Y);

		state[0] = a ^ state[0];
		state[1] = b - state[1];
		state[2] = c + state[2];
	}
}

NAMESPACE_END
