/* Public domain 2013 Sam Trenholme */

#include "dwsiphash.h"
#include "gauche/priv/dws_adapter.h"
#if SIZEOF_LONG > 4

/* Core sipHash round function, as per spec */
void DwSip_round(DwSH_WORD *v0, DwSH_WORD *v1, DwSH_WORD *v2, DwSH_WORD *v3) {
	*v0 += *v1;
	*v2 += *v3;
	*v1 = (*v1 << DwSH_R1) | (*v1 >> DwSH_R1i);
	*v3 = (*v3 << DwSH_R2) | (*v3 >> DwSH_R2i);
	*v1 ^= *v0;
	*v3 ^= *v2;
	*v0 = (*v0 << DwSH_R3) | (*v0 >> DwSH_R3i);
	*v2 += *v1;
	*v0 += *v3; 
	*v1 = (*v1 << DwSH_R4) | (*v1 >> DwSH_R4i);
	*v3 = (*v3 << DwSH_R5) | (*v3 >> DwSH_R5i);
	*v1 ^= *v2;
	*v3 ^= *v0;
	*v2 = (*v2 << DwSH_R6) | (*v2 >> DwSH_R6i);
}

/* Key setup.  Given two keys, set up the first state and final XOR constant */
void DwSip_ksetup(DwSH_WORD *k0, DwSH_WORD *k1, DwSH_WORD *v0, DwSH_WORD *v1,
		DwSH_WORD *v2, DwSH_WORD *v3, DwSH_WORD *fx) {
	*v0 = *k0 ^ DwSH_kc1;
	*v1 = *k1 ^ DwSH_kc2;
	*v2 = *k0 ^ DwSH_kc3;
	*v3 = *k1 ^ DwSH_kc4;
	*fx = DwSH_fx;
}

/* Convert part of a word in to a message block SipHash can understand */
DwSH_WORD DwSip_getword(uint32_t *offset, uint8_t *str, uint32_t len) {
	uint32_t toffset = *offset;
	int shift = 0;
	DwSH_WORD out = 0;
	do {	
		if(toffset >= len) {
			out |= ((DwSH_WORD)len & 0xff) << (DwSH_BWIDTH - 8);
			*offset = len + 1;
			return out;
		}
		out |= (DwSH_WORD)*(str + toffset) << shift;
		shift += 8;
		toffset++;
	} while(toffset < *offset + DwSH_OWIDTH);
	*offset = toffset;
	return out;
}

/* Calculate the hash for a given string */
DwSH_WORD DwSip_hash(uint8_t *str, uint32_t len, DwSH_WORD k1, DwSH_WORD k2) {
	uint32_t offset = 0;
	int a = 0;
	DwSH_WORD v0 = 0, v1 = 0, v2 = 0, v3 = 0, fx = 0, m = 0;
	DwSip_ksetup(&k1, &k2, &v0, &v1, &v2, &v3, &fx);
	while(offset <= len) {
		m = DwSip_getword(&offset, str, len);
		v3 ^= m;
		for(a = 0; a < DwSH_CROUNDS; a++) {
			DwSip_round(&v0,&v1,&v2,&v3);
		}
		v0 ^= m;
	} 
	v2 ^= fx;
	for(a = 0; a < DwSH_FROUNDS; a++) {
		DwSip_round(&v0,&v1,&v2,&v3);
	}
	return v0 ^ v1 ^ v2 ^ v3;
}
	
#ifdef MAIN
#include <stdio.h>
int main() {
	uint8_t str[64];
	uint32_t len = 15;
	DwSH_WORD o = 0;
	for(len = 0; len < 64; len++) {
		str[len] = len;
		o = DwSip_hash(str, len, 0x0706050403020100ULL, 
			0x0f0e0d0c0b0a0908ULL);
		printf("%016llx\n",o);
	}
	return 0;
}
#endif /* MAIN */

#endif  /* SIZEOF_LONG > 4 */

