/*
* Copyright (C) 2010-2011 Mamadou Diop.
*
* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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_transport.c
 * @brief Network transport layer.
 *
 * <h2>10.2	Tansport</h2>
 * A transport layer always has a master socket which determine what kind of network traffic we expect (stream or dgram). 
 * Stream transport can manage TCP, TLS and SCTP sockets. Datagram socket can only manage UDP sockets. <br>
 * A transport can hold both IPv4 and IPv6 sockets.
 * @author Mamadou Diop <diopmamadou(at)doubango[dot]org>
 *

 */
#include "tnet_transport.h"
#include "tls/tnet_tls.h"
#include "tls/tnet_dtls.h"

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

#include <string.h> /* memcpy, ...(<#void * #>, <#const void * #>, <#tsk_size_t #>) */

#ifndef TNET_CIPHER_LIST
#	define TNET_CIPHER_LIST "ALL"
#endif

extern int tnet_transport_prepare(tnet_transport_t *transport);
extern int tnet_transport_unprepare(tnet_transport_t *transport);
extern void* TSK_STDCALL tnet_transport_mainthread(void *param);
extern int tnet_transport_stop(tnet_transport_t *transport);

static void* TSK_STDCALL run(void* self);
static int _tnet_transport_dtls_cb(const void* usrdata, tnet_dtls_socket_event_type_t e, const tnet_dtls_socket_handle_t* handle, const void* data, tsk_size_t size);

static int _tnet_transport_ssl_init(tnet_transport_t* transport)
{
	if(!transport){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}
#if HAVE_OPENSSL
	{
		tnet_socket_type_t type = tnet_transport_get_type(transport);
		tsk_bool_t is_tls = (TNET_SOCKET_TYPE_IS_TLS(type) || TNET_SOCKET_TYPE_IS_WSS(type));
		tsk_bool_t is_dtls = TNET_SOCKET_TYPE_IS_DTLS(type);
		if(is_dtls && !tnet_dtls_is_supported()){
			TSK_DEBUG_ERROR("Requesting to create DTLS transport but source code not built with support for this feature");
			return -1;
		}
		if(is_tls && !tnet_tls_is_supported()){
			TSK_DEBUG_ERROR("Requesting to create TLS transport but source code not built with support for this feature");
			return -1;
		}
		if((transport->tls.enabled = is_tls)){
			if(!transport->tls.ctx_client && !(transport->tls.ctx_client = SSL_CTX_new(SSLv23_client_method()))){
				TSK_DEBUG_ERROR("Failed to create SSL client context");
				return -2;
			}
			if(!transport->tls.ctx_server && !(transport->tls.ctx_server = SSL_CTX_new(SSLv23_server_method()))){
				TSK_DEBUG_ERROR("Failed to create SSL server context");
				return -3;
			}
			SSL_CTX_set_mode(transport->tls.ctx_client, SSL_MODE_AUTO_RETRY);
			SSL_CTX_set_mode(transport->tls.ctx_server, SSL_MODE_AUTO_RETRY);
			SSL_CTX_set_verify(transport->tls.ctx_server, SSL_VERIFY_NONE, tsk_null); // to be updated by tnet_transport_tls_set_certs()
			SSL_CTX_set_verify(transport->tls.ctx_client, SSL_VERIFY_NONE, tsk_null); // to be updated by tnet_transport_tls_set_certs()
			if(SSL_CTX_set_cipher_list(transport->tls.ctx_client, TNET_CIPHER_LIST) <= 0 || SSL_CTX_set_cipher_list(transport->tls.ctx_server, TNET_CIPHER_LIST) <= 0){
				TSK_DEBUG_ERROR("SSL_CTX_set_cipher_list failed [%s]", ERR_error_string(ERR_get_error(), tsk_null));
				return -4;
			}
		}
#if HAVE_OPENSSL_DTLS
		if((transport->dtls.enabled = is_dtls)){
			if(!transport->dtls.ctx && !(transport->dtls.ctx = SSL_CTX_new(DTLSv1_method()))){
				TSK_DEBUG_ERROR("Failed to create DTLSv1 context");
				TSK_OBJECT_SAFE_FREE(transport);
				return -5;
			}
			SSL_CTX_set_mode(transport->dtls.ctx, SSL_MODE_AUTO_RETRY);
			SSL_CTX_set_verify(transport->dtls.ctx, SSL_VERIFY_NONE, tsk_null); // to be updated by tnet_transport_tls_set_certs()
			if(SSL_CTX_set_cipher_list(transport->dtls.ctx, TNET_CIPHER_LIST) <= 0){
				TSK_DEBUG_ERROR("SSL_CTX_set_cipher_list failed [%s]", ERR_error_string(ERR_get_error(), tsk_null));
				return -6;
			}
		}
#endif /* HAVE_OPENSSL_DTLS */
	}
#endif /* HAVE_OPENSSL */

	return 0;
}

