/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005
 *					All rights reserved
 *
 *  This file is part of GPAC / Authoring Tools sub-project
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */

/* The GPAC crypto lib is a simplified version of libmcrypt. */

#include <gpac/intern/m4_crypt_dev.h>


static Bool m4crypt_assign_algo(M4CRYPT td, const char *algorithm)
{
	if (!stricmp(algorithm, "AES-128") || !stricmp(algorithm, "Rijndael-128")) { m4c_register_rijndael_128(td); return 1; }
#ifndef M4_READ_ONLY
	else if (!stricmp(algorithm, "AES-192") || !stricmp(algorithm, "Rijndael-192")) { m4c_register_rijndael_192(td); return 1; }
	else if (!stricmp(algorithm, "AES-256") || !stricmp(algorithm, "Rijndael-256")) { m4c_register_rijndael_256(td); return 1; }
	else if (!stricmp(algorithm, "DES")) { m4c_register_des(td); return 1; }
	else if (!stricmp(algorithm, "3DES")) { m4c_register_3des(td); return 1; }
#endif
	return 0;
}
static Bool m4crypt_assign_mode(M4CRYPT td, const char *mode)
{
	if (!stricmp(mode, "CTR")) { m4c_register_ctr(td); return 1; }
#ifndef M4_READ_ONLY
	else if (!stricmp(mode, "CBC")) { m4c_register_cbc(td); return 1; }
	else if (!stricmp(mode, "CFB")) { m4c_register_cfb(td); return 1; }
	else if (!stricmp(mode, "ECB")) { m4c_register_ecb(td); return 1; }
	else if (!stricmp(mode, "nCFB")) { m4c_register_ncfb(td); return 1; }
	else if (!stricmp(mode, "nOFB")) { m4c_register_nofb(td); return 1; }
	else if (!stricmp(mode, "OFB")) { m4c_register_ofb(td); return 1; }
	else if (!stricmp(mode, "STREAM")) { m4c_register_stream(td); return 1; }
#endif
	return 0;
}

static M4CRYPT m4crypt_open_intern(const char *algorithm, const char *mode, Bool is_check)
{
	M4CRYPT td;
	if ((!algorithm || !mode) && !is_check) return NULL;
	
	SAFEALLOC(td, sizeof(M4CRYPT_STREAM));
	if (td==NULL) return NULL;

	
	if (algorithm && !m4crypt_assign_algo(td, algorithm)) {
		free(td);
		return NULL;
	}
	if (mode && !m4crypt_assign_mode(td, mode)) {
		free(td);
		return NULL;
	}
	if (is_check) return td;

	if (td->is_block_algo != td->is_block_algo_mode) {
		free(td);
		return NULL;
	}
	if (!td->_mcrypt || !td->_mdecrypt || !td->_mcrypt_set_state
		|| !td->a_decrypt || !td->a_encrypt || !td->a_set_key)
	{
		free(td);
		return NULL;
	}
	return td;
}

static void internal_end_mcrypt(M4CRYPT td)
{
	if (!td || !td->keyword_given) return;
	free(td->keyword_given);
	td->keyword_given = NULL;

	if (td->akey) {
		free(td->akey);
		td->akey = NULL;
	}
	if (td->abuf) {
		td->_end_mcrypt(td->abuf);
		free(td->abuf);
		td->abuf = NULL;
	}
}


M4CRYPT m4crypt_open(const char *algorithm, const char *mode)
{
	return m4crypt_open_intern(algorithm, mode, 0);
}

void m4crypt_close(M4CRYPT td)
{
	internal_end_mcrypt(td);
	free(td);
}

