#if HAVE_CRT
#define _CRTDBG_MAP_ALLOC 
#include <stdlib.h> 
#include <crtdbg.h>
#endif //HAVE_CRT
/*
* Copyright (C) 2017, University of the Basque Country (UPV/EHU)
* Contact for licensing options: <licensing-mcpttclient(at)mcopenplatform(dot)com>
*
* The original file was part of Open Source Doubango Framework
* Copyright (C) 2010-2011 Mamadou Diop.
* Copyright (C) 2012 Doubango Telecom <http://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_dtls.c
 * @brief DTLS utilitity functions, based on openssl.
 */
#include "tnet_dtls.h"
#include "tnet_tls.h"
#include "tnet_utils.h"

#include "tsk_object.h"
#include "tsk_string.h"
#include "tsk_memory.h"
#include "tsk_time.h"
#include "tsk_safeobj.h"
#include "tsk_debug.h"

typedef struct tnet_dtls_socket_s
{
	TSK_DECLARE_OBJECT;

	struct tnet_socket_s* wrapped_sock; /* not owner: do not try to close */
	tsk_bool_t verify_peer;
	tsk_bool_t use_srtp;
	tsk_bool_t handshake_completed;
	tsk_bool_t handshake_started;
	tsk_bool_t handshake_storedata; // whether to store handshaking data or to send it to the remote party
	tnet_dtls_setup_t setup;

	struct {
		void* ptr;
		tsk_size_t size;
		tsk_size_t count;
	} handshake_data;

	struct{
		const void* usrdata;
		tnet_dtls_socket_cb_f func;
	} cb;

	struct{
		tnet_fingerprint_t fp;
		tnet_dtls_hash_type_t hash;
		struct sockaddr_storage addr;
	} remote;
	struct{
		tnet_fingerprint_t fp;
		tnet_dtls_hash_type_t hash;
	} local;

#if HAVE_OPENSSL
	SSL *ssl;
	BIO* rbio;
	BIO* wbio;
#endif

	TSK_DECLARE_SAFEOBJ;
}
tnet_dtls_socket_t;

#define _tnet_dtls_socket_do_handshake(self) tnet_dtls_socket_do_handshake(self, tsk_null)
#define _tnet_dtls_socket_raise_event(self, type, data, size) ((self) && (self)->cb.func ? (self)->cb.func((self)->cb.usrdata, (type), (self), (data), (size)) : 0)
#define _tnet_dtls_socket_raise_event_dataless(self, type) _tnet_dtls_socket_raise_event((self), (type), tsk_null, 0)

tsk_bool_t tnet_dtls_is_srtp_supported()
{
#if HAVE_OPENSSL_DTLS_SRTP
	return tsk_true;
#else
	return tsk_false;
#endif
}

tsk_bool_t tnet_dtls_is_supported()
{
#if HAVE_OPENSSL_DTLS
	return tsk_true;
#else
	return tsk_false;
#endif
}


#if HAVE_OPENSSL

static tsk_bool_t _tnet_dtls_is_fingerprint_matching(X509* cert, tnet_fingerprint_t* fingerprint, tnet_dtls_hash_type_t hash);

static int _tnet_dtls_verify_cert(int preverify_ok, X509_STORE_CTX *ctx)
{
	SSL *ssl;
	tnet_dtls_socket_t* socket;

	TSK_DEBUG_INFO("_tnet_dtls_verify_cert");

	ssl = X509_STORE_CTX_get_app_data(ctx);
	socket = (tnet_dtls_socket_t*)SSL_get_app_data(ssl);
	if (!ssl || !socket){
		TSK_DEBUG_ERROR("Not expected");
		return 0;
	}
	tsk_safeobj_lock(socket);
	if (_tnet_dtls_is_fingerprint_matching(ctx->cert, &socket->remote.fp, socket->remote.hash) == tsk_false) {
		TSK_DEBUG_ERROR("Failed to match fingerprint");
		tsk_safeobj_unlock(socket);
		return 0;
	}
	tsk_safeobj_unlock(socket);
	return 1;
}

