//
//crypt.c
//
//Interface for handling a crypto stream.
//
//
//-UserX 2001/11/16

#include <string.h>
#include "pipe/cs-iip11.h"
#include "crypt/sha1.h"
#include "crypt/dh.h"
#include "pipe/pipe.h"
#include "pipe/c-stream.h"
#include "base/mem.h"
#include "base/bignum.h"
#include "base/logger.h"
#include "base/str.h"
#include "base/dblock.h"
#include "crypt/random.h"
#include "crypt/salt.h"
#include "misc/global.h"
#include "base/buffer.h"
#include "crypt/crc.h"

CryptStreamIIP11 blankcryptstreamiip11 = BLANKCRYPTSTREAMIIP11;

CryptStream *csiip11Make(char *csname, char *options) {
	CryptStreamIIP11 *cs = memCopy(&blankcryptstreamiip11, sizeof(CryptStreamIIP11), "CryptStreamIIP11", NULL);
	LOGDEBUG(stringJoin("csiip11Make:", ptrToString(cs)));
	cs->blowfishin = memAlloc(sizeof(BlowfishContext), "CryptStreamBlowfishContext", NULL);
	cs->blowfishout = memAlloc(sizeof(BlowfishContext), "CryptStreamBlowfishContext", NULL);
	return (CryptStream *)cs;
}

void csiip11Init(CryptStreamIIP11 *cs, Pipe *thispipe) {//, Pipe *backpipe
	LOGDEBUG(stringJoin("csiip11Init:", ptrToString(cs)));
	//cs->backPipe = backpipe;
	cs->cs.ParentPipe = thispipe;
}

void csiip11Free(CryptStreamIIP11 *cs) {
	LOGDEBUG(stringJoin("csiip11Free:", ptrToString(cs)));
	if(cs == NULL) {
		return;
	}
	bignumFree(cs->privkey);
	bignumFree(cs->localkey);
	bignumFree(cs->remotekey);
	bignumFree(cs->sharedkey);
	bignumFree(cs->setremotekey);
	dblockFree(cs->insessionkey);
	dblockFree(cs->outsessionkey);

	//memFree also does the clearing of memory. This is just being overly cautious.
	memset(cs->blowfishin, 0, sizeof(BlowfishContext));
	memset(cs->blowfishout, 0, sizeof(BlowfishContext));
	memFree(cs->blowfishin);
	memFree(cs->blowfishout);

	memcpy(cs, &blankcryptstreamiip11, sizeof(CryptStreamIIP11));

	memFree(cs);//xifree(cs);
}

void csiip11Close(CryptStreamIIP11 *cs) {
	cs->cs.ParentPipe->status |= PSTATUS_CLOSED;
	cs->inShake = -1;
	cs->outShake = -1;
}

void csiip11SetKey(CryptStreamIIP11 *cs, DataBlock *db) {
	int i;
	Sha1Context *sha1;
	if(cs->insessionkey == NULL) {
		cs->insessionkey = dblockMake(CSIIP11_KEY_SET_LENGTH, CSIIP11_KEY_SET_LENGTH, "csiip1: insessionkey");
		memset(db->data, 0, CSIIP11_KEY_SET_LENGTH);
	}
	if(cs->outsessionkey == NULL) {
		cs->outsessionkey = dblockMake(CSIIP11_KEY_SET_LENGTH, CSIIP11_KEY_SET_LENGTH, "csiip1: outsessionkey");
		memset(db->data, 0, CSIIP11_KEY_SET_LENGTH);
	}

	sha1 = memAlloc(sizeof(Sha1Context), "Sha1Context", "CryptStreamIIP11KeyHash");
	SHA1Init(sha1);
	SHA1Update(sha1, db->data, db->size);
	db = dblockExpand(db, 20);
	SHA1Final(db->data, sha1);

	for(i = 0; i < db->size; i++) {
		cs->insessionkey->data[i % cs->insessionkey->size] ^= db->data[i];
		cs->outsessionkey->data[i % cs->outsessionkey->size] ^= db->data[i];
	}
	
	Blowfish_Init(cs->blowfishin, cs->insessionkey->data, CSIIP11_KEY_LENGTH);
	Blowfish_Init(cs->blowfishout, cs->outsessionkey->data, CSIIP11_KEY_LENGTH);

	memFree(sha1);
}


