/*
* Copyright (c) 2001-2006 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:  EAP and WLAN authentication protocols.
*
*/


// This is enumeration of EAPOL source code.
#if defined(USE_EAP_MINIMUM_RELEASE_TRACES)
	#undef EAP_FILE_NUMBER_ENUM
	#define EAP_FILE_NUMBER_ENUM 579 
	#undef EAP_FILE_NUMBER_DATE 
	#define EAP_FILE_NUMBER_DATE 1127594498 
#endif //#if defined(USE_EAP_MINIMUM_RELEASE_TRACES)



#include "eap_am_memory.h"
#include "eap_automatic_variable.h"
#include "eap_tlv_message_data.h"

/** @file */


//--------------------------------------------------

EAP_FUNC_EXPORT eap_tlv_message_data_c::~eap_tlv_message_data_c()
{
}

//--------------------------------------------------

EAP_FUNC_EXPORT eap_tlv_message_data_c::eap_tlv_message_data_c(
	abs_eap_am_tools_c * const tools)
	: m_am_tools(tools)
	, m_message_data(tools)
{
}

//-------------------------------------------------------------------

/**
 * This function should increase reference count.
 */
EAP_FUNC_EXPORT void eap_tlv_message_data_c::object_increase_reference_count()
{
}

//-------------------------------------------------------------------

/**
 * This function should first decrease reference count
 * and second return the remaining reference count.
 * Reference count must not be decreased when it is zero.
 */
EAP_FUNC_EXPORT u32_t eap_tlv_message_data_c::object_decrease_reference_count()
{
	return 0;
}

//--------------------------------------------------

EAP_FUNC_EXPORT void * eap_tlv_message_data_c::get_message_data() const
{
	return m_message_data.get_data(m_message_data.get_data_length());
}

//--------------------------------------------------

EAP_FUNC_EXPORT u32_t eap_tlv_message_data_c::get_message_data_length() const
{
	return m_message_data.get_data_length();
}

//--------------------------------------------------

EAP_FUNC_EXPORT eap_status_e eap_tlv_message_data_c::allocate_message_data_buffer(
	const u32_t approximate_buffer_requirement)
{
	if (m_message_data.get_is_valid() == true)
	{
		return m_message_data.set_buffer_length(m_message_data.get_buffer_length()+approximate_buffer_requirement);
	}
	else
	{
		return m_message_data.set_buffer_length(approximate_buffer_requirement);
	}
}

//--------------------------------------------------

EAP_FUNC_EXPORT eap_status_e eap_tlv_message_data_c::copy_message_data(
	const u32_t length,
	const void * const value)
{
	eap_status_e status(eap_status_process_general_error);

	EAP_TRACE_DATA_DEBUG(
		m_am_tools,
		EAP_TRACE_FLAGS_MESSAGE_DATA,
		(EAPL("copy_message_data()"),
		value,
		length));

	status = m_message_data.set_copy_of_buffer(
		value,
		length);
	if (status != eap_status_ok)
	{
		EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
		return EAP_STATUS_RETURN(m_am_tools, status);
	}

	return EAP_STATUS_RETURN(m_am_tools, status);
}

//--------------------------------------------------

EAP_FUNC_EXPORT eap_status_e eap_tlv_message_data_c::set_message_data(
	const u32_t length,
	const void * const value)
{
	eap_status_e status(eap_status_process_general_error);

	EAP_TRACE_DATA_DEBUG(
		m_am_tools,
		EAP_TRACE_FLAGS_MESSAGE_DATA,
		(EAPL("set_message_data()"),
		value,
		length));

	status = m_message_data.set_buffer(
		value,
		length,
		false,
		false);
	if (status != eap_status_ok)
	{
		EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
		return EAP_STATUS_RETURN(m_am_tools, status);
	}

	return EAP_STATUS_RETURN(m_am_tools, status);
}

//--------------------------------------------------

EAP_FUNC_EXPORT eap_status_e eap_tlv_message_data_c::add_message_data(
	const eap_tlv_type_t type,
	const u32_t length,
	const void * const value)
{
	eap_status_e status(eap_status_process_general_error);

	EAP_TRACE_DEBUG(
		m_am_tools,
		EAP_TRACE_FLAGS_MESSAGE_DATA,
		(EAPL("eap_tlv_message_data_c::add_message_data():   type %2d=0x%08x, length %3d=0x%08x\n"),
		 type,
		 type,
		 length,
		 length));

	EAP_TRACE_DATA_DEBUG(
		m_am_tools,
		EAP_TRACE_FLAGS_MESSAGE_DATA,
		(EAPL("add_message_data()"),
		value,
		length));

	u32_t tlv_header_offset(0ul);

	if (m_message_data.get_is_valid() == true)
	{
		tlv_header_offset = m_message_data.get_data_length();
	}
	else
	{
		tlv_header_offset = 0ul;
	}

	// This will allocate space for eap_tlv_header_c too.
	status = m_message_data.add_data_to_offset(
		tlv_header_offset+eap_tlv_header_c::get_header_length(),
		value,
		length);
	if (status != eap_status_ok)
	{
		EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
		return EAP_STATUS_RETURN(m_am_tools, status);
	}

	// Add eap_tlv_header_c to message.
	eap_tlv_header_c header(
		m_am_tools,
		m_message_data.get_data_offset(tlv_header_offset, eap_tlv_header_c::get_header_length()+length),
		eap_tlv_header_c::get_header_length()+length);

	if (header.get_is_valid() == false)
	{
		EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
		return EAP_STATUS_RETURN(m_am_tools, eap_status_allocation_error);
	}

	status = header.reset_header(type, length);
	if (status != eap_status_ok)
	{
		EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
		return EAP_STATUS_RETURN(m_am_tools, status);
	}

	return EAP_STATUS_RETURN(m_am_tools, status);
}