static const EVP_MD *_tnet_dtls_get_hash_evp(tnet_dtls_hash_type_t hash)
{
	switch (hash){
	case tnet_dtls_hash_type_md5: return EVP_md5();
	case tnet_dtls_hash_type_sha1: return EVP_sha1();
	case tnet_dtls_hash_type_sha256: return EVP_sha256();
	case tnet_dtls_hash_type_sha512: return EVP_sha512();
	default: TSK_DEBUG_ERROR("Invalid parameter: %d not valid as hash type", hash); return tsk_null;
	}
}

static int _tnet_dtls_get_fingerprint(X509* cert, const EVP_MD *evp, tnet_fingerprint_t* fingerprint)
{
	if (!cert || !evp || !fingerprint){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}
	else{
		unsigned len = 0, i, j;
		tnet_fingerprint_t fp;

		if (X509_digest(cert, evp, fp, &len) != 1 || len <= 0){
			TSK_DEBUG_ERROR("X509_digest() failed [%s]", ERR_error_string(ERR_get_error(), tsk_null));
			return -2;
		}
		for (i = 0, j = 0; i < len; ++i, j += 3){
			sprintf((char*)&(*fingerprint)[j], (i == (len - 1)) ? "%.2X" : "%.2X:", fp[i]);
		}
		(*fingerprint)[len * 3] = '\0';
		return 0;
	}
}

static tsk_bool_t _tnet_dtls_is_fingerprint_matching(X509* cert, tnet_fingerprint_t* fingerprint, tnet_dtls_hash_type_t hash)
{
	const EVP_MD* evp;
	tnet_fingerprint_t fp;
	int ret;

	if (!cert || !fingerprint){
		TSK_DEBUG_ERROR("Invalid parameter");
		return tsk_false;
	}

	if (!(evp = _tnet_dtls_get_hash_evp(hash))){
		return tsk_false;
	}
	if ((ret = _tnet_dtls_get_fingerprint(cert, evp, &fp))){
		return tsk_false;
	}
	if (!tsk_striequals(fp, fingerprint)){
		TSK_DEBUG_ERROR("DTLS certificate fingerprints mismatch: [%s]#[%s]", fp, *fingerprint);
		return tsk_false;
	}
	return tsk_true;
}

static tsk_bool_t _tnet_dtls_socket_is_remote_cert_fp_match(tnet_dtls_socket_t* socket)
{
	if (!socket){
		TSK_DEBUG_ERROR("Invalid parameter");
		return tsk_false;
	}
	else if (socket->verify_peer){
		X509* cert;

		if (!(cert = SSL_get_peer_certificate(socket->ssl))){
			if (socket->verify_peer){ // print error only if verify certs is enabled
				TSK_DEBUG_ERROR("Failed to get peer certificate [%s]", ERR_error_string(ERR_get_error(), tsk_null));
			}
			return tsk_false;
		}
		if (!_tnet_dtls_is_fingerprint_matching(cert, &socket->remote.fp, socket->remote.hash)){
			X509_free(cert);
			return tsk_false;
		}
		X509_free(cert);

		if (SSL_get_verify_result(socket->ssl) != X509_V_OK){
			TSK_DEBUG_ERROR("SSL_get_verify_result()#X509_V_OK [%s]", ERR_error_string(ERR_get_error(), tsk_null));
			return tsk_false;
		}
	}

	return tsk_true;
}

#endif /* HAVE_OPENSSL */

tnet_dtls_hash_type_t tnet_dtls_get_hash_from_string(const char* hash)
{
	if (hash){
		int32_t i;
		for (i = 0; i < TNET_DTLS_HASH_TYPE_MAX; ++i){
			if (tsk_striequals(TNET_DTLS_HASH_NAMES[i], hash)){
				return (tnet_dtls_hash_type_t)i;
			}
		}
	}
	return tnet_dtls_hash_type_none;
}