//xor rand into the next key position then rotates the session keys.
//will destroy the contents of rand
//individual key sizes are aasume to be the same size as rand
void csiip11RotateSessionKey(DataBlock *key, DataBlock *rand) {
	int i;
	for(i = 0; i < rand->size; i++) {
#if CSIIP11_KEY_SET_COUNT > 1
		key->data[rand->size + i] ^= rand->data[i];
#else
		key->data[i] ^= rand->data[i];
#endif
	}
#if CSIIP11_KEY_SET_COUNT > 1
	memmove(rand->data, key->data, rand->size);
	memmove(key->data, key->data + rand->size, key->size - rand->size);
	memmove(key->data + key->size - rand->size, rand->data, rand->size);
#endif
}

void csiip11ApplyCounter(uint8 *data, uint8 *counter) {
	int i;
	int j;
	for(i = 0; i < CSIIP11_BLOCK_LENGTH; i++) {
		data[i] ^= counter[i];
	}
	for(j = CSIIP11_BLOCK_LENGTH1; j >= 0; j--) {
		for(i = j; i >= 0; i--) {
			if(++counter[i] != 0) {
				break;
			}
		}
	}
}

void csiip11ApplyCBC(uint8 *data, uint8 *cbc) {
	bufferXor(data, cbc, CSIIP11_BLOCK_LENGTH);
}

void csiip11Decode(CryptStreamIIP11 *cs, DataBlock *db) {
	int i;
	uint32 l, r;
	for(i = 0; i < db->size; i += CSIIP11_BLOCK_LENGTH) {
		memcpy(cs->cbctemp, db->data + i, CSIIP11_BLOCK_LENGTH);
		l = *((uint32 *)(&db->data[i + 0]));
		r = *((uint32 *)(&db->data[i + 4]));
#ifdef BIG__ENDIAN
		l = ENDIANSWAP32(l);
		r = ENDIANSWAP32(r);
#endif
		Blowfish_Decrypt(cs->blowfishin, (unsigned long *)&l, (unsigned long *)&r);
#ifdef BIG__ENDIAN
		l = ENDIANSWAP32(l);
		r = ENDIANSWAP32(r);
#endif
		*((uint32 *)(&db->data[i + 0])) = l;
		*((uint32 *)(&db->data[i + 4])) = r;
		csiip11ApplyCounter(db->data + i, cs->counterin);
		csiip11ApplyCBC(db->data + i, cs->cbcin);
		memcpy(cs->cbcin, cs->cbctemp, CSIIP11_BLOCK_LENGTH);
	}
	//xteabDecode((uint32 *)db->data, db->size >> 2, (uint32 *)cs->insessionkey->data);
}

void csiip11Encode(CryptStreamIIP11 *cs, DataBlock *db) {
	int i;
	uint32 l, r;
	for(i = 0; i < db->size; i += CSIIP11_BLOCK_LENGTH) {
		csiip11ApplyCBC(db->data + i, cs->cbcout);
		csiip11ApplyCounter(db->data + i, cs->counterout);
		l = *((uint32 *)(&db->data[i + 0]));
		r = *((uint32 *)(&db->data[i + 4]));
#ifdef BIG__ENDIAN
		l = ENDIANSWAP32(l);
		r = ENDIANSWAP32(r);
#endif
		Blowfish_Encrypt(cs->blowfishout, (unsigned long *)&l, (unsigned long *)&r);
#ifdef BIG__ENDIAN
		l = ENDIANSWAP32(l);
		r = ENDIANSWAP32(r);
#endif
		*((uint32 *)(&db->data[i + 0])) = l;
		*((uint32 *)(&db->data[i + 4])) = r;
		memcpy(cs->cbcout, db->data + i, CSIIP11_BLOCK_LENGTH);
	}
}

