/*
* 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 tnet_stun.c
 * @brief Session Traversal Utilities for NAT (STUN) implementation as per RFC 5389 and RFC 3489(Obsolete).
 *
 * @author Mamadou Diop <diopmamadou(at)doubango.org>
 *
 * @date Created: Sat Nov 8 16:54:58 2009 mdiop
 */
#include "tnet_stun.h"

#include "../tnet_nat.h"
#include "../tnet_utils.h"

#include "tsk_md5.h"
#include "tsk_string.h"
#include "tsk_memory.h"
#include "tsk_buffer.h"
#include "tsk_debug.h"

#include <string.h>

/**@defgroup tnet_stun_group STUN2 (RFC 5389) implementation.
*/


/**@ingroup tnet_stun_group
* Creates new @ref tnet_stun_binding_t object.
*/
tnet_stun_binding_t* tnet_stun_binding_create(tnet_fd_t fd, tnet_socket_type_t socket_type, const char* server_address, tnet_port_t server_port, const char* username, const char* password)
{
	return tsk_object_new(tnet_stun_binding_def_t, fd, socket_type, server_address, server_port, username, password);
}

/**@ingroup tnet_stun_group
 *
 * Create generic STUN2 request with all mandatory headers and attributes. 
 *
 * @param [in,out]	binding	The binding object from which to create the request. 
 *
 * @retval	STUN2 request if succeed and NULL otherwise. 
**/
tnet_stun_message_t *tnet_stun_create_request(const tnet_stun_binding_t* binding)
{
	tnet_stun_message_t *message = tnet_stun_message_create(binding->username, binding->password);
	message->realm = tsk_strdup(binding->realm);
	message->nonce = tsk_strdup(binding->nonce);

	if(message){
		/* Set the request type (RFC 5389 defines only one type) */
		message->type = stun_binding_request;

		{	/* Create random transaction id */
			tsk_istr_t random;
			tsk_md5digest_t digest;

			tsk_strrandom(&random);
			TSK_MD5_DIGEST_CALC(random, sizeof(random), digest);

			memcpy(message->transaction_id, digest, TNET_STUN_TRANSACID_SIZE);
		}

		/* Add software attribute */
		if(binding->software){
			tnet_stun_attribute_t* attribute = (tnet_stun_attribute_t*)tnet_stun_attribute_software_create(binding->software, tsk_strlen(binding->software));
			tnet_stun_message_add_attribute(message, &attribute);
		}
	}
	
	return message;
}

int tnet_stun_send_reliably(const tnet_stun_message_t* message)
{
	return -1;
}