static int _tnet_transport_ssl_deinit(tnet_transport_t* transport)
{
	if(!transport){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}
#if HAVE_OPENSSL
	if(transport->tls.ctx_client){
		SSL_CTX_free(transport->tls.ctx_client);
		transport->tls.ctx_client = tsk_null;
	}
	if(transport->tls.ctx_server){
		SSL_CTX_free(transport->tls.ctx_server);
		transport->tls.ctx_server = tsk_null;
	}
	if(transport->dtls.ctx){
		SSL_CTX_free(transport->dtls.ctx);
		transport->dtls.ctx = tsk_null;
	}
#endif /* HAVE_OPENSSL */
	return 0;
}

tnet_transport_t* tnet_transport_create(const char* host, tnet_port_t port, tnet_socket_type_t type, const char* description)
{		
	tnet_transport_t* transport;

	if((transport = tsk_object_new(tnet_transport_def_t))){
		transport->description = tsk_strdup(description);
		transport->local_host = tsk_strdup(host);
		transport->req_local_port = port;
		transport->type = type;
		transport->context = tnet_transport_context_create();
		
		if((transport->master = tnet_socket_create(transport->local_host, transport->req_local_port, transport->type))){
			transport->local_ip = tsk_strdup(transport->master->ip);
			transport->bind_local_port = transport->master->port;
		}
		else{
			TSK_DEBUG_ERROR("Failed to create master socket");
			TSK_OBJECT_SAFE_FREE(transport);
		}

		if(_tnet_transport_ssl_init(transport) != 0){
			TSK_DEBUG_ERROR("Failed to initialize TLS and/or DTLS caps");
			TSK_OBJECT_SAFE_FREE(transport);
		}
		// set priority
		tsk_runnable_set_priority(TSK_RUNNABLE(transport), TSK_THREAD_PRIORITY_TIME_CRITICAL);
	}

	return transport;
}

tnet_transport_t* tnet_transport_create_2(tnet_socket_t *master, const char* description)
{
	tnet_transport_t* transport;
	if(!master){
		TSK_DEBUG_ERROR("Invalid parameter");
		return tsk_null;
	}

	if((transport = tsk_object_new(tnet_transport_def_t))){
		transport->description = tsk_strdup(description);
		transport->local_host = tsk_strdup(master->ip);
		transport->req_local_port = master->port;
		transport->type = master->type;
		
		transport->master = tsk_object_ref(master);
		transport->local_ip = tsk_strdup(transport->master->ip);
		transport->bind_local_port = transport->master->port;

		transport->context = tnet_transport_context_create();

		if(_tnet_transport_ssl_init(transport) != 0){
			TSK_DEBUG_ERROR("Failed to initialize TLS and/or DTLS caps");
			TSK_OBJECT_SAFE_FREE(transport);
		}

		// set priority
		tsk_runnable_set_priority(TSK_RUNNABLE(transport), TSK_THREAD_PRIORITY_TIME_CRITICAL);
	}

	return transport;
}

tnet_transport_event_t* tnet_transport_event_create(tnet_transport_event_type_t type, const void* callback_data, tnet_fd_t fd)
{
	return tsk_object_new(tnet_transport_event_def_t, type, callback_data, fd);
}