void csiip11Decrypt(CryptStreamIIP11 *cs) {
	int insize;
	DataBlock *db;
	while(1) {
		if(cs->inNeeded == 0) {
			insize = CSIIP11_BLOCK_ROUND(CSIIP11_BLOCK_LENGTH);
		} else if(cs->inNeeded == -1) {
			insize = CSIIP11_BLOCK_ROUND(CSIIP11_KEY_LENGTH);
		} else {
			insize = CSIIP11_BLOCK_ROUND(cs->inNeeded);
		}
		if(cs->cs.ParentPipe->backPipe->inBuffer->size < insize) {
			return;
		}
		db = dblockCopyPart(cs->cs.ParentPipe->backPipe->inBuffer, 0, insize);
		saltAdd(db); //todo: this is probably not the best place to collect salt from.

		cs->cs.ParentPipe->backPipe->inBuffer = dblockDelete(cs->cs.ParentPipe->backPipe->inBuffer, 0, insize);
		csiip11Decode(cs, db);
		if(cs->inNeeded == 0) {
			if(crc16(db->data + 2, 6, 0) != bufferGetBEUInt(db->data + 0, 2)) {
				LOGERROR(stringJoinMany("csiip11Decrypt: CRC 16 failed. size(",
					intToString((int) bufferGetBEUInt(db->data + 2, 2)),
					"). Closing connection.",
					NULL));
				csiip11Close(cs);
				dblockFree(db);
				return;
			}
			cs->crc32 = (uint32) bufferGetBEUInt(db->data + 4, 4);
			cs->inNeeded = (int) bufferGetBEUInt(db->data + 2, 2);
			if(cs->inNeeded == 0) {
				cs->inNeeded = -1;
			}
			LOGDEBUGTRAFFIC(stringJoin("csiip11Decrypt: bytes waiting for ", intToHexString(cs->inNeeded)));
		} else if(cs->inNeeded == -1) {
			LOGDEBUG("csiip11Decrypt: Got session key rotate:");
			LOGDEBUGKEY(stringJoin("key:", bufferToHexString(db->data, db->size)));
			if(crc32(db->data, CSIIP11_KEY_LENGTH, 0) != cs->crc32) {
				LOGERROR("csiip11Decrypt: CRC 32 failed on key rotation. Closing connection.");
				csiip11Close(cs);
				dblockFree(db);
				return;
			}
			csiip11RotateSessionKey(cs->insessionkey, db);
			Blowfish_Init(cs->blowfishin, cs->insessionkey->data, CSIIP11_KEY_LENGTH);
			cs->inNeeded = 0;
		} else {
			if(crc32(db->data, cs->inNeeded, 0) != cs->crc32) {
				LOGERROR("csiip11Decrypt: CRC 32 failed. Closing connection.");
				csiip11Close(cs);
				dblockFree(db);
				return;
			}
			db->size = cs->inNeeded;
			cs->cs.ParentPipe->inBuffer = dblockAppendBlock(cs->cs.ParentPipe->inBuffer, db);
			LOGDEBUGTRAFFIC(stringJoin("csiip11Decrypt: got data, length ", intToHexString(cs->inNeeded)));
			cs->inNeeded = 0;
			//if we are still on the hand shake retrieving more data is bad.
			if(cs->inShake != 0 || cs->outShake != 0) {
				dblockFree(db);
				return;
			}
		}
		dblockFree(db);
	}
}

void csiip11EncryptWrite(CryptStreamIIP11 *cs, DataBlock *db) {

	saltBuffer(db->data + db->size, (CSIIP11_BLOCK_LENGTH - (db->size & CSIIP11_BLOCK_LENGTH1)) & CSIIP11_BLOCK_LENGTH1);

	db->size = (db->size + CSIIP11_BLOCK_LENGTH1) & (~CSIIP11_BLOCK_LENGTH1);

	csiip11Encode(cs, db);
	cs->cs.ParentPipe->backPipe->outBuffer = dblockAppendBlock(cs->cs.ParentPipe->backPipe->outBuffer, db);

	saltAdd(db); //todo: this is probably not the best place to collect salt from.

	dblockFree(db);

}