/**@ingroup tnet_stun_group
 *
 * Internal function to send a STUN message using unrealiable protocol such as UDP.
 *
 *
 * @param	localFD			The local file descriptor. 
 * @param	RTO				The Retransmission TimeOut. 
 * @param	Rc				The Number of retransmissions. 
 * @param [in,out]	message	The message to send. 
 * @param [in,out]	server	The destination STUN server. 
 *
 * @return	The response from the server or NULL if transport error. 
**/
tnet_stun_response_t* tnet_stun_send_unreliably(tnet_fd_t localFD, uint16_t RTO, uint16_t Rc, const tnet_stun_message_t* message, struct sockaddr* server)
{
	/*	RFC 5389 - 7.2.1.  Sending over UDP
		STUN indications are not retransmitted; thus, indication transactions over UDP 
		are not reliable.
	*/
	//int retransmit = (message->type == stun_binding_request);

	int ret = -1;
	uint16_t i, rto = RTO;
	struct timeval tv;
	fd_set set;

	tsk_buffer_t *buffer = tnet_stun_message_serialize(message);
	tnet_stun_response_t *response = 0;
	
	if(!buffer)
	{
		goto bail;
	}
	
	{
//#ifndef SIO_UDP_CONNRESET
//#  ifndef IOC_VENDOR
//#    define IOC_VENDOR 0x18000000
//#  endif
//#  ifndef _WSAIOW
//#    define _WSAIOW(x,y) (IOC_IN|(x)|(y))
//#  endif
//#  define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12)
//#endif
//		DWORD dwBytesReturned = 0;
//		BOOL bNewBehavior = TRUE;
//		DWORD status;
//		
//		// disable  new behavior using
//		// IOCTL: SIO_UDP_CONNRESET
//		status = WSAIoctl(localFD, SIO_UDP_CONNRESET, &bNewBehavior, sizeof(bNewBehavior),
//			NULL, 0, &dwBytesReturned, NULL, NULL);
	}

	tv.tv_sec = 0;
	tv.tv_usec = 0;

	/*	RFC 5389 - 7.2.1.  Sending over UDP
		A client SHOULD retransmit a STUN request message starting with an
		interval of RTO ("Retransmission TimeOut"), doubling after each
		retransmission.

		e.g. 0 ms, 500 ms, 1500 ms, 3500 ms, 7500ms, 15500 ms, and 31500 ms
	*/
	for(i=0; i<Rc; i++){
		tv.tv_sec += rto/1000;
		tv.tv_usec += (rto% 1000) * 1000;
		
		ret = tnet_sockfd_sendto(localFD, server, buffer->data, buffer->size);
		
		FD_ZERO(&set);
		FD_SET(localFD, &set);

		if((ret = select(localFD+1, &set, NULL, NULL, &tv))<0){
			goto bail;
		}
		else if(ret == 0){
		/* timeout */

			rto *= 2;
			continue;
		}
		else if(FD_ISSET(localFD, &set)){
		/* there is data to read */

			tsk_size_t len = 0;
			void* data = 0;

			/* Check how how many bytes are pending */
			if((ret = tnet_ioctlt(localFD, FIONREAD, &len))<0){
				goto bail;
			}
			
			/* Receive pending data */
			data = tsk_calloc(len, sizeof(uint8_t));
			if((ret = tnet_sockfd_recvfrom(localFD, data, len, 0, server))<0){
				TSK_FREE(data);
								
				TSK_DEBUG_ERROR("Recving STUN dgrams failed with error code:%d", tnet_geterrno());
				goto bail;
			}

			/* Parse the incoming response. */
			response = tnet_stun_message_deserialize(data, (tsk_size_t)ret);
			TSK_FREE(data);

			if(response){
				if(tnet_stun_transacid_cmp(message->transaction_id, response->transaction_id)){
				/* Not same transaction id */
					TSK_OBJECT_SAFE_FREE(response);
					continue;
				}
			}
			
			goto bail;
		}
		else{
			continue;
		}
	}
	
bail:
	TSK_OBJECT_SAFE_FREE(buffer);

	return response;
}

/**@ingroup tnet_stun_group
 * 	Internal function to send a STUN2 binding request over the network.
 *
 * @param [in,out]	context	The NAT context holding the user preferences. 
 * @param [in,out]	binding	The STUN binding object used to create the message to send. 
 *
 * @return	Zero if succeed and non-zero error code otherwise. 
**/
int tnet_stun_send_bind(const tnet_nat_context_t* context, tnet_stun_binding_t *binding)
{
	int ret = -1;
	tnet_stun_response_t *response = 0;
	tnet_stun_request_t *request = 0;
	

	goto stun_phase0;

	/*	RFC 5389 - 10.2.1.1.  First Request
		If the client has not completed a successful request/response
		transaction with the server (as identified by hostname, if the DNS
		procedures of Section 9 are used, else IP address if not), it SHOULD
		omit the USERNAME, MESSAGE-INTEGRITY, REALM, and NONCE attributes.
		In other words, the very first request is sent as if there were no
		authentication or message integrity applied.
	*/
stun_phase0:
	{
		if(!(request = tnet_stun_create_request(binding))){
			goto bail;
		}		

		if(TNET_SOCKET_TYPE_IS_DGRAM(context->socket_type)){
			response = tnet_stun_send_unreliably(binding->localFD, context->RTO, context->Rc, request, (struct sockaddr*)&binding->server);
		}

		if(response){
			if(TNET_STUN_RESPONSE_IS_ERROR(response)){
				short code = tnet_stun_message_get_errorcode(response);
				const char* realm = tnet_stun_message_get_realm(response);
				const char* nonce = tnet_stun_message_get_nonce(response);

				if(code == 401 && realm && nonce){
					if(!binding->nonce){
						/* First time we get a nonce */
						tsk_strupdate(&binding->nonce, nonce);
						tsk_strupdate(&binding->realm, realm);

						/* Delete the message and response before retrying*/
						TSK_OBJECT_SAFE_FREE(response);
						TSK_OBJECT_SAFE_FREE(request);

						// Send again using new transaction identifier
						return tnet_stun_send_bind(context, binding);
					}
					else{
						ret = -3;
					}
				}
				else{
					ret = -2;
				}
			}
			else{
				const tnet_stun_attribute_t *attribute;
				if((attribute= tnet_stun_message_get_attribute(response, stun_xor_mapped_address))){
					ret = 0;
					binding->xmaddr = tsk_object_ref((void*)attribute);
				}
				else if((attribute= tnet_stun_message_get_attribute(response, stun_mapped_address))){
					ret = 0;
					binding->maddr = tsk_object_ref((void*)attribute);
				}
			}
		}
	} 
	/* END OF stun_phase0 */
	
bail:
	TSK_OBJECT_SAFE_FREE(response);
	TSK_OBJECT_SAFE_FREE(request);

	return ret;
}