tnet_dtls_setup_t tnet_dtls_get_setup_from_string(const char* setup)
{
	if (setup){
		int32_t i;
		for (i = 0; i < TNET_DTLS_SETUP_MAX; ++i){
			if (tsk_striequals(TNET_DTLS_SETUP_NAMES[i], setup)){
				return (tnet_dtls_setup_t)i;
			}
		}
	}
	return tnet_dtls_setup_none;
}

int tnet_dtls_get_fingerprint(const char* certfile, tnet_fingerprint_t* fingerprint, tnet_dtls_hash_type_t hash)
{
#if !HAVE_OPENSSL || !HAVE_OPENSSL_DTLS
	TSK_DEBUG_ERROR("OpenSSL or DTLS not enabled");
	return -200;
#else
	{
		X509* x509;
		BIO* bio;
		int ret = 0;
		const EVP_MD *evp;

		if (tsk_strnullORempty(certfile) || !fingerprint){
			TSK_DEBUG_ERROR("Invalid parameter");
			return -1;
		}

		if (!(evp = _tnet_dtls_get_hash_evp(hash))){
			return -1;
		}

		x509 = tsk_null;
		bio = tsk_null;

		if (!(bio = BIO_new(BIO_s_file()))){
			TSK_DEBUG_ERROR("BIO_new(BIO_s_file()) failed [%s]", ERR_error_string(ERR_get_error(), tsk_null));
			ret = -3;
			goto bail;
		}
		if (BIO_read_filename(bio, certfile) != 1){
			TSK_DEBUG_ERROR("BIO_read_filename(%s) failed [%s]", certfile, ERR_error_string(ERR_get_error(), tsk_null));
			ret = -4;
			goto bail;
		}
		if (!(x509 = PEM_read_bio_X509(bio, tsk_null, 0, tsk_null))){
			TSK_DEBUG_ERROR("PEM_read_bio() failed [%s]", ERR_error_string(ERR_get_error(), tsk_null));
			ret = -5;
			goto bail;
		}
		if ((ret = _tnet_dtls_get_fingerprint(x509, evp, fingerprint))){
			goto bail;
		}

	bail:
		if (bio){
			BIO_free_all(bio);
		}
		return ret;
	}
#endif
}

tnet_dtls_socket_handle_t* tnet_dtls_socket_create(struct tnet_socket_s* wrapped_sock, struct ssl_ctx_st* ssl_ctx)
{
#if !HAVE_OPENSSL || !HAVE_OPENSSL_DTLS
	TSK_DEBUG_ERROR("OpenSSL or DTLS not enabled");
	return tsk_null;
#else
	tnet_dtls_socket_t* socket;

	if (!wrapped_sock || !ssl_ctx){
		TSK_DEBUG_ERROR("Invalid parameter");
		return tsk_null;
	}
	if ((socket = tsk_object_new(tnet_dtls_socket_def_t))){
		socket->wrapped_sock = tsk_object_ref(wrapped_sock);
		if (!(socket->ssl = SSL_new(ssl_ctx))){
			TSK_DEBUG_ERROR("SSL_new(CTX) failed [%s]", ERR_error_string(ERR_get_error(), tsk_null));
			TSK_OBJECT_SAFE_FREE(socket);
			return tsk_null;
		}
		if (!(socket->rbio = BIO_new(BIO_s_mem())) || !(socket->wbio = BIO_new(BIO_s_mem()))){
			TSK_DEBUG_ERROR("BIO_new_socket(%d) failed [%s]", socket->wrapped_sock->fd, ERR_error_string(ERR_get_error(), tsk_null));
			if (socket->rbio){
				BIO_free(socket->rbio);
			}
			if (socket->wbio){
				BIO_free(socket->wbio);
			}
			TSK_OBJECT_SAFE_FREE(socket);
			return tsk_null;
		}
		BIO_set_mem_eof_return(socket->rbio, -1);
		BIO_set_mem_eof_return(socket->wbio, -1);
		SSL_set_bio(socket->ssl, socket->rbio, socket->wbio);
		SSL_set_mode(socket->ssl, SSL_MODE_AUTO_RETRY);
		SSL_set_read_ahead(socket->ssl, 1);

		if ((socket->verify_peer = (SSL_CTX_get_verify_mode(ssl_ctx) != SSL_VERIFY_NONE))){
			TSK_DEBUG_INFO("SSL cert verify: ON");
			socket->verify_peer = tsk_true;
			SSL_set_verify(socket->ssl, (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT), _tnet_dtls_verify_cert);
		}
		else {
			// FIXME:
			TSK_DEBUG_ERROR("????");
		}

		SSL_set_app_data(socket->ssl, socket);
	}
	return socket;
#endif
}

