/*
* 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 73 
	#undef EAP_FILE_NUMBER_DATE 
	#define EAP_FILE_NUMBER_DATE 1127594498 
#endif //#if defined(USE_EAP_MINIMUM_RELEASE_TRACES)



#include "eap_diameter_avp_header.h"

/** @file */

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

/**
 * This function returns length of the required padding.
 */
u32_t eap_diameter_avp_header_c::get_padding_length() const
{
	// NOTE AVP is padded always to multiple of 4 octets.
	u32_t avp_length = get_length();
	u32_t remainder = avp_length % 4ul;
	if (remainder != 0ul)
	{
		return 4ul - remainder;
	}
	else
	{
		return 0;
	}
}

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

/**
 * The destructor of the eap_diameter_avp_header_c class does nothing.
 */
eap_diameter_avp_header_c::~eap_diameter_avp_header_c()
{
}

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

/**
 * The constructor of the eap_diameter_avp_header_c class simply initializes the attributes.
 */
eap_diameter_avp_header_c::eap_diameter_avp_header_c(
	abs_eap_am_tools_c * const tools,
	void * const header_begin,
	const u32_t header_buffer_length)
	: eap_general_header_base_c(tools, header_begin, header_buffer_length)
	, m_am_tools(tools)
{
}

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

/**
 * This function returns the AVP Code.
 */
eap_diameter_avp_code_c eap_diameter_avp_header_c::get_avp_code() const
{
	const u8_t * const code_data = get_header_offset(m_avp_code_offset, sizeof(u32_t));
	if (code_data  != 0)
	{
		const u32_t avp_code = eap_read_u32_t_network_order(code_data, sizeof(u32_t));

		eap_diameter_vendor_id_e vendor_id(eap_diameter_vendor_id_of_ietf);

		if (get_avp_flag_vendor_specific() == true)
		{
			// We have vendor specific AVP code.
			// This reads the vendor ID.
			const u8_t * const vendor_id_data = get_header_offset(m_vendor_id_or_data_offset, sizeof(u32_t));
			if (vendor_id_data  != 0)
			{
				vendor_id = static_cast<eap_diameter_vendor_id_e>(eap_read_u32_t_network_order(vendor_id_data, sizeof(u32_t)));
			}
			else
			{
				return eap_diameter_avp_code_none;
			}
		}

		eap_diameter_avp_code_c vendor_specific_code(vendor_id, avp_code);

		return vendor_specific_code;
	}
	else
	{
		return eap_diameter_avp_code_none;
	}
}

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

/**
 * This function returns the AVP vendor specific flag.
 */
bool eap_diameter_avp_header_c::get_avp_flag_vendor_specific() const
{
	const u8_t * const flags = get_header_offset(m_flags_offset, sizeof(u8_t));
	if (flags != 0)
	{
		if (((*flags) & m_flag_mask_vendor_specific_avp) != 0)
		{
			return true;
		}
	}
	return false;
}

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

/**
 * This function returns the AVP mandatory flag.
 */
bool eap_diameter_avp_header_c::get_avp_flag_mandatory_avp() const
{
	const u8_t * const flags = get_header_offset(m_flags_offset, sizeof(u8_t));
	if (flags != 0)
	{
		if (((*flags) & m_flag_mask_mandatory_avp) != 0)
		{
			return true;
		}
	}
	return false;
}

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

/**
 * This function returns the AVP reserved flags.
 */
u8_t eap_diameter_avp_header_c::get_avp_flags_reserved() const
{
	const u8_t * const flags = get_header_offset(m_flags_offset, sizeof(u8_t));
	if (flags != 0)
	{
		return static_cast<u8_t>((*flags) & m_flag_mask_reserved_avp);
	}
	
	return 0xff;
}

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

/**
 * This function returns the length of AVP (header+data).
 */
u32_t eap_diameter_avp_header_c::get_length() const
{
	return get_header_length() + get_data_length();
}

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

/**
 * This function returns the length of AVP data.
 */