int tnet_transport_tls_set_certs(tnet_transport_handle_t *handle, const char* ca, const char* pbk, const char* pvk, tsk_bool_t verify)
{
	tnet_transport_t *transport = handle;
	static const char* ssl_password = tsk_null;
	
	if(!transport){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}

	tsk_strupdate(&transport->tls.ca, ca);
	tsk_strupdate(&transport->tls.pvk, pvk);
	tsk_strupdate(&transport->tls.pbk, pbk);
	transport->tls.verify = verify;

#if HAVE_OPENSSL
	{
		int32_t i, ret;
		SSL_CTX* contexts[3] = { tsk_null };

		/* init DTLS/TLS contexts */
		if((ret = _tnet_transport_ssl_init(transport))){
			return ret;
		}

		if(transport->tls.enabled){
			contexts[0] = transport->tls.ctx_client;
			contexts[1] = transport->tls.ctx_server;
		}
		if(transport->dtls.enabled){
			contexts[2] = transport->dtls.ctx;
			/* Reset fingerprints */
			memset(transport->dtls.fingerprints, 0, sizeof(transport->dtls.fingerprints));
		}

		for(i = 0; i < sizeof(contexts)/sizeof(contexts[0]); ++i){
			if(!contexts[i]){
				continue;
			}
			SSL_CTX_set_verify(contexts[i], transport->tls.verify ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT) : SSL_VERIFY_NONE, tsk_null);
			if(!tsk_strnullORempty(transport->tls.ca) || !tsk_strnullORempty(transport->tls.pvk) || !tsk_strnullORempty(transport->tls.ca)){
				/* Sets Public key (cert) */
				if(!tsk_strnullORempty(transport->tls.pbk) && (ret = SSL_CTX_use_certificate_file(contexts[i], transport->tls.pbk, SSL_FILETYPE_PEM)) != 1) {
					TSK_DEBUG_ERROR("SSL_CTX_use_certificate_file failed [%d,%s]", ret, ERR_error_string(ERR_get_error(), tsk_null));
					return -3;
				}
				/*Sets the password of the private key*/
				if(!tsk_strnullORempty(ssl_password)){
					SSL_CTX_set_default_passwd_cb_userdata(contexts[i], (void*)ssl_password);
				}

				/* Sets Private key (cert) */
				if (!tsk_strnullORempty(transport->tls.pvk) && (ret = SSL_CTX_use_PrivateKey_file(contexts[i], transport->tls.pvk, SSL_FILETYPE_PEM)) != 1) {
					TSK_DEBUG_ERROR("SSL_CTX_use_PrivateKey_file failed [%d,%s]", ret, ERR_error_string(ERR_get_error(), tsk_null));
					return -4;
				}
				/* Checks private key */
				if(!tsk_strnullORempty(transport->tls.pvk) && SSL_CTX_check_private_key(contexts[i]) == 0) {
					TSK_DEBUG_ERROR("SSL_CTX_check_private_key failed [%d,%s]", ret, ERR_error_string(ERR_get_error(), tsk_null));
					return -5;
				}
				/* Sets trusted CAs and CA file */
				if(!tsk_strnullORempty(transport->tls.ca) && (ret = SSL_CTX_load_verify_locations(contexts[i], transport->tls.ca, /*tlsdir_cas*/tsk_null)) != 1) {
				   TSK_DEBUG_ERROR("SSL_CTX_load_verify_locations failed [%d, %s]", ret, ERR_error_string(ERR_get_error(), tsk_null));
				   return -5;
				}
			}
		}
	}
#endif /* HAVE_OPENSSL */

	return 0;
}

int tnet_transport_start(tnet_transport_handle_t* handle)
{
	int ret = -1;
	if(handle){
		tnet_transport_t *transport = handle;
				
		/* prepare transport */
		if((ret = tnet_transport_prepare(transport))){
			TSK_DEBUG_ERROR("Failed to prepare transport.");
			goto bail;
		}
		
		/* start transport */
		TSK_RUNNABLE(transport)->run = run;
		if((ret = tsk_runnable_start(TSK_RUNNABLE(transport), tnet_transport_event_def_t))){
			TSK_DEBUG_ERROR("Failed to start transport.");
			goto bail;
		}
	}
	else{
		TSK_DEBUG_ERROR("NULL transport object.");
	}

bail:
	return ret;
}

int tnet_transport_issecure(const tnet_transport_handle_t *handle)
{
	if(handle)
	{
		const tnet_transport_t *transport = handle;
		if(transport->master){
			return TNET_SOCKET_TYPE_IS_SECURE(transport->master->type);
		}
	}
	else{
		TSK_DEBUG_ERROR("NULL transport object.");
	}
	return 0;
}

const char* tnet_transport_get_description(const tnet_transport_handle_t *handle)
{
	if(handle){
		const tnet_transport_t *transport = handle;
		return transport->description;
	}
	else{
		TSK_DEBUG_ERROR("NULL transport object.");
		return tsk_null;
	}
}

int tnet_transport_get_ip_n_port(const tnet_transport_handle_t *handle, tnet_fd_t fd, tnet_ip_t *ip, tnet_port_t *port)
{
	if(handle){
		return tnet_get_ip_n_port(fd, tsk_true/*local*/, ip, port);
	}
	else{
		TSK_DEBUG_ERROR("NULL transport object.");
	}
	return -1;
}