M4Err m4crypt_set_key(M4CRYPT td, void *key, u32 keysize, const void *IV)
{
	int (*__mcrypt_set_key_stream) (void *, const void *, int, const void *, int);
	int (*__mcrypt_set_key_block) (void *, const void *, int);

	if (td->is_block_algo== 0) {
		/* stream */
		__mcrypt_set_key_stream = td->a_set_key;
		if (__mcrypt_set_key_stream == NULL) return M4BadParam;
		return __mcrypt_set_key_stream(td->akey, key, keysize, IV, (IV!=NULL) ? m4crypt_get_iv_size(td) : 0);
	} else {
		__mcrypt_set_key_block = td->a_set_key;
		if (__mcrypt_set_key_block == NULL) return M4BadParam;
		return __mcrypt_set_key_block(td->akey, key, keysize);
	}
}

M4Err m4crypt_set_state(M4CRYPT td, const void *iv, int size) 
{
	if (!td) return M4BadParam;
	return td->_mcrypt_set_state(td->abuf, (void *) iv, size);
}

M4Err m4crypt_get_state(M4CRYPT td, void *iv, int *size)
{
	if (!td) return M4BadParam;
	return td->_mcrypt_get_state(td->abuf, iv, size);
}

u32 m4crypt_get_block_size(M4CRYPT td) { return td ? td->algo_block_size : 0; }

u32 m4crypt_get_iv_size(M4CRYPT td) 
{
	if (!td) return 0;
	if (td->is_block_algo_mode) return td->algo_block_size;
	return td->algo_IV_size;
}

u32 m4crypt_get_key_size(M4CRYPT td) { return td ? td->key_size : 0; }

u32 m4crypt_get_supported_key_sizes(M4CRYPT td, u32 *key_sizes)
{
	u32 i;
	if (!td || !td->num_key_sizes) return 0;
	for (i=0; i<td->num_key_sizes; i++) key_sizes[i] = td->key_sizes[i];
	return td->num_key_sizes;
}

Bool m4crypt_is_block_algorithm(M4CRYPT td) { return td ? td->is_block_algo : 0; }
const char *m4crypt_get_algorithm_name(M4CRYPT td) { return td ? td->algo_name : NULL; }
const char *m4crypt_get_mode_name(M4CRYPT td) { return td ? td->mode_name : NULL; }
Bool m4crypt_is_block_mode(M4CRYPT td) { return td ? td->is_block_mode : 0; }
Bool m4crypt_mode_has_iv(M4CRYPT td) { return td ? td->has_IV : 0; }
Bool m4crypt_is_block_algorithm_mode(M4CRYPT td) { return td ? td->is_block_algo_mode : 0; }
u32 m4crypt_get_algorithm_version(M4CRYPT td) { return td ? td->algo_version : 0; }
u32 m4crypt_get_mode_version(M4CRYPT td) { return td ? td->mode_version : 0; }



M4Err m4crypt_init(const M4CRYPT td, void *key, u32 lenofkey, const void *IV)
{
	M4Err e;
	u32 sizes[MAX_KEY_SIZES];
	u32 i, num_of_sizes, ok = 0;
	u32 key_size = m4crypt_get_key_size(td);

	if ((lenofkey > key_size) || (lenofkey==0)) return M4BadParam;
	num_of_sizes = m4crypt_get_supported_key_sizes(td, sizes);
	if (num_of_sizes) {
		for (i=0; i < num_of_sizes; i++) {
			if (lenofkey == sizes[i]) {
				ok = 1;
				break;
			}
		}
	} else if (lenofkey <= m4crypt_get_key_size(td)) {
		ok = 1;
	}

	if (ok == 0) { /* not supported key size */
		key_size = m4crypt_get_key_size(td);
		if (sizes != NULL) {
			for (i = 0; i < num_of_sizes; i++) {
				if (lenofkey <= sizes[i]) {
					key_size = sizes[i];
					break;
				}
			}
		} else { /* well every key size is supported! */
			key_size = lenofkey;
		}
	} else {
		key_size = lenofkey;
	}

	td->keyword_given = malloc(sizeof(char)*m4crypt_get_key_size(td));
	if (td->keyword_given==NULL) return M4OutOfMem; 
	
	memmove(td->keyword_given, key, lenofkey);

	td->akey = malloc(sizeof(char)*td->algo_size);
	if (td->akey==NULL) {
		free(td->keyword_given);
		return M4OutOfMem;
	}
	if (td->mode_size > 0) {
		td->abuf = malloc(sizeof(char)*td->mode_size);
		if (td->abuf==NULL) {
			free(td->keyword_given);
			free(td->akey);
			return M4OutOfMem;
		}
	}
	e = td->_init_mcrypt(td->abuf, (void *) key, key_size, (void *) IV, m4crypt_get_block_size(td));
	if (e!=M4OK) {
		free(td->keyword_given);
		free(td->akey);
		free(td->abuf);
		return e;
	}

	e = m4crypt_set_key(td, (void *) td->keyword_given, key_size, IV);

	if (e!=M4OK) internal_end_mcrypt(td);
	return e;
}