tnet_fd_t tnet_dtls_socket_get_fd(const tnet_dtls_socket_handle_t* handle)
{
	return handle ? ((const tnet_dtls_socket_t*)handle)->wrapped_sock->fd : TNET_INVALID_FD;
}

const struct sockaddr_storage* tnet_dtls_socket_get_remote_addr(const tnet_dtls_socket_handle_t* handle)
{
	return handle ? &((const tnet_dtls_socket_t*)handle)->remote.addr : tsk_null;
}

int tnet_dtls_socket_set_callback(tnet_dtls_socket_handle_t* handle, const void* usrdata, tnet_dtls_socket_cb_f func)
{
	tnet_dtls_socket_t* socket = handle;

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

	socket->cb.usrdata = usrdata;
	socket->cb.func = func;
	return 0;
}

int tnet_dtls_socket_set_remote_fingerprint(tnet_dtls_socket_handle_t* handle, const tnet_fingerprint_t* fingerprint, tnet_dtls_hash_type_t hash)
{
	tnet_dtls_socket_t* socket = handle;

	if (!socket || !fingerprint){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}

	memcpy(socket->remote.fp, &(*fingerprint)[0], sizeof(tnet_fingerprint_t));
	socket->remote.hash = hash;
	return 0;
}

/*
rfc5764: 4.1.  The use_srtp Extension
*/
int tnet_dtls_socket_use_srtp(tnet_dtls_socket_handle_t*handle)
{
#if !HAVE_OPENSSL || !HAVE_OPENSSL_DTLS || !HAVE_OPENSSL_DTLS_SRTP
	TSK_DEBUG_ERROR("OpenSSL or DTLS not enabled");
	return -200;
#else
	tnet_dtls_socket_t* socket = handle;
	if (!socket){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}

	if ((socket->use_srtp = tsk_true)){
		if (!socket->verify_peer){
			socket->verify_peer = tsk_true; // DTLS-SRTP requires certtificates
			SSL_set_verify(socket->ssl, (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT), _tnet_dtls_verify_cert);
		}
	}
	return 0;
#endif
}

int tnet_dtls_socket_set_setup(tnet_dtls_socket_handle_t* handle, tnet_dtls_setup_t setup)
{
#if !HAVE_OPENSSL || !HAVE_OPENSSL_DTLS
	TSK_DEBUG_ERROR("OpenSSL or DTLS not enabled");
	return -200;
#else
	tnet_dtls_socket_t* socket = handle;
	if (!socket){
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}

	switch ((socket->setup = setup)){
	case tnet_dtls_setup_passive:
		SSL_set_accept_state(socket->ssl);
		break;
	case tnet_dtls_setup_active:
	case tnet_dtls_setup_actpass:
	case tnet_dtls_setup_none:
		if (setup != tnet_dtls_setup_active){
			TSK_DEBUG_WARN("using setup=%s is not a good idea", TNET_DTLS_SETUP_NAMES[setup]);
		}
		SSL_set_connect_state(socket->ssl);
		break;
	default:
		TSK_DEBUG_ERROR("%d not valid value for DTLS setup", (int32_t)setup);
		break;
	}
	return 0;
#endif
}

int tnet_dtls_socket_set_store_handshakingdata(tnet_dtls_socket_handle_t* handle, tsk_bool_t handshake_storedata)
{
	if (!handle) {
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}
	((tnet_dtls_socket_t*)handle)->handshake_storedata = handshake_storedata;
	return 0;
}