int tnet_transport_get_ip_n_port_2(const tnet_transport_handle_t *handle, tnet_ip_t *ip, tnet_port_t *port)
{
	const tnet_transport_t *transport = handle;
	if(transport){
		// do not check the master, let the application die if "null"
		if(ip){
			memcpy(*ip, transport->master->ip, sizeof(transport->master->ip));
		}
		if(port){
			*port = transport->master->port;
		}
		return 0;
	}
	else{
		TSK_DEBUG_ERROR("NULL transport object.");
		return -1;
	}
}

int tnet_transport_set_natt_ctx(tnet_transport_handle_t *handle, tnet_nat_context_handle_t* natt_ctx)
{
	tnet_transport_t *transport = handle;

	if(transport && natt_ctx){
		TSK_OBJECT_SAFE_FREE(transport->natt_ctx); // delete old
		transport->natt_ctx = tsk_object_ref(natt_ctx);
		return 0;
	}
	else{
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}
}

int tnet_transport_get_public_ip_n_port(const tnet_transport_handle_t *handle, tnet_fd_t fd, tnet_ip_t *ip, tnet_port_t *port)
{
	tsk_bool_t stun_ok = tsk_false;
	tnet_nat_context_handle_t* natt_ctx;
	const tnet_transport_t *transport = handle;
	if(!transport){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}

	if(TNET_SOCKET_TYPE_IS_DGRAM(transport->type) && (natt_ctx = tsk_object_ref(transport->natt_ctx))){
		tnet_stun_binding_id_t bind_id = TNET_STUN_INVALID_BINDING_ID;
		// if the socket is already monitored by the transport we should pause because both the transport and
		// NAT binder will try to read from it
		
		// Pause the soket
		tnet_transport_pause_socket(transport, fd, tsk_true);
		// Performs STUN binding
		bind_id = tnet_nat_stun_bind(transport->natt_ctx, fd);
		// Resume the socket
		tnet_transport_pause_socket(transport, fd, tsk_false);
		
		if(TNET_STUN_IS_VALID_BINDING_ID(bind_id)){
			char* public_ip = tsk_null;
			if(tnet_nat_stun_get_reflexive_address(transport->natt_ctx, bind_id, &public_ip, port) == 0){
				if(ip && public_ip){
					tsk_size_t ip_len = tsk_strlen(public_ip);
					memcpy(ip, public_ip, ip_len> sizeof(*ip)?sizeof(*ip):ip_len);
				}
				stun_ok = tsk_true;
			}
			TSK_FREE(public_ip);
			tnet_nat_stun_unbind(transport->natt_ctx, bind_id);
		}
		tsk_object_unref(natt_ctx);
	}

	if(!stun_ok){
		if(fd == TNET_INVALID_FD && transport->local_ip){
			memcpy(*ip, transport->local_ip, TSK_MIN(sizeof(tnet_ip_t), tsk_strlen(transport->local_ip)));
			*port = transport->bind_local_port;
			return 0;
		}
		else{
			return tnet_transport_get_ip_n_port(handle, fd, ip, port);
		}
	}
	
	return 0;
}

const char* tnet_transport_dtls_get_local_fingerprint(const tnet_transport_handle_t *handle, tnet_dtls_hash_type_t hash)
{
	const tnet_transport_t *transport = handle;

	if(!transport){
		TSK_DEBUG_ERROR("Invalid parameter");
		return tsk_null;
	}

	if(!transport->dtls.enabled){
		TSK_DEBUG_ERROR("DTLS not enabled on this transport");
		return tsk_null;
	}
	if(hash > sizeof(transport->dtls.fingerprints)/sizeof(transport->dtls.fingerprints[0])){
		TSK_DEBUG_ERROR("%d not valid for fingerprint hash", hash);
		return tsk_null;
	}
	if(tsk_strnullORempty(transport->tls.pbk)){
		TSK_DEBUG_ERROR("No certificate for which to get fingerprint");
		return tsk_null;
	}

	if(tnet_dtls_get_fingerprint(transport->tls.pbk, &((tnet_transport_t *)transport)->dtls.fingerprints[hash], hash) == 0){
		return (const char*)transport->dtls.fingerprints[hash];
	}
	return tsk_null;
}