u32_t eap_diameter_avp_header_c::get_data_length() const
{
	const u8_t * const length_data = get_header_offset(m_length_offset, 3ul*sizeof(u8_t));
	if (length_data != 0)
	{
		u32_t header_and_data_length = eap_read_u24_t_network_order(length_data, 3ul*sizeof(u8_t));
		if (header_and_data_length > get_header_length())
		{
			return header_and_data_length - get_header_length();
		}
	}
	return 0ul;
}

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

/**
 * This function returns the header length of AVP.
 */
u32_t eap_diameter_avp_header_c::get_header_length(const bool include_vendor_specific)
{
	if (include_vendor_specific == true)
	{
		return m_data_with_vendor_id_offset;
	}
	else
	{
		return m_vendor_id_or_data_offset;
	}
}

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

/**
 * This function returns the header length of AVP.
 */
u32_t eap_diameter_avp_header_c::get_header_length() const
{
	return get_header_length(get_avp_flag_vendor_specific());
}

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

/**
 * This function returns pointer to the offset of data of AVP.
 * @param offset is the offset of queried data in bytes.
 * @param contignuous_bytes is the length of queried data in bytes.
 */
u8_t * eap_diameter_avp_header_c::get_data_offset(const u32_t offset, const u32_t contignuous_bytes) const
{
	u32_t data_length = get_header_buffer_length() - get_header_length();

	if (data_length >= offset+contignuous_bytes)
	{
		u8_t * data = 0;
		
		if (get_avp_flag_vendor_specific() == true)
		{
			data = get_header_offset(m_data_with_vendor_id_offset, offset+contignuous_bytes);
		}
		else
		{
			data = get_header_offset(m_vendor_id_or_data_offset, offset+contignuous_bytes);
		}
		
		if (data != 0)
		{
			return &data[offset];
		}
		else
		{
			return 0;
		}
	}
	else
	{
		EAP_ASSERT_ALWAYS(data_length >= offset+contignuous_bytes);
	}
	return 0;
}

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

/**
 * This function returns pointer to the offset of data of AVP.
 * @param contignuous_bytes is the length of queried data in bytes.
 */
u8_t * eap_diameter_avp_header_c::get_data(const u32_t contignuous_bytes) const
{
	return get_data_offset(0u, contignuous_bytes);
}

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

/**
 * This function return pointer to the next AVP header in the same buffer.
 */
u8_t * eap_diameter_avp_header_c::get_next_header() const
{
	// NOTE AVP is padded always to multiple of 4 octets.
	u32_t data_length = get_header_buffer_length() - get_header_length();
	u32_t avp_data_length = get_data_length();
	u32_t padding_length = get_padding_length();
	u32_t required_length = avp_data_length+padding_length+EAP_DIAMETER_AVP_MINIMUM_HEADER_LENGTH;
	
	if (data_length >= required_length)
	{
		return get_data_offset(avp_data_length+padding_length, EAP_DIAMETER_AVP_MINIMUM_HEADER_LENGTH);
	}
	else
	{
		return 0;
	}
}

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

/**
 * This function checks the header is valid.
 */
eap_status_e eap_diameter_avp_header_c::check_header() const
{
	if (get_avp_code() != eap_diameter_avp_code_eap_message)
	{
		return EAP_STATUS_RETURN(m_am_tools, eap_status_header_corrupted);
	}
	else if (get_avp_flags_reserved() != 0)
	{
		return EAP_STATUS_RETURN(m_am_tools, eap_status_header_corrupted);
	}
	else if (get_avp_flag_vendor_specific() == true
			 && get_header_buffer_length() < m_data_with_vendor_id_offset)
	{
		return EAP_STATUS_RETURN(m_am_tools, eap_status_buffer_too_short);
	}
	else if (get_avp_flag_vendor_specific() == false
			 && get_header_buffer_length() < m_vendor_id_or_data_offset)
	{
		return EAP_STATUS_RETURN(m_am_tools, eap_status_buffer_too_short);
	}
	return EAP_STATUS_RETURN(m_am_tools, eap_status_ok);
}

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