int tnet_dtls_socket_get_handshakingdata(tnet_dtls_socket_handle_t* handle, const void** data, tsk_size_t *size)
{
	if (!handle || !data || !size) {
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}
	*data = ((tnet_dtls_socket_t*)handle)->handshake_data.ptr;
	*size = ((tnet_dtls_socket_t*)handle)->handshake_data.count;
	return 0;
}

tsk_bool_t tnet_dtls_socket_is_remote_cert_fp_match(tnet_dtls_socket_handle_t* handle)
{
#if !HAVE_OPENSSL || !HAVE_OPENSSL_DTLS
	TSK_DEBUG_ERROR("OpenSSL or DTLS not enabled");
	return -1;
#else
	return _tnet_dtls_socket_is_remote_cert_fp_match((tnet_dtls_socket_t*)handle);
#endif
}

int tnet_dtls_socket_do_handshake(tnet_dtls_socket_handle_t* handle, const struct sockaddr_storage* remote_addr)
{
#if !HAVE_OPENSSL || !HAVE_OPENSSL_DTLS
	TSK_DEBUG_ERROR("OpenSSL or DTLS not enabled");
	return -1;

#else
	tnet_dtls_socket_t *socket = handle;
	int ret = 0, len;
	void* out_data;

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

	tsk_safeobj_lock(socket);

	// update remote address even if handshaking is completed
	if (remote_addr) {
		socket->remote.addr = *remote_addr;
	}

	if (socket->handshake_completed) {
		TSK_DEBUG_INFO("Handshake completed");
		ret = 0;
		goto bail;
	}

	if (!socket->handshake_started) {
		if ((ret = SSL_do_handshake(socket->ssl)) != 1) {
			switch ((ret = SSL_get_error(socket->ssl, ret))) {
			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_WRITE:
			case SSL_ERROR_NONE:
				break;
			default:
				TSK_DEBUG_ERROR("DTLS handshake failed [%s]", ERR_error_string(ERR_get_error(), tsk_null));
				_tnet_dtls_socket_raise_event_dataless(socket, tnet_dtls_socket_event_type_handshake_failed);
				ret = -2;
				goto bail;
			}
		}
		socket->handshake_started = (ret == SSL_ERROR_NONE); // TODO: reset for renegotiation
	}

	if ((len = BIO_get_mem_data(socket->wbio, &out_data)) > 0 && out_data) {
		if (socket->handshake_storedata) {
			if ((int)socket->handshake_data.size < len) {
				if (!(socket->handshake_data.ptr = tsk_realloc(socket->handshake_data.ptr, len))) {
					socket->handshake_data.size = 0;
					socket->handshake_data.count = 0;
					ret = -5;
					goto bail;
				}
				socket->handshake_data.size = len;
			}
			socket->handshake_data.count = len;
			memcpy(socket->handshake_data.ptr, out_data, len);
		}
		else {
			tnet_port_t port;
			tnet_ip_t ip;
			tnet_get_sockip_n_port((const struct sockaddr *)&socket->remote.addr, &ip, &port);
			TSK_DEBUG_INFO("DTLS data handshake to send with len = %d, ip = %.*s and port = %d", len, sizeof(ip), ip, port);
			if (TNET_SOCKET_TYPE_IS_DGRAM(socket->wrapped_sock->type)) { // UDP
				len = tnet_sockfd_sendto(socket->wrapped_sock->fd, (const struct sockaddr *)&socket->remote.addr, out_data, len);
			}
			else { // TCP, TLS, WSS...
				len = tnet_socket_send_stream(socket->wrapped_sock, out_data, len);
			}
			TSK_DEBUG_INFO("DTLS data handshake sent len = %d", len);
		}
	}

	BIO_reset(socket->rbio);
	BIO_reset(socket->wbio);

	if ((socket->handshake_completed = SSL_is_init_finished(socket->ssl))) {
		TSK_DEBUG_INFO("DTLS handshake completed");

#if HAVE_OPENSSL_DTLS_SRTP
		if (socket->use_srtp){
#if !defined(SRTP_MAX_KEY_LEN)
#	define cipher_key_length (128 >> 3) // rfc5764 4.1.2.  SRTP Protection Profiles
#	define cipher_salt_length (112 >> 3) // rfc5764 4.1.2.  SRTP Protection Profiles
			// "cipher_key_length" is also equal to srtp_profile_get_master_key_length(srtp_profile_aes128_cm_sha1_80)
			// "cipher_salt_length" is also srtp_profile_get_master_salt_length(srtp_profile_aes128_cm_sha1_80)
#	define SRTP_MAX_KEY_LEN (cipher_key_length + cipher_salt_length)
#endif /* SRTP_MAX_KEY_LEN */
#define EXTRACTOR_dtls_srtp_text "EXTRACTOR-dtls_srtp"
#define EXTRACTOR_dtls_srtp_text_len 19
			uint8_t keying_material[SRTP_MAX_KEY_LEN << 1];
			static const tsk_size_t keying_material_size = sizeof(keying_material);
			/*if(socket->use_srtp)*/{
				SRTP_PROTECTION_PROFILE *p = SSL_get_selected_srtp_profile(socket->ssl);
				if (!p) {
					TSK_DEBUG_ERROR("SSL_get_selected_srtp_profile() returned null [%s]", ERR_error_string(ERR_get_error(), tsk_null));
					ret = -2;
					goto bail;
				}
				// alert user
				_tnet_dtls_socket_raise_event(socket, tnet_dtls_socket_event_type_dtls_srtp_profile_selected, p->name, tsk_strlen(p->name));

				memset(keying_material, 0, sizeof(keying_material));

				// rfc5764 - 4.2.  Key Derivation
				ret = SSL_export_keying_material(socket->ssl, keying_material, sizeof(keying_material), EXTRACTOR_dtls_srtp_text, EXTRACTOR_dtls_srtp_text_len, tsk_null, 0, 0);
				if (ret != 1) {
					// alert listener
					_tnet_dtls_socket_raise_event_dataless(socket, tnet_dtls_socket_event_type_error);
					TSK_DEBUG_ERROR("SSL_export_keying_material() failed [%s]", ERR_error_string(ERR_get_error(), tsk_null));
					ret = -2;
					goto bail;
				}
			}
			// alert listener
			_tnet_dtls_socket_raise_event(socket, tnet_dtls_socket_event_type_dtls_srtp_data, keying_material, keying_material_size);
		}
#endif /* HAVE_OPENSSL_DTLS_SRTP */
		_tnet_dtls_socket_raise_event_dataless(socket, tnet_dtls_socket_event_type_handshake_succeed);
	}
	ret = 0; // clear "ret", error will directly jump to "bail:"
bail:
	tsk_safeobj_unlock(socket);
	return ret;
#endif
}