/**@ingroup tnet_stun_group
 *
 * Public function to create a binding context.
 *
 * @param [in,out]	nat_context	The NAT context. 
 * @param	localFD				The local file descriptor for which to create the binding context. 
 *
 * @return	A valid binding id if succeed and @ref TNET_STUN_INVALID_BINDING_ID otherwise. If the returned id is valid then
 *			the newly created binding will contain the server reflexive address associated to the local file descriptor.
**/
tnet_stun_binding_id_t tnet_stun_bind(const tnet_nat_context_t* nat_context, tnet_fd_t localFD)
{
	tnet_stun_binding_id_t id = TNET_STUN_INVALID_BINDING_ID;

	tnet_stun_binding_t *binding = 0;

	if(nat_context && localFD != TNET_INVALID_FD){
		if(!(binding = tnet_stun_binding_create(localFD, nat_context->socket_type, nat_context->server_address, nat_context->server_port, nat_context->username, nat_context->password))){
			goto bail;
		}

		if(tnet_stun_send_bind(nat_context, binding)){
			TSK_OBJECT_SAFE_FREE(binding);
			goto bail;
		}

		id = binding->id;
		tsk_list_push_back_data(nat_context->stun_bindings, (void**)&binding);
	}

bail:
	return id;
}

/**@ingroup tnet_stun_group
 * Compares two transaction ids.
 *
 * @param	id1	The first transaction identifier. 
 * @param	id2	The second transaction  identifier. 
 *
 * @return	Zero if the two identifiers are equal and non-zero value otherwise.
**/
int tnet_stun_transacid_cmp(const tnet_stun_transacid_t id1, const tnet_stun_transacid_t id2)
{
	tsk_size_t i;
	for(i=0; i<sizeof(tnet_stun_transacid_t); i++){
		if(id1[i] != id2[i]){
			return (id1[i] - id2[i]);
		}
	}
	return 0;
}










//=================================================================================================
//	STUN2 BINDING object definition
//
static tsk_object_t* tnet_stun_binding_ctor(tsk_object_t * self, va_list * app)
{
	tnet_stun_binding_t *binding = self;
	if(binding){
		static tnet_stun_binding_id_t __binding_unique_id = 0;

		const char* server_address;
		tnet_port_t server_port;

		binding->id = ++__binding_unique_id;

		binding->localFD = va_arg(*app, tnet_fd_t);
		binding->socket_type = va_arg(*app, tnet_socket_type_t);
	
		server_address = tsk_strdup(va_arg(*app, const char*));
#if defined(__GNUC__)
		server_port = (tnet_port_t)va_arg(*app, unsigned);
#else
		server_port = va_arg(*app, tnet_port_t);
#endif

		binding->username = tsk_strdup(va_arg(*app, const char*));
		binding->password = tsk_strdup(va_arg(*app, const char*));
		
		tnet_sockaddr_init(server_address, server_port, binding->socket_type, &binding->server);		

		binding->software = tsk_strdup(TNET_SOFTWARE);
	}
	return self;
}

static tsk_object_t* tnet_stun_binding_dtor(tsk_object_t * self)
{ 
	tnet_stun_binding_t *binding = self;
	if(binding){
		TSK_FREE(binding->username);
		TSK_FREE(binding->password);
		TSK_FREE(binding->realm);
		TSK_FREE(binding->nonce);

		TSK_FREE(binding->software);

		TSK_OBJECT_SAFE_FREE(binding->maddr);
		TSK_OBJECT_SAFE_FREE(binding->xmaddr);
	}

	return self;
}

static const tsk_object_def_t tnet_stun_binding_def_s = 
{
	sizeof(tnet_stun_binding_t),
	tnet_stun_binding_ctor, 
	tnet_stun_binding_dtor,
	tsk_null, 
};
const tsk_object_def_t *tnet_stun_binding_def_t = &tnet_stun_binding_def_s;