/*
rfc5764: 4.1.  The use_srtp Extension
*/
int tnet_transport_dtls_use_srtp(tnet_transport_handle_t *handle, const char* srtp_profiles, struct tnet_socket_s** sockets, tsk_size_t sockets_count)
{
	tnet_transport_t *transport = handle;

	if(!transport || !srtp_profiles){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}
	if(!transport->dtls.enabled){
		TSK_DEBUG_ERROR("DTLS not enabled on this transport");
		return -2;
	}
#if HAVE_OPENSSL_DTLS_SRTP
	{
		tsk_size_t i;
		transport->dtls.use_srtp = tsk_true;
		SSL_CTX_set_tlsext_use_srtp(transport->dtls.ctx, srtp_profiles);
		if(sockets){
			for(i = 0; i < sockets_count; ++i){
				if(sockets[i] && sockets[i]->dtlshandle){
					tnet_dtls_socket_use_srtp(sockets[i]->dtlshandle);
				}
			}
		}
		return 0;
	}
#else
	TSK_DEBUG_ERROR("Your OpenSSL version do not support DTLS-SRTP");
	return -2;
#endif
}

int tnet_transport_dtls_set_remote_fingerprint(tnet_transport_handle_t *handle, const tnet_fingerprint_t* fingerprint, tnet_dtls_hash_type_t hash, struct tnet_socket_s** sockets, tsk_size_t sockets_count)
{
	const tnet_transport_t *transport = handle;

	if(!transport || !fingerprint){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}
	if(!transport->dtls.enabled){
		TSK_DEBUG_ERROR("DTLS not enabled on this transport");
		return -2;
	}
#if HAVE_OPENSSL_DTLS
	if(sockets){
		tsk_size_t i;
		for(i = 0; i < sockets_count; ++i){
			if(sockets[i] && sockets[i]->dtlshandle){
				tnet_dtls_socket_set_remote_fingerprint(sockets[i]->dtlshandle, fingerprint, hash);
			}
		}
	}
	return 0;
#else
	TSK_DEBUG_ERROR("Your OpenSSL version do not support DTLS");
	return -2;
#endif
}

tsk_bool_t tnet_transport_dtls_is_enabled(const tnet_transport_handle_t *handle)
{
	const tnet_transport_t *transport = handle;
	if(!transport){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}
	return transport->dtls.enabled;
}

/*
Enable or disable DTLS on the transport and all coresponding sockets
*@param handle The transport for which to enable or disable DTLS
*@param enabled Whether to enable or disable DTLS
*@param sockets List of all sockets for which to enable or disable DLS could be null. You should include the master socket in this list.
*@param sockets_count The number of sockets
*@return 0 if succeed, otherwise non-zero error code
*/
int tnet_transport_dtls_set_enabled(tnet_transport_handle_t *handle, tsk_bool_t enabled, struct tnet_socket_s** sockets, tsk_size_t sockets_count)
{
	tnet_transport_t *transport = handle;
	tnet_socket_type_t type;
	int ret;

	if(!transport){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}
	type = tnet_transport_get_type(transport);

	if(!TNET_SOCKET_TYPE_IS_DTLS(type) && !TNET_SOCKET_TYPE_IS_UDP(type)){
		TSK_DEBUG_ERROR("Trying to enable/disable DTLS on invalid transport type: %d", type);
		return -3;
	}

	if(enabled & !tnet_dtls_is_supported()){
		TSK_DEBUG_ERROR("Trying to enable DTLS but code source not built with this feature");
		return -1;
	}

	if((transport->dtls.enabled = enabled)){
		TNET_SOCKET_TYPE_SET_DTLS(transport->type);
		if((ret = _tnet_transport_ssl_init(transport))){
			return ret;
		}
	}
	else{
		TNET_SOCKET_TYPE_SET_UDP(transport->type);
		ret = _tnet_transport_ssl_deinit(transport);
	}

	if(sockets && sockets_count){
		tsk_size_t i;
		for(i = 0; i < sockets_count; ++i){
			if(!sockets[i]){
				continue;
			}
			if(enabled){
				if(!sockets[i]->dtlshandle){
					if(!(sockets[i]->dtlshandle = tnet_dtls_socket_create(sockets[i]->fd, transport->dtls.ctx))){
						return -4;
					}
				}
				if(transport->dtls.use_srtp){
					tnet_dtls_socket_use_srtp(sockets[i]->dtlshandle);
				}
				TNET_SOCKET_TYPE_SET_DTLS(sockets[i]->type);
				tnet_dtls_socket_set_callback(sockets[i]->dtlshandle, transport, _tnet_transport_dtls_cb);
			}
			else{
				TSK_OBJECT_SAFE_FREE(sockets[i]->dtlshandle);
				TNET_SOCKET_TYPE_SET_UDP(sockets[i]->type);
			}
		}
	}
	
	return ret;
}