/**
 * This function returns debug strings of the AVP Code.
 */
eap_const_string eap_diameter_avp_header_c::get_avp_code_string() const
{

#if defined(USE_EAP_TRACE_STRINGS)
	const eap_diameter_avp_code_c code = get_avp_code();

	EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_none)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_user_name)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_user_password)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_chap_password)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_nas_ip_address)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_nas_port)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_service_type)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_framed_protocol)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_framed_ip_address)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_framed_ip_netmask)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_framed_routing)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_filter_id)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_framed_mtu)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_framed_compression)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_login_ip_host)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_login_service)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_login_tcp_port)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_reply_message)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_callback_number)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_callback_id)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_framed_route)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_framed_ipx_network)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_state)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_class)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_vendor_specific)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_session_timeout)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_idle_timeout)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_termination_action)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_called_station_id)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_calling_station_id)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_nas_identifier)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_proxy_state)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_login_lat_service)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_login_lat_node)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_login_lat_group)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_framed_appletalk_link)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_framed_appletalk_network)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_framed_appletalk_zone)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_chap_challenge)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_nas_port_type)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_port_limit)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_login_lat_port)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_eap_message)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_message_authenticator)
	else EAP_IF_RETURN_STRING(code, eap_diameter_avp_code_eap_message)
#if defined(EAP_USE_TTLS_PLAIN_MS_CHAP_V2_HACK)
	else EAP_IF_RETURN_STRING(code, eap_diameter_vendor_code_of_microsoft_ms_chap_error.get_code())
	else EAP_IF_RETURN_STRING(code, eap_diameter_vendor_code_of_microsoft_ms_chap_nt_enc_pw.get_code())
	else EAP_IF_RETURN_STRING(code, eap_diameter_vendor_code_of_microsoft_ms_chap_challenge.get_code())
	else EAP_IF_RETURN_STRING(code, eap_diameter_vendor_code_of_microsoft_ms_chap2_response.get_code())
	else EAP_IF_RETURN_STRING(code, eap_diameter_vendor_code_of_microsoft_ms_chap2_success.get_code())
	else EAP_IF_RETURN_STRING(code, eap_diameter_vendor_code_of_microsoft_ms_chap2_cpw.get_code())
#endif //#if defined(EAP_USE_TTLS_PLAIN_MS_CHAP_V2_HACK)
	else
#endif // #if defined(USE_EAP_TRACE_STRINGS)
	{
		return EAPL("Unknown AVP code");
	}
}

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

/**
 * This function sets the AVP Code.
 */
eap_status_e eap_diameter_avp_header_c::set_avp_code(const eap_diameter_avp_code_c code)
{
	eap_status_e status(eap_status_process_general_error);

	{
		u8_t * const code_data = get_header_offset(m_avp_code_offset, sizeof(u32_t));
		
		if (code_data == 0)
		{
			return EAP_STATUS_RETURN(m_am_tools, eap_status_allocation_error);
		}

		status = eap_write_u32_t_network_order(
				code_data,
				sizeof(u32_t),
				code.get_vendor_code());
		if (status != eap_status_ok)
		{
			return EAP_STATUS_RETURN(m_am_tools, status);
		}
	}

	if (code.get_vendor_id() != eap_diameter_vendor_id_of_ietf)
	{
		// We have vendor specific AVP code.
		// This writes the vendor ID.

		status = set_avp_flag_vendor_specific(true);
		if (status != eap_status_ok)
		{
			return EAP_STATUS_RETURN(m_am_tools, status);
		}

		u8_t * const vendor_id_data = get_header_offset(m_vendor_id_or_data_offset, sizeof(u32_t));
		
		if (vendor_id_data == 0)
		{
			return EAP_STATUS_RETURN(m_am_tools, eap_status_allocation_error);
		}

		status = eap_write_u32_t_network_order(
				vendor_id_data,
				sizeof(u32_t),
				code.get_vendor_id());
		if (status != eap_status_ok)
		{
			return EAP_STATUS_RETURN(m_am_tools, status);
		}
	}

	return EAP_STATUS_RETURN(m_am_tools, status);
}

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