void csiip11EncryptSend(CryptStreamIIP11 *cs, DataBlock *dbd) {
	DataBlock *db;
	//DataBlock *dbd;
	DataBlock *dbr;
	int outsize;

	outsize = dbd->size;

	//if(outsize > 0xfff0) {
	//	outsize = 0xfff0;
	//}
	LOGDEBUGTRAFFIC(stringJoin("csiip11EncryptSend: sending bytes ", intToHexString(outsize)));

	db = dblockMake(CSIIP11_BLOCK_LENGTH, CSIIP11_BLOCK_LENGTH, "csiip11EncryptHeader");
	//dbd = dblockMake(CSIIP11_BLOCK_ROUND(outsize),0, "csiip11EncryptData");

	//dbd = dblockMove(dbd, cs->cs.ParentPipe->outBuffer, outsize);
	dbd = dblockExpand(dbd, CSIIP11_BLOCK_ROUND(outsize));
	dbd->size = CSIIP11_BLOCK_ROUND(outsize);

	bufferSetBEUInt(db->data + 2, outsize, 2);
	bufferSetBEUInt(db->data + 4, crc32(dbd->data, outsize, 0), 4);
	bufferSetBEUInt(db->data + 0, crc16(db->data + 2, 6, 0), 2);

	csiip11EncryptWrite(cs, db);
	csiip11EncryptWrite(cs, dbd);

	if(++cs->outCount >= CSIIP11_WRITES_TILL_ROTATE) {
		cs->outCount = 0;

		db = dblockMake(CSIIP11_BLOCK_LENGTH, CSIIP11_BLOCK_LENGTH, "csiip11EncryptRotate");
		//dbd = dblockMake(CSIIP11_BLOCK_ROUND(KEY_LENGTH),0, "csiip11EncryptKey");
		dbr = randomDBlock(CSIIP11_KEY_LENGTH);
		dbd = dblockCopy(dbr);

		bufferSetBEUInt(db->data + 2, 0, 2);
		bufferSetBEUInt(db->data + 4, crc32(dbd->data, CSIIP11_KEY_LENGTH, 0), 4);
		bufferSetBEUInt(db->data + 0, crc16(db->data + 2, 6, 0), 2);

		csiip11EncryptWrite(cs, db);
		csiip11EncryptWrite(cs, dbd);

		LOGDEBUGTRAFFIC("csiip11Encrypt: Sent session key rotate:");
		LOGDEBUGKEY(stringJoin("key:", bufferToHexString(dbr->data, dbr->size)));
		csiip11RotateSessionKey(cs->outsessionkey, dbr);

		Blowfish_Init(cs->blowfishout, cs->outsessionkey->data, CSIIP11_KEY_LENGTH);

		dblockFree(dbr);
	}
}

void csiip11Encrypt(CryptStreamIIP11 *cs) {
	DataBlock *db;
	int outsize;
	if(cs->cs.ParentPipe->outBuffer->size == 0) {
		return;
	}

	outsize = cs->cs.ParentPipe->outBuffer->size;

	if(outsize > 0xfff0) {
		outsize = 0xfff0;
	}
	LOGDEBUGTRAFFIC(stringJoin("csiip11Encrypt: sending bytes ", intToHexString(outsize)));

	db = dblockMake(CSIIP11_BLOCK_ROUND(outsize),0, "csiip11EncryptData");

	db = dblockMove(db, cs->cs.ParentPipe->outBuffer, outsize);
	csiip11EncryptSend(cs, db);
}