void m4crypt_deinit(M4CRYPT td)
{
	internal_end_mcrypt(td);
}

M4Err m4crypt_encrypt(M4CRYPT td, void *plaintext, int len)
{
	if (!td) return M4BadParam;
	return td->_mcrypt(td->abuf, plaintext, len, m4crypt_get_block_size(td), td->akey, td->a_encrypt, td->a_decrypt);
}

M4Err m4crypt_decrypt(M4CRYPT td, void *ciphertext, int len)
{
	if (!td) return M4BadParam;
	return td->_mdecrypt(td->abuf, ciphertext, len, m4crypt_get_block_size(td), td->akey, td->a_encrypt, td->a_decrypt);
}


u32 m4crypt_str_get_algorithm_version(const char *algorithm)
{
	u32 ret;
	M4CRYPT td = m4crypt_open_intern(algorithm, NULL, 1);
	ret = td ? td->algo_version : 0;
	m4crypt_close(td);
	return ret;
}
u32 m4crypt_str_get_mode_version(const char *mode)
{
	u32 ret;
	M4CRYPT td = m4crypt_open_intern(NULL, mode, 1);
	ret = td ? td->mode_version : 0;
	m4crypt_close(td);
	return ret;
}
Bool m4crypt_str_is_block_algorithm(const char *algorithm)
{
	Bool ret;
	M4CRYPT td = m4crypt_open_intern(algorithm, NULL, 1);
	ret = td ? td->is_block_algo : 0;
	m4crypt_close(td);
	return ret;
}
Bool m4crypt_str_is_block_algorithm_mode(const char *algorithm)
{
	Bool ret;
	M4CRYPT td = m4crypt_open_intern(algorithm, NULL, 1);
	ret = td ? td->is_block_algo_mode : 0;
	m4crypt_close(td);
	return ret;
}
Bool m4crypt_str_is_block_mode(const char *mode)
{
	Bool ret;
	M4CRYPT td = m4crypt_open_intern(NULL, mode, 1);
	ret = td ? td->is_block_mode : 0;
	m4crypt_close(td);
	return ret;
}
u32 m4crypt_str_module_get_algo_block_size(const char *algorithm)
{
	u32 ret;
	M4CRYPT td = m4crypt_open_intern(algorithm, NULL, 1);
	ret = td ? td->algo_block_size : 0;
	m4crypt_close(td);
	return ret;
}
u32 m4crypt_str_module_get_algo_key_size(const char *algorithm)
{
	u32 ret;
	M4CRYPT td = m4crypt_open_intern(algorithm, NULL, 1);
	ret = td ? td->key_size : 0;
	m4crypt_close(td);
	return ret;
}
u32 m4crypt_str_get_algo_supported_key_sizes(const char *algorithm, int *keys)
{
	u32 ret;
	M4CRYPT td = m4crypt_open_intern(algorithm, NULL, 1);
	ret = m4crypt_get_supported_key_sizes(td, keys);
	m4crypt_close(td);
	return ret;
}