/**
 * This function sets the AVP vendor specific flag.
 */
eap_status_e eap_diameter_avp_header_c::set_avp_flag_vendor_specific(
	const bool vendor_specific)
{
	u8_t * const flags = get_header_offset(m_flags_offset, sizeof(u8_t));
	if (flags != 0)
	{
		if (vendor_specific == true)
		{
			*flags = static_cast<u8_t>((*flags) | m_flag_mask_vendor_specific_avp);
		}
		else
		{
			*flags = static_cast<u8_t>((*flags) & ~m_flag_mask_vendor_specific_avp);
		}
		return EAP_STATUS_RETURN(m_am_tools, eap_status_ok);
	}
	
	return EAP_STATUS_RETURN(m_am_tools, eap_status_allocation_error);
}

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

/**
 * This function sets the AVP mandatory flag.
 */
eap_status_e eap_diameter_avp_header_c::set_avp_flag_mandatory_avp(const bool mandatory)
{
	u8_t * const flags = get_header_offset(m_flags_offset, sizeof(u8_t));
	if (flags != 0)
	{
		if (mandatory == true)
		{
			*flags = static_cast<u8_t>((*flags) | m_flag_mask_mandatory_avp);
		}
		else
		{
			*flags = static_cast<u8_t>((*flags) & ~m_flag_mask_mandatory_avp);
		}
		return EAP_STATUS_RETURN(m_am_tools, eap_status_ok);
	}
	
	return EAP_STATUS_RETURN(m_am_tools, eap_status_allocation_error);
}

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

/**
 * This function sets the AVP mandatory flag.
 */
eap_status_e eap_diameter_avp_header_c::set_avp_flags_reserved()
{
	u8_t * const flags = get_header_offset(m_flags_offset, sizeof(u8_t));
	if (flags != 0)
	{
		*flags = static_cast<u8_t>(((*flags) & ~m_flag_mask_reserved_avp) | 0ul);
		return EAP_STATUS_RETURN(m_am_tools, eap_status_ok);
	}
	
	return EAP_STATUS_RETURN(m_am_tools, eap_status_allocation_error);
}

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

/**
 * This function sets the AVP data length.
 */
eap_status_e eap_diameter_avp_header_c::set_data_length(const u32_t p_length)
{
	u8_t * const length_data = get_header_offset(m_length_offset, 3ul*sizeof(u8_t));
	if (length_data != 0)
	{
		return EAP_STATUS_RETURN(
			m_am_tools,
			eap_write_u24_t_network_order(
				length_data,
				3ul*sizeof(u8_t),
				p_length));
	}

	return EAP_STATUS_RETURN(m_am_tools, eap_status_allocation_error);
}

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

/**
 * This function resets the AVP header.
 */
eap_status_e eap_diameter_avp_header_c::reset_header(const u16_t data_length)
{
	eap_status_e status = set_avp_code(eap_diameter_avp_code_none);
	if (status != eap_status_ok)
	{
		return EAP_STATUS_RETURN(m_am_tools, status);
	}
	
	status = set_avp_flag_vendor_specific(false);
	if (status != eap_status_ok)
	{
		return EAP_STATUS_RETURN(m_am_tools, status);
	}
	
	status = set_avp_flag_mandatory_avp(false);
	if (status != eap_status_ok)
	{
		return EAP_STATUS_RETURN(m_am_tools, status);
	}
	
	status = set_avp_flags_reserved();
	if (status != eap_status_ok)
	{
		return EAP_STATUS_RETURN(m_am_tools, status);
	}

	status = set_data_length(data_length);
	if (status != eap_status_ok)
	{
		return EAP_STATUS_RETURN(m_am_tools, status);
	}
	
	return EAP_STATUS_RETURN(m_am_tools, status);
}

//-----------------------------------------------------------------------------
// End.
