#include <stdio.h>
#include <string.h>

#undef OPENSSL_NO_DES
#include "des.h"
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/bn.h>
#include "openssl-0.9.8/include/openssl/engine.h"
#include <openssl/ossl_typ.h>
#define CRYPTO_PARALLEL_LIB_NAME "des-ecb"

/*****************************************************************************
 *** Function declarations and global variable definitions                 ***
 *****************************************************************************/

/*****************************************************************************
 * Constants used when creating the ENGINE
 **/
static const char *engine_desecb_id = "des-ecb";
static const char *engine_desecb_name = "des-ecb engine support";

/*****************************************************************************
 * Functions to handle the engine
 **/
static int desecb_destroy(ENGINE *e);
static int desecb_init(ENGINE *e);
static int desecb_finish(ENGINE *e);
#if 0
static int desecb_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f)()); 
#endif

/*****************************************************************************
 * Symetric cipher and digest function registrars
 **/
static int desecb_ciphers(ENGINE *e, const EVP_CIPHER **cipher, const int **nids, int nid);

static int desecb_cipher_nids[] = { NID_des_ecb, 0 };
	
/*****************************************************************************
 * DES functions
 **/
static int init_key(EVP_CIPHER_CTX *ctx, const unsigned char *key, const unsigned char *iv, int enc);
static int des_ecb_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out, const unsigned char *in, unsigned int inl);
static int des_ctrl(EVP_CIPHER_CTX *c, int type, int arg, void *ptr);

/*****************************************************************************
 * Our DES ciphers
 **/
static const EVP_CIPHER des_ecb =
{
	NID_des_ecb,
	8, 					/* block_size */
	8,	 				/* key_len Default value for variable length ciphers */
	8,					/* iv_len */
	EVP_CIPH_RAND_KEY | EVP_CIPH_ECB_MODE, 	/* Various flags */
	init_key,				/* init key */
	des_ecb_cipher,				/* encrypt/decrypt data */
	NULL,					/* cleanup ctx */
	sizeof(DES_key_schedule),		/* how big ctx->cipher_data needs to be */
	EVP_CIPHER_set_asn1_iv,			/* Populate a ASN1_TYPE with parameters */
	EVP_CIPHER_get_asn1_iv,			/* Get parameters from a ASN1_TYPE */
	des_ctrl, 				/* Miscellaneous operations */
	NULL					/* Application data */
};
	
/*****************************************************************************
 * Functions to handle the engine
 **/

static int bind_desecb(ENGINE *e)
{
	
	if(!ENGINE_set_id(e, engine_desecb_id)
		|| !ENGINE_set_name(e, engine_desecb_name)
		|| !ENGINE_set_ciphers(e, desecb_ciphers)
		|| !ENGINE_set_destroy_function(e, desecb_destroy)
		|| !ENGINE_set_init_function(e, desecb_init)
		|| !ENGINE_set_finish_function(e, desecb_finish)
		/* || !ENGINE_set_ctrl_function(e, rsaref_ctrl) */
		/* || !ENGINE_set_cmd_defns(e, rsaref_cmd_defns) */
		|| !EVP_add_cipher(&des_ecb))
		return 0;

	return 1;
}

#ifndef OPENSSL_NO_DYNAMIC_ENGINE
static int bind_helper(ENGINE *e, const char *id)
{
	if(id && (strcmp(id, engine_desecb_id) != 0))
		return 0;
	if(!bind_desecb(e))
		return 0;
	return 1;
}       
IMPLEMENT_DYNAMIC_CHECK_FN()
IMPLEMENT_DYNAMIC_BIND_FN(bind_helper)
#else
static ENGINE *engine_desecb(void)
{
	ENGINE *ret = ENGINE_new();
	if(!ret)
		return NULL;
	if(!bind_desecb(ret))
	{
		ENGINE_free(ret);
		return NULL;
	}
	return ret;
}

void ENGINE_load_desecb(void)
{
	/* Copied from eng_[openssl|dyn].c */
	ENGINE *toadd = engine_desecb();
	if(!toadd) return;
	ENGINE_add(toadd);
	ENGINE_free(toadd);
	//ERR_clear_error();
}
#endif /* OPENSSL_NO_DYNAMIC_ENGINE */

/* Initiator which is only present to make sure this engine looks available */
static int desecb_init(ENGINE *e)
{
	return 1;
}

/* Finisher which is only present to make sure this engine looks available */
static int desecb_finish(ENGINE *e)
{
	return 1;
}

/* Destructor (complements the "ENGINE_ncipher()" constructor) */
static int desecb_destroy(ENGINE *e)
{
	//ERR_unload_RSAREF_strings();
	return 1;
}

/*****************************************************************************
 * Symetric cipher and digest function registrars
 **/
static int desecb_ciphers(ENGINE *e, const EVP_CIPHER **cipher, const int **nids, int nid)
{
	int ok = 1;
	if(!cipher)
		{
		/* We are returning a list of supported nids */
		*nids = desecb_cipher_nids;
		return (sizeof(desecb_cipher_nids)-1)/sizeof(desecb_cipher_nids[0]);
		}
	/* We are being asked for a specific cipher */
	switch (nid)
	{
		case NID_des_ecb:
			*cipher = &des_ecb; 
			break;
		default:
			ok = 0;
			*cipher = NULL;
			break;
	}
	return ok;
}

/*****************************************************************************
 * DES functions
 **/
 static int init_key(EVP_CIPHER_CTX *ctx, const unsigned char *key, const unsigned char *iv, int enc)
{
	DES_cblock *deskey = (DES_cblock *)key;
	#ifdef EVP_CHECK_DES_KEY
	if(DES_set_key_checked(deskey,ctx->cipher_data) != 0)
		return 0;
	#else
	DES_set_key_unchecked(deskey,ctx->cipher_data);
	#endif
	return 1;
}

static int des_ctrl(EVP_CIPHER_CTX *c, int type, int arg, void *ptr)
{
	switch(type)
	{
		case EVP_CTRL_RAND_KEY:
			if (RAND_bytes(ptr, 8) <= 0)
			return 0;
			DES_set_odd_parity((DES_cblock *)ptr);
		return 1;

	default:
		return -1;
	}
}

static int des_ecb_cipher(EVP_CIPHER_CTX *ctx, unsigned char *out, const unsigned char *in, unsigned int inl)
{
	unsigned int i, bl; 
	bl = ctx->cipher->block_size;
	if(inl < bl) return 1;
	inl -= bl; 
	for(i=0; i <= inl; i+=bl) DES_ecb_encrypt((DES_cblock *)(in + i), (DES_cblock *)(out + i), ctx->cipher_data, ctx->encrypt);

	return 1; 
}
