//
//rand-key.c
//
//
//
//
//-UserX 2001/11/19

#include <string.h>
#include "crypt/rand-key.h"
#include "base/mem.h"
#include "base/dblock.h"
#include "base/buffer.h"

RandomKey *randomkeyMake(void) {
	RandomKey *rk = memAlloc(sizeof(RandomKey), "RandomKey", NULL);//ximalloc(sizeof(RandomKey));
	SHA1Init(&rk->Sha1);
	rk->output = dblockMake(RANDOM_KEY_BLOCK_LENGTH, RANDOM_KEY_BLOCK_LENGTH, "randomkeyoutput");
	rk->outputoffset = 0;
	rk->outputcounter = 0;
	rk->key = dblockMake(RANDOM_KEY_LENGTH, RANDOM_KEY_LENGTH, "randomkeykey");
	rk->blowfish = memAlloc(sizeof(BlowfishContext), "RandomKeyBlowfishContext", NULL);//ximalloc(sizeof(BlowfishContext));
	rk->counter = dblockMake(RANDOM_KEY_BLOCK_LENGTH, RANDOM_KEY_BLOCK_LENGTH, "randomkeycounter");
	return rk;
}

void randomkeyMakeKey(RandomKey *rk, DataBlock *seed) {
	int ic;
	DataBlock *db;
	if(rk == NULL) {
		return;
	}
	db = dblockEmpty("randomkeyseedscratch");
	//clear any existing key.
	rk->key = dblockResize(rk->key, 0);
	rk->key->size = 0;

	//crear the count of required zeros
	ic = 0;
	while(rk->key->size < RANDOM_KEY_LENGTH) {
		//make a buffer with seed prepended by a number 0 bytes equal 
		//to the number complete iteration done by this loop.
		db = dblockResize(db, ic);
		db->size = ic;
		memset(db->data, 0, ic);
		db = dblockAppendBlock(db, seed);

		//put this through a SHA1 hash.
		SHA1Init(&rk->Sha1);
		SHA1Update(&rk->Sha1, db->data, db->size);
		db = dblockResize(db, 20);
		db->size = 20;
		SHA1Final(db->data, &rk->Sha1);
		
		
		//Append this to already created key data.
		rk->key = dblockAppendBlock(rk->key, db);

		//increment count of required zeros
		ic++;
	}
	dblockFree(db);
	//crop to the actual keylength.
	rk->key->size = RANDOM_KEY_LENGTH;

	randomkeyRekey(rk);
}

//This should be called after a change of the random key's key
void randomkeyRekey(RandomKey *rk) {
	Blowfish_Init(rk->blowfish, rk->key->data, RANDOM_KEY_LENGTH);

	memset(rk->counter->data, 0, RANDOM_KEY_BLOCK_LENGTH);

	//todo: replace this with triple DES
	//xteabEncode((uint32 *) rk->counter->data, RANDOM_KEY_BLOCK_LENGTH / 4, (uint32 *) rk->key->data);
	Blowfish_Encrypt(rk->blowfish, (unsigned long *)(rk->counter->data), (unsigned long *)(rk->counter->data + 4));

	randomkeyGenerateOutput(rk);
}

void randomkeyCounterIncrement(RandomKey *rk) {
	bufferInc(rk->counter->data, rk->counter->size);
	//int i;
	//for(i = rk->counter->size - 1; i >= 0; i--) {
	//	if(rk->counter->data[i]-- != 0) { //carry if data[i] is zero.
	//		break; //break if no carry
	//	}
	//}
}

//Create a new key from the output of the random generator
void randomkeyGateKey(RandomKey *rk) {
	DataBlock *db = dblockMake(RANDOM_KEY_LENGTH, RANDOM_KEY_LENGTH, "randomkeygate");
	rk->outputcounter = 0;
//todo: consider: appending this to the original seed and do a makekey with that.	
	randomkeyGetBuffer(rk, db->data, db->size);
	
	dblockFree(rk->key);
	rk->key = db;

	Blowfish_Init(rk->blowfish, rk->key->data, RANDOM_KEY_LENGTH);
	randomkeyGenerateOutput(rk);
}

//Increment then encrypt the counter to generate another 
//buffer of output.
void randomkeyGenerateOutput(RandomKey *rk) {

	//increment counter
	randomkeyCounterIncrement(rk);

	rk->outputcounter++;
	if(rk->outputcounter > RANDOM_KEY_PG) {
		randomkeyGateKey(rk);
	}

	//copy the counter.
	memcpy(rk->output->data, rk->counter->data, RANDOM_KEY_BLOCK_LENGTH);

	//encrypt the copy of the counter
	//todo: change this to triple DES
	//xteabEncode((uint32 *) rk->output->data, RANDOM_KEY_BLOCK_LENGTH / 4, (uint32 *) rk->key->data);
	Blowfish_Encrypt(rk->blowfish, (unsigned long *)(rk->output->data), (unsigned long *)(rk->output->data + 4));

	//clear output offset.
	rk->outputoffset = 0;
}

//get a buffer of x bytes length
void randomkeyGetBuffer(RandomKey *rk, uint8 *buffer, int length) {
	if(rk == NULL || buffer == NULL) {
		return;
	}
	for(;length > 0; buffer++, length--, rk->outputoffset++) {
		//check there are still bytes on the output buffer
		if(rk->outputoffset >= rk->output->size) {
			//generate if there are not
			randomkeyGenerateOutput(rk);
		}
		*buffer = rk->output->data[rk->outputoffset];
	}	
}