tsk_bool_t tnet_dtls_socket_is_handshake_completed(const tnet_dtls_socket_handle_t* handle)
{
	return (handle && ((const tnet_dtls_socket_t *)handle)->handshake_completed);
}

/*
Handles DTLS data received over the network using standard functions (e.g. recvfrom())
@param handle
@param data When "use_srtp" is enabled this must point to DTLS handshake data.
@param size DTLS data size
@returns 0 if succeed, non-zero error code otherwise
*/
int tnet_dtls_socket_handle_incoming_data(tnet_dtls_socket_handle_t* handle, const void* data, tsk_size_t size)
{
#if !HAVE_OPENSSL || !HAVE_OPENSSL_DTLS
	TSK_DEBUG_ERROR("OpenSSL or DTLS not enabled");
	return -200;
#else
	tnet_dtls_socket_t *socket = handle;
	int ret = 0;

	if (!socket || !data || !size) {
		TSK_DEBUG_ERROR("Invalid parameter");
		return -1;
	}

	tsk_safeobj_lock(socket);

	TSK_DEBUG_INFO("Receive DTLS data: %u", size);

	// BIO_reset(socket->rbio);
	// BIO_reset(socket->wbio);

	if (!socket->rbio || !socket->wbio) {
		TSK_DEBUG_ERROR("BIO not initialized yet");
		ret = -2;
		goto bail;
	}

	if ((ret = _tnet_dtls_socket_do_handshake(socket))) {
		goto bail;
	}

	if ((ret = BIO_write(socket->rbio, data, (int)size)) != size) {
		ret = SSL_get_error(socket->ssl, ret);
		TSK_DEBUG_ERROR("BIO_write(rbio, %u) failed [%s]", size, ERR_error_string(ERR_get_error(), tsk_null));
		ret = -1;
		goto bail;
	}

	/*if((ret = SSL_read(socket->ssl, (void*)data, size)) <= 0){
		switch((ret = SSL_get_error(socket->ssl, ret))){
		case SSL_ERROR_WANT_READ:
		case SSL_ERROR_WANT_WRITE:
		case SSL_ERROR_NONE:
		break;
		default:
		{
		unsigned long sslErr = ERR_get_error();
		const uint8_t* pData = (const uint8_t*)data;
		TSK_DEBUG_ERROR("%lu = SSL_read(rbio, %u) failed [%s]", sslErr, size, ERR_error_string(ret, tsk_null));
		// try to understand what's going on
		// rfc6347 - 4.1.  Record Layer
		// rfc6347 - 4.2.2.  Handshake Message Format
		// rfc6347 - 4.3.2.  Handshake Protocol
		if(size > 14 && pData[0] == 0x16){ // content-type=='Handshake'
		if(pData[13] == 0x01 && (socket->setup == tnet_dtls_setup_active || socket->setup == tnet_dtls_setup_actpass)){ // Handshake Type=='client Hello'
		TSK_DEBUG_INFO("DTLS engine was in client mode but we are receiving 'Client Hello' messages. This is a bug in the remote peer: Re-negotiating!");
		tnet_dtls_socket_set_setup(socket, tnet_dtls_setup_passive);
		break;
		}
		else if(pData[13] == 0x02 && (socket->setup == tnet_dtls_setup_passive || socket->setup == tnet_dtls_setup_actpass)){ // Handshake Type=='server Hello'
		TSK_DEBUG_INFO("DTLS engine was in server mode but we are receiving 'Server Hello' messages. This is a bug in the remote peer: Re-negotiating!");
		tnet_dtls_socket_set_setup(socket, tnet_dtls_setup_active);
		break;
		}
		}
		//return -1;
		break;
		}
		}
		}*/

	ret = _tnet_dtls_socket_do_handshake(socket);

bail:
	tsk_safeobj_unlock(socket);
	return ret;
#endif
}