int tnet_transport_dtls_set_setup(tnet_transport_handle_t* handle, tnet_dtls_setup_t setup, struct tnet_socket_s** sockets, tsk_size_t sockets_count)
{
	tnet_transport_t *transport = handle;

	if(!transport){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}

	if(!transport->dtls.enabled){
		TSK_DEBUG_ERROR("DTLS not enabled on this transport");
		return -2;
	}
	if(sockets && sockets_count){
		tsk_size_t i;
		for(i = 0; i < sockets_count; ++i){
			if(!sockets[i] || !sockets[i]->dtlshandle){
				continue;
			}
			tnet_dtls_socket_set_setup(sockets[i]->dtlshandle, setup);
		}
	}
	return 0;
}

int tnet_transport_dtls_do_handshake(tnet_transport_handle_t *handle, struct tnet_socket_s** sockets, tsk_size_t sockets_count, const struct sockaddr_storage** remote_addrs, tsk_size_t remote_addrs_count)
{
	tnet_transport_t *transport = handle;
	tsk_size_t i;

	if(!transport || !sockets){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}

	if(!transport->dtls.enabled){
		TSK_DEBUG_ERROR("DTLS not enabled on this transport");
		return -2;
	}

	if(sockets){
		int ret;
		for(i = 0; i < sockets_count; ++i){
			if(sockets[i] && sockets[i]->dtlshandle){
				if((ret = tnet_dtls_socket_do_handshake(sockets[i]->dtlshandle, 
					(remote_addrs && i < remote_addrs_count) ? remote_addrs[i] : tsk_null)) != 0){
						return ret;
				}
			}
		}
	}

	return 0;
}

tnet_socket_type_t tnet_transport_get_type(const tnet_transport_handle_t *handle)
{
	if(!handle){
		TSK_DEBUG_ERROR("Invalid parameter");
		return tnet_socket_type_invalid;
	}
	return ((const tnet_transport_t *)handle)->type;
}

tnet_fd_t tnet_transport_get_master_fd(const tnet_transport_handle_t *handle)
{
	if(!handle){
		TSK_DEBUG_ERROR("Invalid parameter");
		return TNET_INVALID_FD;
	}
	return ((const tnet_transport_t *)handle)->master ? ((const tnet_transport_t *)handle)->master->fd : TNET_INVALID_FD;
}