void csiip11InHandshake(CryptStreamIIP11 *cs) {
	int i;
	DataBlock *db;
	switch(cs->inShake) {
	case 1:
		if(cs->outShake <= 1) {
			return;
		}
		cs->inShake = 2;
	case 2: //wait for outer key length
		if(cs->cs.ParentPipe->backPipe->inBuffer->size < 4) {
			return;
		}
		i = (int) bufferGetBEInt(cs->cs.ParentPipe->backPipe->inBuffer->data, 4);
		cs->cs.ParentPipe->backPipe->inBuffer = dblockDelete(cs->cs.ParentPipe->backPipe->inBuffer, 0, 4);

		LOGDEBUGKEY(stringJoin("csiip11InHandshake: outer pubkey length got:", intToString(i)));

		if(i > cs->outerkeylength) {
			cs->outerkeylength = i;
		}

		LOGDEBUGKEY(stringJoin("csiip11InHandshake: outer pubkey using:", intToString(cs->outerkeylength)));
		
		cs->inShake = 3;
	case 3: //wait for remote outer key and sending of local pub key
		if(cs->cs.ParentPipe->backPipe->inBuffer->size < cs->outerkeylength / 8) {
			return;
		}
		if(cs->outShake <= 3) {
			return;
		}
		cs->inShake = 4;
	case 4: //compute shared outer key
		if(nextdhdelay > 0) {
			return;
		}
		//get remote key
		db = dblockCopyPart(cs->cs.ParentPipe->backPipe->inBuffer, 0, cs->outerkeylength / 8);
		bufferReverse(db->data, db->size);
		cs->remotekey = bignumFromBuffer(db->data, cs->outerkeylength / 8);
		LOGDEBUG(stringJoinMany("csiip11InHandshake: ", ptrToString(cs), " got remote public key", NULL));
		//LOGDEBUGKEY(stringJoin("remotekey:", bufferToHexString((uint8 *)cs->remotekey->data, cs->outerkeylength / 8)));
		LOGDEBUGKEY(stringJoin("remotekey:", bignumToHex(cs->remotekey)));
		cs->cs.ParentPipe->backPipe->inBuffer = dblockDelete(cs->cs.ParentPipe->backPipe->inBuffer, 0, cs->outerkeylength / 8);
		dblockFree(db);
		//validate public key
		if(cs->setremotekey != NULL) {
			if(bignumCompare(cs->remotekey, cs->setremotekey) != 0) {
				LOGERROR("csiip11InHandshake: remotekey is wrong!");
				//LOGDEBUGKEY(stringJoin("remotekey:", bufferToHexString((uint8 *)cs->setremotekey->data, cs->outerkeylength / 8)));
				LOGDEBUGKEY(stringJoin("expectedremotekey:", bignumToHex(cs->setremotekey)));
				cs->inShake = -1;
				cs->cs.ParentPipe->backPipe->status |= PSTATUS_CLOSED;
				cs->cs.ParentPipe->status |= PSTATUS_CLOSED;
				return;
			} else {
				LOGDEBUG(stringJoinMany("csiip11InHandshake: ", ptrToString(cs), " public key mathced", NULL));
			}
		}
		//compute shared key
		cs->sharedkey = dhGenerateSharedKey(cs->outerkeylength, cs->privkey, cs->remotekey);
		randomAddEntropyClock(2);
		nextdhdelay = NEXTDHDELAY;
		if(cs->sharedkey == NULL) {
			csiip11Close(cs);
			return;
		}
		//create encryption keys for out key
		db = dblockMake(cs->outerkeylength / 8, cs->outerkeylength / 8, "csiip11: outerkey");
		bignumToBuf(db->data, cs->sharedkey, db->size);
		LOGDEBUG(stringJoinMany("csiip11InHandshake: ", ptrToString(cs), " made shared key", NULL));
		//LOGDEBUGKEY(stringJoin("sharedkey:", bufferToHexString((uint8 *)db->data, db->size)));
		LOGDEBUGKEY(stringJoin("sharedkey:", bignumToHex(cs->sharedkey)));
		bufferReverse(db->data, db->size);
		csiip11SetKey(cs, db);
		dblockFree(db);


		bignumFree(cs->privkey);
		bignumFree(cs->localkey);
		bignumFree(cs->remotekey);
		bignumFree(cs->sharedkey);
		bignumFree(cs->setremotekey);
		cs->privkey = NULL;
		cs->localkey = NULL;
		cs->remotekey = NULL;
		cs->sharedkey = NULL;
		cs->setremotekey = NULL;


		cs->inShake = 5;
	case 5: //wait for inner key length
		csiip11Decrypt(cs);
		if(cs->cs.ParentPipe->inBuffer->size < 4) {
			return;
		}
		i = (int) bufferGetBEInt(cs->cs.ParentPipe->inBuffer->data, 4);
		if(i != 0) {
			if(cs->innerkeylength < i) {
				cs->innerkeylength = i;
			}
		}
		cs->cs.ParentPipe->inBuffer = dblockDelete(cs->cs.ParentPipe->inBuffer, 0, 4);

		cs->inShake = 6;
	case 6: //wait for remote inner key
		if(nextdhdelay > 0) {
			return;
		}
		if(cs->outShake <= 7) {
			return;
		}
		csiip11Decrypt(cs);
		if(cs->cs.ParentPipe->inBuffer->size < cs->innerkeylength / 8) {
			return;
		}
		cs->inShake = 7;
	case 7: //compute shared inner key
		if(nextdhdelay > 0) {
			return;
		}
		//get remote key
		db = dblockCopyPart(cs->cs.ParentPipe->inBuffer, 0, cs->innerkeylength / 8);
		bufferReverse(db->data, db->size);
		cs->remotekey = bignumFromBuffer(db->data, cs->innerkeylength / 8);
		LOGDEBUG(stringJoinMany("csiip11InHandshake: ", ptrToString(cs), " got remote public key", NULL));
		//LOGDEBUGKEY(stringJoin("remotekey:", bufferToHexString((uint8 *)cs->remotekey->data, cs->innerkeylength / 8)));
		LOGDEBUGKEY(stringJoin("remotekey:", bignumToHex(cs->remotekey)));
		cs->cs.ParentPipe->inBuffer = dblockDelete(cs->cs.ParentPipe->inBuffer, 0, cs->innerkeylength / 8);
		dblockFree(db);
		//compute shared key
		cs->sharedkey = dhGenerateSharedKey(cs->innerkeylength, cs->privkey, cs->remotekey);
		randomAddEntropyClock(2);
		nextdhdelay = NEXTDHDELAY;
		if(cs->sharedkey == NULL) {
			csiip11Close(cs);
			return;
		}
		//create encryption keys for out key
		db = dblockMake(cs->innerkeylength / 8, cs->innerkeylength / 8, "csiip11: innerkey");
		bignumToBuf(db->data, cs->sharedkey, db->size);
		LOGDEBUG(stringJoinMany("csiip11InHandshake: ", ptrToString(cs), " made shared key", NULL));
		//LOGDEBUGKEY(stringJoin("sharedkey:", bufferToHexString((uint8 *)db->data, db->size)));
		LOGDEBUGKEY(stringJoin("sharedkey:", bignumToHex(cs->sharedkey)));
		bufferReverse(db->data, db->size);
		csiip11SetKey(cs, db);
		dblockFree(db);


		bignumFree(cs->privkey);
		bignumFree(cs->localkey);
		bignumFree(cs->remotekey);
		bignumFree(cs->sharedkey);
		bignumFree(cs->setremotekey);
		cs->privkey = NULL;
		cs->localkey = NULL;
		cs->remotekey = NULL;
		cs->sharedkey = NULL;
		cs->setremotekey = NULL;

	//done
		cs->inShake = 0;
		cs->outShake = 0;

		cs->inNeeded = 0;
		cs->cs.ParentPipe->status |= PSTATUS_READY;


	//default:
	}
}