//=================================================================================================
//	DTLS socket object definition
//
static tsk_object_t* tnet_dtls_socket_ctor(tsk_object_t * self, va_list * app)
{
	tnet_dtls_socket_t *socket = self;
	if (socket){
		tsk_safeobj_init(socket);
	}
	return self;
}

static tsk_object_t* tnet_dtls_socket_dtor(tsk_object_t * self)
{
	tnet_dtls_socket_t *socket = self;
	if (socket){
#if HAVE_OPENSSL
		if (socket->rbio) {
			//BIO_free(socket->rbio);
			socket->rbio = tsk_null;
		}
		if (socket->wbio) {
			//BIO_free(socket->wbio);
			socket->wbio = tsk_null;
		}
		if (socket->ssl) {
			SSL_shutdown(socket->ssl);
			// https://www.openssl.org/docs/crypto/BIO_s_bio.html
			// implicitly frees internal_bio
			SSL_free(socket->ssl);
		}
#endif
		TSK_FREE(socket->handshake_data.ptr);
		TSK_OBJECT_SAFE_FREE(socket->wrapped_sock);
		tsk_safeobj_deinit(socket);
	}
	return self;
}

static const tsk_object_def_t tnet_dtls_socket_def_s =
{
	sizeof(tnet_dtls_socket_t),
	tnet_dtls_socket_ctor,
	tnet_dtls_socket_dtor,
	tsk_null,
};
const tsk_object_def_t *tnet_dtls_socket_def_t = &tnet_dtls_socket_def_s;