/**
* Connects a socket.
* @param handle The transport to use to connect() the socket. The new socket will be managed by this transport.
* @param host The remote @a host to connect() to.
* @param port The remote @a port to connect() to.
* @param type The type of the socket to use to connect() to the remote @a host.
* @retval The newly connected socket. For non-blocking sockets you should use @ref tnet_sockfd_waitUntilWritable to check
* the socket for writability.
* @sa tnet_sockfd_waitUntilWritable.
*/
tnet_fd_t tnet_transport_connectto(const tnet_transport_handle_t *handle, const char* host, tnet_port_t port, tnet_socket_type_t type)
{
	tnet_transport_t *transport = (tnet_transport_t*)handle;
	struct sockaddr_storage to;
	int status = -1;
	tnet_fd_t fd = TNET_INVALID_FD;
	tnet_tls_socket_handle_t* tls_handle = tsk_null;
	
	if(!transport || !transport->master){
		TSK_DEBUG_ERROR("Invalid transport handle");
		goto bail;
	}
	
	if((TNET_SOCKET_TYPE_IS_STREAM(transport->master->type) && !TNET_SOCKET_TYPE_IS_STREAM(type)) ||
		(TNET_SOCKET_TYPE_IS_DGRAM(transport->master->type) && !TNET_SOCKET_TYPE_IS_DGRAM(type))){
		TSK_DEBUG_ERROR("Master/destination types mismatch [%u/%u]", transport->master->type, type);
		goto bail;
	}

	/* Init destination sockaddr fields */
	if((status = tnet_sockaddr_init(host, port, type, &to))){
		TSK_DEBUG_ERROR("Invalid HOST/PORT [%s/%u]", host, port);
		goto bail;
	}
	else if(TNET_SOCKET_TYPE_IS_IPV46(type)){
		/* Update the type (unambiguously) */
		if(to.ss_family == AF_INET6){
			TNET_SOCKET_TYPE_SET_IPV6Only(type);
		}
		else{
			TNET_SOCKET_TYPE_SET_IPV4Only(type);
		}
	}
	
	/*
	* STREAM ==> create new socket and connect it to the remote host.
	* DGRAM ==> connect the master to the remote host.
	*/
	if(TNET_SOCKET_TYPE_IS_STREAM(type)){		
		/* Create client socket descriptor. */
		if((status = tnet_sockfd_init(transport->local_host, TNET_SOCKET_PORT_ANY, type, &fd))){
			TSK_DEBUG_ERROR("Failed to create new sockfd.");
			goto bail;
		}
	}
	else{
		fd = transport->master->fd;
	}
	
	if((status = tnet_sockfd_connectto(fd, (const struct sockaddr_storage *)&to))){
		if(fd != transport->master->fd){
			tnet_sockfd_close(&fd);
		}
		goto bail;
	}
	else{
        static const tsk_bool_t __isClient = tsk_true;
        static const tsk_bool_t __takeOwnership = tsk_true;
		if(TNET_SOCKET_TYPE_IS_TLS(type) || TNET_SOCKET_TYPE_IS_WSS(type)){
#if HAVE_OPENSSL
			tls_handle = tnet_tls_socket_create(fd, transport->tls.ctx_client);     
			if((status = tnet_tls_socket_connect(tls_handle))){
				tnet_sockfd_close(&fd);
				goto bail;
			}
#endif
		}
        /* Add the socket */
        // socket must be added after connect() otherwise many Linux systems when return POLLHUP as the fd is not active yet
        if((status = tnet_transport_add_socket(handle, fd, type, __takeOwnership, __isClient, tls_handle))){
            TNET_PRINT_LAST_ERROR("Failed to add new socket");
            tnet_sockfd_close(&fd);
            goto bail;
        }
	}
	
bail:
	TSK_OBJECT_SAFE_FREE(tls_handle);
	return fd;
}

int tnet_transport_set_callback(const tnet_transport_handle_t *handle, tnet_transport_cb_f callback, const void* callback_data)
{
	tnet_transport_t *transport = (tnet_transport_t*)handle;
	int ret = -1;
	
	if(!transport){
		TSK_DEBUG_ERROR("Invalid server handle.");
		return ret;
	}

	transport->callback = callback;
	transport->callback_data = callback_data;
	return 0;
}


int tnet_transport_shutdown(tnet_transport_handle_t* handle)
{
	if(handle){
		int ret;
		if((ret = tnet_transport_stop(handle)) == 0){
			ret = tnet_transport_unprepare(handle);
		}
		return ret;
	}
	else{
		TSK_DEBUG_ERROR("NULL transport object.");
		return -1;
	}
}


static int _tnet_transport_dtls_cb(const void* usrdata, tnet_dtls_socket_event_type_t dtls_e, const tnet_dtls_socket_handle_t* handle, const void* data, tsk_size_t size)
{
	tnet_transport_t *transport = (tnet_transport_t*)usrdata;
	if(transport){
		tnet_transport_event_type_t t_e;
		const struct sockaddr_storage* remote_addr;
		tnet_fd_t fd;
		tnet_transport_event_t* e;

		switch(dtls_e){
			case tnet_dtls_socket_event_type_handshake_started: t_e = event_dtls_handshake_started; break;
			case tnet_dtls_socket_event_type_handshake_succeed: t_e = event_dtls_handshake_succeed; break;
			case tnet_dtls_socket_event_type_handshake_failed: t_e = event_dtls_handshake_failed; break;
			case tnet_dtls_socket_event_type_fingerprint_mismatch: t_e = event_dtls_fingerprint_mismatch; break;
			case tnet_dtls_socket_event_type_dtls_srtp_profile_selected: t_e = event_dtls_srtp_profile_selected; break;
			case tnet_dtls_socket_event_type_dtls_srtp_data: t_e = event_dtls_srtp_data; break;
			case tnet_dtls_socket_event_type_error: t_e = event_dtls_error; break;
			default: TSK_DEBUG_ERROR("DTLS event = %d ignored", dtls_e); return -1;
		}	
		remote_addr = tnet_dtls_socket_get_remote_addr(handle);
		fd = tnet_dtls_socket_get_fd(handle);
		if((e = tnet_transport_event_create(t_e, transport->callback_data, fd))){
			if(data && size && (e ->data = tsk_malloc(size))){
				memcpy(e ->data, data, size);
				e->size = size;
			}
			if(remote_addr){
				e->remote_addr = *remote_addr;
			}
			TSK_RUNNABLE_ENQUEUE_OBJECT_SAFE(TSK_RUNNABLE(transport), e);
			return 0;
		}
	}
	return -1;
}