//--------------------------------------------------

EAP_FUNC_EXPORT eap_status_e eap_tlv_message_data_c::add_message_data_array(
	const eap_tlv_type_t type,
	const u32_t length_of_each_data_block,
	eap_array_c<eap_variable_data_c> * const data_array)
{
	eap_status_e status(eap_status_process_general_error);

	u32_t tlv_header_offset(0ul);

	if (m_message_data.get_is_valid() == true)
	{
		tlv_header_offset = m_message_data.get_data_length();
	}
	else
	{
		tlv_header_offset = 0ul;
	}

	if (data_array->get_object_count() != 0ul)
	{
		for (u32_t ind = 0ul; ind < data_array->get_object_count(); ind++)
		{
			eap_variable_data_c * const data = data_array->get_object(ind);
			if (data == 0)
			{
				EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
				return EAP_STATUS_RETURN(m_am_tools, eap_status_illegal_parameter);
			}

			if (data->get_data_length() != length_of_each_data_block)
			{
				EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
				return EAP_STATUS_RETURN(m_am_tools, eap_status_illegal_parameter);
			}

			// This will allocate space for eap_tlv_header_c too.
			status = m_message_data.add_data_to_offset(
				tlv_header_offset+eap_tlv_header_c::get_header_length()+(ind*length_of_each_data_block),
				data);
			if (status != eap_status_ok)
			{
				EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
				return EAP_STATUS_RETURN(m_am_tools, status);
			}
		} // for()
	}
	else
	{
		// This will allocate space for eap_tlv_header_c only.
		status = m_message_data.add_data_to_offset(
			tlv_header_offset+eap_tlv_header_c::get_header_length(),
			0,
			0ul);
		if (status != eap_status_ok)
		{
			EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
			return EAP_STATUS_RETURN(m_am_tools, status);
		}
	}

	u32_t length = data_array->get_object_count() * length_of_each_data_block;

	// Add eap_tlv_header_c to message.
	eap_tlv_header_c header(
		m_am_tools,
		m_message_data.get_data_offset(tlv_header_offset, eap_tlv_header_c::get_header_length()+length),
		eap_tlv_header_c::get_header_length()+length);

	if (header.get_is_valid() == false)
	{
		EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
		return EAP_STATUS_RETURN(m_am_tools, eap_status_allocation_error);
	}

	status = header.reset_header(type, length);
	if (status != eap_status_ok)
	{
		EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
		return EAP_STATUS_RETURN(m_am_tools, status);
	}

	return EAP_STATUS_RETURN(m_am_tools, status);
}

//--------------------------------------------------

