/*
* Copyright (C) 2009 Mamadou Diop.
*
* Contact: Mamadou Diop <diopmamadou(at)doubango.org>
*	
* This file is part of Open Source Doubango Framework.
*
* DOUBANGO 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 3 of the License, or
* (at your option) any later version.
*	
* DOUBANGO 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 DOUBANGO.
*
*/

/**@file tcomp_udvm.c
 * @brief  SigComp UDVM machine.
 *
 * @author Mamadou Diop <diopmamadou(at)yahoo.fr>
 *
 * @date Created: Sat Nov 8 16:54:58 2009 mdiop
 */
#include "tcomp_udvm.h"
#include "tsk_debug.h"

#include <string.h>

#define TCOMP_UDVM_HEADER_RESERVED_SIZE 22


/**Executes the bytecode.
*/
static tsk_bool_t tcomp_udvm_runByteCode(tcomp_udvm_t *udvm)
{
	uint16_t operand_1, operand_2, operand_3, operand_4, operand_5, operand_6, operand_7;
	tsk_bool_t excution_failed = tsk_false, end_message = tsk_false;
	if(!udvm->isOK) {
		TSK_DEBUG_ERROR("Cannot run/execute bytecode on invalid.");
		return tsk_false;
	}

	// LOOP - EXCUTE all bytecode
	while( !excution_failed && !end_message )
	{
		uint8_t udvm_instruction = * (TCOMP_UDVM_GET_BUFFER_AT(udvm->executionPointer));
		udvm->last_memory_address_of_instruction = udvm->executionPointer;
		udvm->executionPointer++; /* Skip the 1-byte [INSTRUCTION]. */

		switch(udvm_instruction)
		{
		case TCOMP_UDVM_INST__DECOMPRESSION_FAILURE:
			{
				TCOMP_UDVM_EXEC_INST__DECOMPRESSION_FAILURE(udvm);
				excution_failed = tsk_true;
				break;
			}

		case TCOMP_UDVM_INST__AND:
			{
				operand_1 = tcomp_udvm_opget_reference_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__AND(udvm, operand_1, operand_2);
				break;
			}

		case TCOMP_UDVM_INST__OR:
			{
				operand_1 = tcomp_udvm_opget_reference_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__OR(udvm, operand_1, operand_2);
				break;
			}

		case TCOMP_UDVM_INST__NOT:
			{
				operand_1 = tcomp_udvm_opget_reference_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__NOT(udvm, operand_1);
				break;
			}

		case TCOMP_UDVM_INST__LSHIFT:
			{
				operand_1 = tcomp_udvm_opget_reference_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__LSHIFT(udvm, operand_1, operand_2);
				break;
			}

		case TCOMP_UDVM_INST__RSHIFT:
			{
				operand_1 = tcomp_udvm_opget_reference_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__RSHIFT(udvm, operand_1, operand_2);
				break;
			}

		case TCOMP_UDVM_INST__ADD:
			{
				operand_1 = tcomp_udvm_opget_reference_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__ADD(udvm, operand_1, operand_2);
				break;
			}

		case TCOMP_UDVM_INST__SUBTRACT:
			{
				operand_1 = tcomp_udvm_opget_reference_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__SUBTRACT(udvm, operand_1, operand_2);
				break;
			}

		case TCOMP_UDVM_INST__MULTIPLY:
			{
				operand_1 = tcomp_udvm_opget_reference_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__MULTIPLY(udvm, operand_1, operand_2);
				break;
			}

		case TCOMP_UDVM_INST__DIVIDE:
			{
				operand_1 = tcomp_udvm_opget_reference_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__DIVIDE(udvm, operand_1, operand_2);
				break;
			}

		case TCOMP_UDVM_INST__REMAINDER:
			{
				operand_1 = tcomp_udvm_opget_reference_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__REMAINDER(udvm, operand_1, operand_2);
				break;
			}

		case TCOMP_UDVM_INST__SORT_ASCENDING:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				operand_3 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__SORT_ASCENDING(udvm, operand_1, operand_2, operand_3);
				break;
			}

		case TCOMP_UDVM_INST__SORT_DESCENDING:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				operand_3 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__SORT_DESCENDING(udvm, operand_1, operand_2, operand_3);
				break;
			}

		case TCOMP_UDVM_INST__SHA_1:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				operand_3 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__SHA_1(udvm, operand_1, operand_2, operand_3);
				break;
			}

		case TCOMP_UDVM_INST__LOAD:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__LOAD(udvm, operand_1, operand_2);
				break;
			}

		case TCOMP_UDVM_INST__MULTILOAD:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_literal_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__MULTILOAD(udvm, operand_1, operand_2);
				break;
			}

		case TCOMP_UDVM_INST__PUSH:
			{
				excution_failed = !TCOMP_UDVM_EXEC_INST__PUSH2(udvm);
				break;
			}

		case TCOMP_UDVM_INST__POP:
			{
				excution_failed = !TCOMP_UDVM_EXEC_INST__POP2(udvm);
				break;
			}

		case TCOMP_UDVM_INST__COPY:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				operand_3 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__COPY(udvm, operand_1, operand_2, operand_3);
				break;
			}

		case TCOMP_UDVM_INST__COPY_LITERAL:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				operand_3 = tcomp_udvm_opget_reference_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__COPY_LITERAL(udvm, operand_1, operand_2, operand_3);
				break;
			}

		case TCOMP_UDVM_INST__COPY_OFFSET:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				operand_3 = tcomp_udvm_opget_reference_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__COPY_OFFSET(udvm, operand_1, operand_2, operand_3);
				break;
			}

		case TCOMP_UDVM_INST__MEMSET:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				operand_3 = tcomp_udvm_opget_multitype_param(udvm);
				operand_4 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__MEMSET(udvm, operand_1, operand_2, operand_3, operand_4);
				break;
			}

		case TCOMP_UDVM_INST__JUMP:
			{
				excution_failed = !TCOMP_UDVM_EXEC_INST__JUMP2(udvm);
				break;
			}

		case TCOMP_UDVM_INST__COMPARE:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				operand_3 = tcomp_udvm_opget_address_param(udvm, udvm->last_memory_address_of_instruction);
				operand_4 = tcomp_udvm_opget_address_param(udvm, udvm->last_memory_address_of_instruction);
				operand_5 = tcomp_udvm_opget_address_param(udvm, udvm->last_memory_address_of_instruction);
				excution_failed = !TCOMP_UDVM_EXEC_INST__COMPARE(udvm, operand_1, operand_2, operand_3, operand_4, operand_5);
				break;
			}

		case TCOMP_UDVM_INST__CALL:
			{
				operand_1 = tcomp_udvm_opget_address_param(udvm, udvm->last_memory_address_of_instruction);
				excution_failed = !TCOMP_UDVM_EXEC_INST__CALL(udvm, operand_1);
				break;
			}

		case TCOMP_UDVM_INST__RETURN:
			{
				excution_failed = !TCOMP_UDVM_EXEC_INST__RETURN(udvm);
				break;
			}

		case TCOMP_UDVM_INST__SWITCH:
			{
				operand_1 = tcomp_udvm_opget_literal_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__SWITCH(udvm, operand_1, operand_2);
				break;
			}

		case TCOMP_UDVM_INST__CRC:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				operand_3 = tcomp_udvm_opget_multitype_param(udvm);
				operand_4 = tcomp_udvm_opget_reference_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__CRC(udvm, operand_1, operand_2, operand_3, operand_4);
				break;
			}

		case TCOMP_UDVM_INST__INPUT_BYTES:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				operand_3 = tcomp_udvm_opget_address_param(udvm, udvm->last_memory_address_of_instruction);
				excution_failed = !TCOMP_UDVM_EXEC_INST__INPUT_BYTES(udvm, operand_1, operand_2, operand_3);
				break;
			}

		case TCOMP_UDVM_INST__INPUT_BITS:
			{	
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				operand_3 = tcomp_udvm_opget_address_param(udvm, udvm->last_memory_address_of_instruction);
				excution_failed = !TCOMP_UDVM_EXEC_INST__INPUT_BITS(udvm, operand_1, operand_2, operand_3);
				break;
			}

		case TCOMP_UDVM_INST__INPUT_HUFFMAN:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_address_param(udvm, udvm->last_memory_address_of_instruction);
				operand_3 = tcomp_udvm_opget_literal_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__INPUT_HUFFMAN(udvm, operand_1, operand_2, operand_3);
				break;
			}

		case TCOMP_UDVM_INST__STATE_ACCESS:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				operand_3 = tcomp_udvm_opget_multitype_param(udvm);
				operand_4 = tcomp_udvm_opget_multitype_param(udvm);
				operand_5 = tcomp_udvm_opget_multitype_param(udvm);
				operand_6 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__STATE_ACCESS(udvm, operand_1, operand_2, operand_3, operand_4, operand_5, operand_6);
				break;
			}

		case TCOMP_UDVM_INST__STATE_CREATE:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				operand_3 = tcomp_udvm_opget_multitype_param(udvm);
				operand_4 = tcomp_udvm_opget_multitype_param(udvm);
				operand_5 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__STATE_CREATE(udvm, operand_1, operand_2, operand_3, operand_4, operand_5);
				break;
			}

		case TCOMP_UDVM_INST__STATE_FREE:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__STATE_FREE(udvm, operand_1, operand_2);
				break;
			}

		case TCOMP_UDVM_INST__OUTPUT:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__OUTPUT(udvm, operand_1, operand_2);
				break;
			}

		case TCOMP_UDVM_INST__END_MESSAGE:
			{
				operand_1 = tcomp_udvm_opget_multitype_param(udvm);
				operand_2 = tcomp_udvm_opget_multitype_param(udvm);
				operand_3 = tcomp_udvm_opget_multitype_param(udvm);
				operand_4 = tcomp_udvm_opget_multitype_param(udvm);
				operand_5 = tcomp_udvm_opget_multitype_param(udvm);
				operand_6 = tcomp_udvm_opget_multitype_param(udvm);
				operand_7 = tcomp_udvm_opget_multitype_param(udvm);
				excution_failed = !TCOMP_UDVM_EXEC_INST__END_MESSAGE(udvm, operand_1, operand_2, operand_3, operand_4, operand_5, operand_6, operand_7);
				end_message = 1;
				break;
			}

		default:
			tcomp_udvm_createNackInfo2(udvm, NACK_INVALID_OPCODE);
			goto bail;
		}

		//TCOMP_UDVM_DEBUG_PRINT(200);
	}