void csiip11OutHandshake(CryptStreamIIP11 *cs) {
	DataBlock *db;
	int i;
	switch(cs->outShake) {
	case 1: //send outer key length
		if(cs->cs.noderef != NULL) {
			if(!isStringBlank(cs->cs.noderef->PrivateKey)) {
				cs->privkey = bignumFromHex(cs->cs.noderef->PrivateKey);
			}
			if(!isStringBlank(cs->cs.noderef->PublicKey)) {
				cs->setremotekey = bignumFromHex(cs->cs.noderef->PublicKey);
			}
		}
		
		if(cs->privkey != NULL) {
			i = dhGetPrimeLength(bignumNumBits(cs->privkey));
			if(i != 0) {
				cs->outerkeylength = i;
			}
			LOGDEBUGKEY(stringJoin("csiip11OutHandshake: premade outer privkey length:", intToString(bignumNumBits(cs->privkey))));
			LOGDEBUGKEY(stringJoin("implied pubkey length:", intToString(cs->outerkeylength)));
		}

		db = dblockMake(4, 4, "csiip1OutHandshakeHeader1");
		bufferSetBEInt(db->data, cs->outerkeylength, 4);
		cs->cs.ParentPipe->backPipe->outBuffer = dblockAppendBlock(cs->cs.ParentPipe->backPipe->outBuffer, db);
		dblockFree(db);

		LOGDEBUGKEY(stringJoin("csiip11OutHandshake: outer pubkey length requested:", intToString(cs->outerkeylength)));

		cs->outShake = 2;
	case 2: //wait for outer keylength
		if(cs->inShake <= 2) {
			return;
		}

		//abort if we have a private key but the agreed length does not match
		if(cs->privkey != NULL) {
			i = dhGetPrimeLength(bignumNumBits(cs->privkey));
			if(i != 0) {
				if(cs->outerkeylength != i) {
					LOGERROR("csiip11OutHandshake: Agreed length does not match premade key length.");
					LOGERROR(stringJoinMany("- agreed length:", intToString(cs->outerkeylength), ", local key length:", intToString(i), NULL));
					csiip11Close(cs);
				}
			}
		}

		cs->outShake = 3;
	case 3: //generate and send outerkey
		if(nextdhdelay > 0) {
			return;
		}

		if(cs->privkey == NULL) {
			cs->privkey = dhGeneratePrivKey(dhGetExponentLength(cs->outerkeylength));
		} else {
			LOGDEBUG("csiip11OutHandshake: using premade private key");
		}

		cs->localkey = dhGeneratePubKey(cs->outerkeylength, cs->privkey);
		randomAddEntropyClock(2);
		nextdhdelay = NEXTDHDELAY;

		if(cs->localkey == NULL) {
			csiip11Close(cs);
			return;
		}

		LOGDEBUG(stringJoinMany("csiip11OutHandshake: ", ptrToString(cs), " made private and local public key", NULL));
		//LOGDEBUGKEY(stringJoin("privkey:", bufferToHexString((uint8 *)cs->privkey->data, bignumNumBits(cs->privkey) + 7 / 8)));
		//LOGDEBUGKEY(stringJoin("pubkey:", bufferToHexString((uint8 *)cs->localkey->data, bignumNumBits(cs->localkey) + 7 / 8)));
		LOGDEBUGKEY(stringJoin("privkey:", bignumToHex(cs->privkey)));
		LOGDEBUGKEY(stringJoin("pubkey:", bignumToHex(cs->localkey)));
		db = dblockMake(cs->outerkeylength / 8, cs->outerkeylength / 8, "pubkey");
		bignumToBuf(db->data, cs->localkey, cs->outerkeylength / 8);
		bufferReverse(db->data, db->size);
		cs->cs.ParentPipe->backPipe->outBuffer = dblockAppendBlock(cs->cs.ParentPipe->backPipe->outBuffer, db);
		dblockFree(db);

		cs->outShake = 4;
	case 4: //wait for shared outerkey
		if(cs->inShake <= 4) {
			return;
		}

		cs->outShake = 5;
	case 5: //send inner key length
		db = dblockMake(4, 4, "csiip1OutHandshakeHeader2");
		bufferSetBEInt(db->data, cs->innerkeylength, 4);
		csiip11EncryptSend(cs, db);

		cs->outShake = 6;
	case 6: //wait for inner key length
		if(cs->inShake <= 5) {
			return;
		}
		cs->outShake = 7;
	case 7: //send inner key
		if(nextdhdelay > 0) {
			return;
		}

		cs->privkey = dhGeneratePrivKey(dhGetExponentLength(cs->innerkeylength));

		cs->localkey = dhGeneratePubKey(cs->innerkeylength, cs->privkey);
		randomAddEntropyClock(2);
		nextdhdelay = NEXTDHDELAY;

		if(cs->localkey == NULL) {
			csiip11Close(cs);
			return;
		}

		LOGDEBUG(stringJoinMany("csiip11OutHandshake: ", ptrToString(cs), " made private and local public key", NULL));
		//LOGDEBUGKEY(stringJoin("privkey:", bufferToHexString((uint8 *)cs->privkey->data, cs->innerkeylength / 8)));
		//LOGDEBUGKEY(stringJoin("pubkey:", bufferToHexString((uint8 *)cs->localkey->data, cs->innerkeylength / 8)));
		LOGDEBUGKEY(stringJoin("privkey:", bignumToHex(cs->privkey)));
		LOGDEBUGKEY(stringJoin("pubkey:", bignumToHex(cs->localkey)));
		db = dblockMake(cs->innerkeylength / 8, cs->innerkeylength / 8, "pubkey");
		bignumToBuf(db->data, cs->localkey, cs->innerkeylength / 8);
		bufferReverse(db->data, db->size);
		csiip11EncryptSend(cs, db);

		cs->outShake = 8;
	case 8: //wait for shared inner key
		//do nothing
		;
	}
	
}

void csiip11Read(CryptStreamIIP11 *cs) {
	if(cs->inShake != 0) {
		int i = cs->inShake;
		csiip11InHandshake(cs);
		if(i != cs->inShake) {
			LOGDEBUG(stringJoinMany("csiip11InHandshake: ", ptrToString(cs), " handshake stage:", intToString(cs->inShake), NULL));
		}
	} else {
		csiip11Decrypt(cs);
	}
}

void csiip11Write(CryptStreamIIP11 *cs) {
	if(cs->outShake != 0) {
		int i = cs->outShake;
		csiip11OutHandshake(cs);
		if(i != cs->outShake) {
			LOGDEBUG(stringJoinMany("csiip11OutHandshake: ", ptrToString(cs), " handshake stage:", intToString(cs->outShake), NULL));
		}
	} else {
		csiip11Encrypt(cs);
	}
}