/*
* Runnable interface implementation.
*/
static void* TSK_STDCALL run(void* self)
{
	int ret = 0;
	tsk_list_item_t *curr;
	tnet_transport_t *transport = self;
	
	TSK_DEBUG_INFO("Transport::run() - enter");

	/* create main thread */
	if((ret = tsk_thread_create(transport->mainThreadId, tnet_transport_mainthread, transport))){ /* More important than "tsk_runnable_start" ==> start it first. */
		TSK_FREE(transport->context); /* Otherwise (tsk_thread_create is ok) will be freed when mainthread exit. */
		TSK_DEBUG_FATAL("Failed to create main thread [%d]", ret);
		return tsk_null;
	}
	/* set thread priority 
     iOS: no incoming pkts (STUN, rtp, dtls...) when thread priority is changed -> to be checked
     */
#if !TNET_UNDER_IPHONE && !TNET_UNDER_IPHONE_SIMULATOR
	ret = tsk_thread_set_priority(transport->mainThreadId[0], TSK_THREAD_PRIORITY_TIME_CRITICAL);
#endif
	
	TSK_RUNNABLE_RUN_BEGIN(transport);
	
	if((curr = TSK_RUNNABLE_POP_FIRST_SAFE(TSK_RUNNABLE(transport)))){
		const tnet_transport_event_t *e = (const tnet_transport_event_t*)curr->data;
		
		if(transport->callback){
			transport->callback(e);
		}
		tsk_object_unref(curr);
	}
	
	TSK_RUNNABLE_RUN_END(transport);

	TSK_DEBUG_INFO("Transport::run(%s) - exit", transport->description);

	return tsk_null;
}




//=================================================================================================
//	Transport object definition
//
static tsk_object_t* tnet_transport_ctor(tsk_object_t * self, va_list * app)
{
	tnet_transport_t *transport = self;
	if(transport){
	}
	return self;
}

static tsk_object_t* tnet_transport_dtor(tsk_object_t * self)
{ 
	tnet_transport_t *transport = self;
	if(transport){
		tnet_transport_set_callback(transport, tsk_null, tsk_null);
		tnet_transport_shutdown(transport);
		TSK_OBJECT_SAFE_FREE(transport->master);
		TSK_OBJECT_SAFE_FREE(transport->context);
		TSK_OBJECT_SAFE_FREE(transport->natt_ctx);
		TSK_FREE(transport->description);
		TSK_FREE(transport->local_ip);
		TSK_FREE(transport->local_host);

		// (tls and dtls) = ssl
		TSK_FREE(transport->tls.ca);
		TSK_FREE(transport->tls.pbk);
		TSK_FREE(transport->tls.pvk);
		_tnet_transport_ssl_deinit(transport); // openssl contexts
	}

	return self;
}

static const tsk_object_def_t tnet_transport_def_s = 
{
	sizeof(tnet_transport_t),
	tnet_transport_ctor, 
	tnet_transport_dtor,
	tsk_null, 
};
const tsk_object_def_t *tnet_transport_def_t = &tnet_transport_def_s;



//=================================================================================================
//	Transport event object definition
//
static tsk_object_t* tnet_transport_event_ctor(tsk_object_t * self, va_list * app)
{
	tnet_transport_event_t *e = self;
	if(e){
		e->type = va_arg(*app, tnet_transport_event_type_t);
		e->callback_data = va_arg(*app, const void*);
		e->local_fd = va_arg(*app, tnet_fd_t);	
	}
	return self;
}

static tsk_object_t* tnet_transport_event_dtor(tsk_object_t * self)
{ 
	tnet_transport_event_t *e = self;
	if(e){
		TSK_FREE(e->data);
	}

	return self;
}

static const tsk_object_def_t tnet_transport_event_def_s = 
{
	sizeof(tnet_transport_event_t),
	tnet_transport_event_ctor, 
	tnet_transport_event_dtor,
	0, 
};
const tsk_object_def_t *tnet_transport_event_def_t = &tnet_transport_event_def_s;