EAP_FUNC_EXPORT eap_status_e eap_tlv_message_data_c::parse_message_data(
	eap_array_c<eap_tlv_header_c> * const tlv_blocks)
{
	eap_status_e status(eap_status_ok);

	tlv_blocks->reset();

	u8_t *next_header_begins = m_message_data.get_data();
	u32_t remaining_message_data_length = m_message_data.get_data_length();

	if (next_header_begins == 0
		|| remaining_message_data_length == 0)
	{
		// TLV is empty.
		EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
		return EAP_STATUS_RETURN(m_am_tools, eap_status_ok);
	}

	// This is reference to the first eap_tlv_header_c in the message_data.
	eap_tlv_header_c header(
		m_am_tools,
		next_header_begins,
		remaining_message_data_length);

	while (header.get_is_valid() == true)
	{
		const u32_t payload_length(header.get_header_length()+header.get_value_length());

		status = header.check_header();
		if (status != eap_status_ok)
		{
			EAP_TRACE_DATA_ERROR(
				m_am_tools,
				TRACE_FLAGS_ERROR,
				(EAPL("ERROR: illegal payload"),
				header.get_header_buffer(header.get_header_buffer_length()),
				header.get_header_buffer_length()));

			EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
			return EAP_STATUS_RETURN(m_am_tools, status);
		}
		else
		{
			EAP_TRACE_DATA_DEBUG(
				m_am_tools,
				EAP_TRACE_FLAGS_MESSAGE_DATA,
				(EAPL("payload"),
				header.get_header_buffer(payload_length),
				payload_length));
		}


		if (remaining_message_data_length < payload_length)
		{
			EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
			return EAP_STATUS_RETURN(m_am_tools, eap_status_illegal_payload);
		}

		{
			eap_tlv_header_c * const tlv = new eap_tlv_header_c(
				m_am_tools,
				header.get_header_buffer(payload_length),
				payload_length);

			eap_automatic_variable_c<eap_tlv_header_c> automatic_tlv(m_am_tools, tlv);

			if (tlv == 0)
			{
				EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
				return EAP_STATUS_RETURN(m_am_tools, eap_status_allocation_error);
			}
			
			if (tlv->get_is_valid() == false)
			{
				EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
				return EAP_STATUS_RETURN(m_am_tools, eap_status_allocation_error);
			}

			automatic_tlv.do_not_free_variable();

			status = tlv_blocks->add_object(tlv, true);
			if (status != eap_status_ok)
			{
				EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
				return EAP_STATUS_RETURN(m_am_tools, status);
			}


			u32_t tlv_length = tlv->get_header_length()+tlv->get_value_length();
			next_header_begins += tlv_length;
			remaining_message_data_length -= tlv_length;

			if (next_header_begins >= (m_message_data.get_data()+m_message_data.get_data_length())
				|| remaining_message_data_length == 0)
			{
				// No next header.
				break;
			}
		}

		header.set_header_buffer(
			next_header_begins,
			remaining_message_data_length);
		if (header.get_is_valid() == false)
		{
			EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
			return EAP_STATUS_RETURN(m_am_tools, status);
		}
	} // while()

	return EAP_STATUS_RETURN(m_am_tools, status);
}

//--------------------------------------------------

EAP_FUNC_EXPORT eap_status_e eap_tlv_message_data_c::add_message_header(
	const eap_tlv_type_t type,
	const u32_t length)
{
	// This will allocate space for eap_tlv_header_c without any value.

	EAP_TRACE_DEBUG(
		m_am_tools,
		EAP_TRACE_FLAGS_MESSAGE_DATA,
		(EAPL("eap_tlv_message_data_c::add_message_header(): type %2d=0x%08x, length %3d=0x%08x\n"),
		 type,
		 type,
		 length,
		 length));

	u32_t network_order_type(eap_htonl(type));

	eap_status_e status = m_message_data.add_data(
		&network_order_type,
		sizeof(network_order_type));
	if (status != eap_status_ok)
	{
		EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
		return EAP_STATUS_RETURN(m_am_tools, status);
	}

	u32_t network_order_length(eap_htonl(length));

	status = m_message_data.add_data(
		&network_order_length,
		sizeof(network_order_length));
	if (status != eap_status_ok)
	{
		EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
		return EAP_STATUS_RETURN(m_am_tools, status);
	}

	EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
	return EAP_STATUS_RETURN(m_am_tools, status);
}

//--------------------------------------------------

EAP_FUNC_EXPORT eap_status_e eap_tlv_message_data_c::allocate_message_buffer(
	const eap_tlv_type_t type,
	const u32_t length,
	void * * const buffer)
{
	eap_status_e status(eap_status_process_general_error);

	*buffer = 0;

	u32_t tlv_header_offset(0ul);

	if (m_message_data.get_is_valid() == true)
	{
		tlv_header_offset = m_message_data.get_data_length();
	}
	else
	{
		tlv_header_offset = 0ul;
	}

	// This will allocate space for eap_tlv_header_c only.
	status = m_message_data.add_data_to_offset(
		tlv_header_offset+eap_tlv_header_c::get_header_length()+length,
		0,
		0ul);
	if (status != eap_status_ok)
	{
		EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
		return EAP_STATUS_RETURN(m_am_tools, status);
	}

	// Add eap_tlv_header_c to message.
	eap_tlv_header_c header(
		m_am_tools,
		m_message_data.get_data_offset(tlv_header_offset, eap_tlv_header_c::get_header_length()+length),
		eap_tlv_header_c::get_header_length()+length);

	if (header.get_is_valid() == false)
	{
		EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
		return EAP_STATUS_RETURN(m_am_tools, eap_status_allocation_error);
	}

	status = header.reset_header(type, length);
	if (status != eap_status_ok)
	{
		EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
		return EAP_STATUS_RETURN(m_am_tools, status);
	}

	*buffer = header.get_value(length);

	if ((*buffer) == 0)
	{
		EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
		return EAP_STATUS_RETURN(m_am_tools, eap_status_allocation_error);
	}

	EAP_TRACE_END(m_am_tools, TRACE_FLAGS_DEFAULT);
	return EAP_STATUS_RETURN(m_am_tools, status);
}

//--------------------------------------------------

EAP_FUNC_EXPORT bool eap_tlv_message_data_c::get_is_valid()
{
	return m_message_data.get_is_valid();
}

//--------------------------------------------------



// End.