bail:
	udvm->lpResult->consumed_cycles = udvm->consumed_cycles;
	return (!excution_failed);
}

/**Decompress the bytecode.
*/
tsk_bool_t tcomp_udvm_decompress(tcomp_udvm_t *udvm)
{
	return tcomp_udvm_runByteCode(udvm);
}




















//========================================================
//	UDVM machine definition
//
static tsk_object_t* tcomp_udvm_create(tsk_object_t * self, va_list * app)
{
	tcomp_udvm_t *udvm = self;
	if(udvm){
		/* RFC 3320 - 7.  SigComp Message Format */
		udvm->sigCompMessage = va_arg(*app, tcomp_message_t *);
		udvm->stateHandler = va_arg(*app, tcomp_statehandler_t *);
		udvm->lpResult = va_arg(*app, tcomp_result_t *);
		
		udvm->isOK = 1;
		udvm->maximum_UDVM_cycles = 0; /* RFC 3320 subclause 8.6 */
		udvm->consumed_cycles = 0;
		udvm->memory = TCOMP_BUFFER_CREATE();

		/* Alloc UDVM memory */
		if(udvm->sigCompMessage->stream_based){
			/*
			* If the transport is stream-based however, then a fixed-size input buffer is required to accommodate the stream, independently of the
			* size of each SigComp message. So, for simplicity, the UDVM memory size is set to (decompression_memory_size / 2).
			*/

			tcomp_buffer_allocBuff(udvm->memory, udvm->stateHandler->sigcomp_parameters->dmsValue/2);
		}
		else{
			/*
			* If the transport is message-based then sufficient memory must be available
			* to buffer the entire SigComp message before it is passed to the UDVM. So if the message is n bytes long, then the UDVM memory size is set
			* to (decompression_memory_size - n), up to a maximum of 65536 bytes.
			*/
			tcomp_buffer_allocBuff(udvm->memory, (udvm->stateHandler->sigcomp_parameters->dmsValue-udvm->sigCompMessage->totalSize));
		}

		/*
		* Has feedback with my state id?
		*/
		if(tcomp_buffer_getSize(udvm->sigCompMessage->ret_feedback_buffer)){
			size_t size = tcomp_buffer_getSize(udvm->sigCompMessage->ret_feedback_buffer);
			tcomp_buffer_allocBuff(udvm->lpResult->ret_feedback, size);
			memcpy(tcomp_buffer_getBuffer(udvm->lpResult->ret_feedback), tcomp_buffer_getBuffer(udvm->sigCompMessage->ret_feedback_buffer), size);
		}
		
		/*
		*	Has state?
		*/
		if(tcomp_buffer_getSize(udvm->sigCompMessage->stateId)){
			/* Find the provided state */
			tcomp_state_t* lpState = NULL;
			uint16_t match_count = tcomp_statehandler_findState(udvm->stateHandler, udvm->sigCompMessage->stateId, &lpState);
			if( (!match_count || match_count>1 || !lpState)
				|| (lpState->minimum_access_length > tcomp_buffer_getSize(udvm->sigCompMessage->stateId))
				|| ((size_t)(lpState->address + lpState->length) > TCOMP_UDVM_GET_SIZE()) )
			{
				tcomp_udvm_createNackInfo(udvm, NACK_STATE_NOT_FOUND, udvm->sigCompMessage->stateId, 0);
				udvm->isOK = 0;
				return self;
			}
			//this->sigCompMessage->stateId.print();//FIXME
			/*
			* Copy bytecodes to UDVM memory
			*/
			if( (size_t)(lpState->address + lpState->length) >= TCOMP_UDVM_GET_SIZE() ){
				tcomp_udvm_createNackInfo2(udvm, NACK_SEGFAULT);
				udvm->isOK = 0;
				return self;
			}
			memcpy( TCOMP_UDVM_GET_BUFFER_AT(lpState->address), 
				tcomp_buffer_getBuffer(lpState->value), 
				tcomp_buffer_getSize(lpState->value) );
			
			//RFC 3320 - 7.2.  Accessing Stored State (Useful values)
			TCOMP_UDVM_SET_2BYTES_VAL(TCOMP_UDVM_HEADER_PARTIAL_STATE_ID_LENGTH_INDEX, tcomp_buffer_getSize(udvm->sigCompMessage->stateId));
			TCOMP_UDVM_SET_2BYTES_VAL(TCOMP_UDVM_HEADER_STATE_LENGTH_INDEX, tcomp_buffer_getSize(lpState->value));
			
			udvm->executionPointer = lpState->instruction;
		}
		else // DON'T HAVE STATE
		{
			/*
			* Copy bytecodes to UDVM memory
			*/
			size_t bytecodes_destination = udvm->sigCompMessage->bytecodes_destination;
			if( (bytecodes_destination + tcomp_buffer_getSize(udvm->sigCompMessage->uploaded_UDVM_buffer)) >= TCOMP_UDVM_GET_SIZE() ){
				tcomp_udvm_createNackInfo2(udvm, NACK_BYTECODES_TOO_LARGE);
				udvm->isOK = 0;
				return self;
			}
			memcpy( TCOMP_UDVM_GET_BUFFER_AT(bytecodes_destination),
				tcomp_buffer_getBuffer(udvm->sigCompMessage->uploaded_UDVM_buffer),
				tcomp_buffer_getSize(udvm->sigCompMessage->uploaded_UDVM_buffer));

			// Set pointer indicating execution index
			udvm->executionPointer = bytecodes_destination;
		}

		/* RFC 3320-Section_8.6.  UDVM Cycles
		/*
		* To ensure that a SigComp message cannot consume excessive processing
		* resources, SigComp limits the number of "UDVM cycles" allocated to
		* each message.  The number of available UDVM cycles is initialized to
		* 1000 plus the number of bits in the SigComp header (as described in
		* Section 7); this sum is then multiplied by cycles_per_bit.
		*/
		/*maximum_UDVM_cycles = (8 * n + 1000) * cycles_per_bit*///FIXME:header_size
		udvm->maximum_UDVM_cycles = ( (8 * udvm->sigCompMessage->header_size + 1000)* udvm->stateHandler->sigcomp_parameters->cpbValue );
		
		//
		//	RFC 3320 - 7.2.  Useful values
		//
		TCOMP_UDVM_SET_2BYTES_VAL(TCOMP_UDVM_HEADER_UDVM_MEMORY_SIZE_INDEX, TCOMP_UDVM_GET_SIZE());
		TCOMP_UDVM_SET_2BYTES_VAL(TCOMP_UDVM_HEADER_CYCLES_PER_BIT_INDEX, udvm->stateHandler->sigcomp_parameters->cpbValue);
		TCOMP_UDVM_SET_2BYTES_VAL(TCOMP_UDVM_HEADER_SIGCOMP_VERSION_INDEX, udvm->stateHandler->sigcomp_parameters->SigComp_version);
		memset(TCOMP_UDVM_GET_BUFFER_AT(TCOMP_UDVM_HEADER_RESERVED_INDEX), 0, TCOMP_UDVM_HEADER_RESERVED_SIZE);
	}
	else{
		TSK_DEBUG_ERROR("Failed to create new udvm machine.");
	}
	
	return self;
}

static tsk_object_t* tcomp_udvm_destroy(tsk_object_t *self)
{
	tcomp_udvm_t *udvm = self;
	if(udvm){
		TSK_OBJECT_SAFE_FREE(udvm->memory);
	}
	else{
		TSK_DEBUG_ERROR("Null udvm machine.");
	}

	return self;
}

static const tsk_object_def_t tcomp_udvm_def_s = 
{
	sizeof(tcomp_udvm_t),
	tcomp_udvm_create,
	tcomp_udvm_destroy,
	tsk_null
};
const tsk_object_def_t *tcomp_udvm_def_t = &tcomp_udvm_def_s;
