/*
 *  LibNoPoll: A websocket library
 *  Copyright (C) 2015 Advanced Software Production Line, S.L.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2.1
 *  of the License, or (at your option) any later version.
 *
 *  This program 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free
 *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307 USA
 *  
 *  You may find a copy of the license under this software is released
 *  at COPYING file. This is LGPL software: you are welcome to develop
 *  proprietary applications using this library without any royalty or
 *  fee but returning back any change, improvement or addition in the
 *  form of source code, project image, documentation patches, etc.
 *
 *  For commercial support on build Websocket enabled solutions
 *  contact us:
 *          
 *      Postal address:
 *         Advanced Software Production Line, S.L.
 *         Edificio Alius A, Oficina 102,
 *         C/ Antonio Suarez Nº 10,
 *         Alcalá de Henares 28802 Madrid
 *         Spain
 *
 *      Email address:
 *         info@aspl.es - http://www.aspl.es/nopoll
 */

/** 
 * \defgroup nopoll_conn noPoll Connection: functions required to create WebSocket client connections.
 */

/** 
 * \addtogroup nopoll_conn
 * @{
 */

#include <nopoll_conn.h>
#include <nopoll_private.h>

#if defined(NOPOLL_OS_UNIX)
# include <netinet/tcp.h>
#endif


/** 
 * @brief Allows to enable/disable non-blocking/blocking behavior on
 * the provided socket.
 * 
 * @param socket The socket to be configured.
 *
 * @param enable nopoll_true to enable blocking I/O, otherwise use
 * nopoll_false to enable non blocking I/O.
 * 
 * @return nopoll_true if the operation was properly done, otherwise
 * nopoll_false is returned.
 */
nopoll_bool                 nopoll_conn_set_sock_block         (NOPOLL_SOCKET socket,
								nopoll_bool   enable)
{
#if defined(NOPOLL_OS_UNIX)
	int  flags;
#endif

	if (enable) {
		/* enable blocking mode */
#if defined(NOPOLL_OS_WIN32)
		if (!nopoll_win32_blocking_enable (socket)) {
			return nopoll_false;
		}
#else
		if ((flags = fcntl (socket, F_GETFL, 0)) < -1) {
			return nopoll_false;
		} /* end if */

		flags &= ~O_NONBLOCK;
		if (fcntl (socket, F_SETFL, flags) < -1) {
			return nopoll_false;
		} /* end if */
#endif
	} else {
		/* enable nonblocking mode */
#if defined(NOPOLL_OS_WIN32)
		/* win32 case */
		if (!nopoll_win32_nonblocking_enable (socket)) {
			return nopoll_false;
		}
#else
		/* unix case */
		if ((flags = fcntl (socket, F_GETFL, 0)) < -1) {
			return nopoll_false;
		}
		
		flags |= O_NONBLOCK;
		if (fcntl (socket, F_SETFL, flags) < -1) {
			return nopoll_false;
		}
#endif
	} /* end if */

	return nopoll_true;
}


/** 
 * @brief Allows to get current timeout set for \ref noPollConn
 * connect operation.
 *
 * See also \ref nopoll_conn_connect_timeout.
 *
 * @return Current timeout configured. Returned value is measured in
 * microseconds (1 second = 1000000 microseconds). If a null value is
 * received, 0 is return and no timeout is implemented.
 */
long              nopoll_conn_get_connect_timeout (noPollCtx * ctx)
{
	/* check context recevied */
	if (ctx == NULL) {
		/* get the the default connect */
		return (0);
	} /* end if */
		
	/* return current value */
	return ctx->conn_connect_std_timeout;
}

/** 
 * @brief Allows to configure nopoll connect timeout.
 * 
 * This function allows to set the TCP connect timeout used by \ref
 * nopoll_conn_new.
 *
 * @param ctx The context where the operation will be performed.
 *
 * @param microseconds_to_wait Timeout value to be used. The value
 * provided is measured in microseconds. Use 0 to restore the connect
 * timeout to the default value.
 */
void               nopoll_conn_connect_timeout (noPollCtx * ctx,
						long        microseconds_to_wait)
{
	/* check reference received */
	if (ctx == NULL)
		return;
	
	/* configure new value */
	ctx->conn_connect_std_timeout = microseconds_to_wait;

	return;
}


/** 
 * @brief Allows to configure tcp no delay flag (enable/disable Nagle
 * algorithm).
 * 
 * @param socket The socket to be configured.
 *
 * @param enable The value to be configured, nopoll_true to enable tcp no
 * delay.
 * 
 * @return nopoll_true if the operation is completed.
 */
nopoll_bool                 nopoll_conn_set_sock_tcp_nodelay   (NOPOLL_SOCKET socket,
								nopoll_bool      enable)
{
	/* local variables */
	int result;

#if defined(NOPOLL_OS_WIN32)
	BOOL   flag = enable ? TRUE : FALSE;
	result      = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char  *)&flag, sizeof(BOOL));
#else
	int    flag = enable;
	result      = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof (flag));
#endif
	if (result == NOPOLL_INVALID_SOCKET) {
		return nopoll_false;
	}

	/* properly configured */
	return nopoll_true;
} /* end */


/** 
 * @brief Allows to configure which network interface to bind to.
 * 
 * @param socket The socket to be configured.
 *
 * @param options The options defining the interface value to be configured.
 * 
 * @return nopoll_true if the operation is completed.
 */
nopoll_bool                 nopoll_conn_set_bind_interface (NOPOLL_SOCKET socket,
							    noPollConnOpts  * options)
{
	/* no local variable here please */

	if ((NULL != options) && (NULL != options->_interface)) {
#if defined(NOPOLL_OS_WIN32) || defined(NOPOLL_OS_WIN64)
		/* Windows still not supported: send us a patch! */ 
		return nopoll_false;
#elif defined(__APPLE__) || defined(__FreeBSD__)
		/* Mac/OSX: that supports  */
		/* bind to the interface */
		return setsockopt (socket, SOL_SOCKET, IP_RECVIF,
				   options->_interface, strlen(options->_interface) ) == 0;
#else
		/* Linux/Unix case: that supports SO_BINDTODEVICE */
		/* bind to the interface */
		return setsockopt (socket, SOL_SOCKET, SO_BINDTODEVICE,
				   options->_interface, strlen (options->_interface) ) == 0;
#endif
	}

	/* properly configured */
	return nopoll_true;
} /* end */


/** 
 * @internal Allows to create a plain socket connection against the
 * host and port provided.
 *
 * @param ctx The context where the connection happens.
 *
 * @param host The host server to connect to.
 *
 * @param port The port server to connect to.
 *
 * @param options The connection options to apply.
 *
 * @return A connected socket or -1 if it fails. 
 */
NOPOLL_SOCKET nopoll_conn_sock_connect_opts (noPollCtx       * ctx,
					     const char      * host,
					     const char      * port,
					     noPollConnOpts  * options)
{
	struct hostent     * hostent;
	struct sockaddr_in   saddr;
	NOPOLL_SOCKET        session;

	/* resolve hosting name */
	hostent = gethostbyname (host);
	if (hostent == NULL) {
		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "unable to resolve host name %s", host);
		return -1;
	} /* end if */

	/* create the socket and check if it */
	session      = socket (AF_INET, SOCK_STREAM, 0);
	if (session == NOPOLL_INVALID_SOCKET) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "unable to create socket");
		return -1;
	} /* end if */

	/* disable nagle */
	nopoll_conn_set_sock_tcp_nodelay (session, nopoll_true);

	/* bind to specified interface */
	if( nopoll_true != nopoll_conn_set_bind_interface (session, options) ) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "unable to bind to specified interface");
		nopoll_close_socket (session);
		return -1;
	} /* end if */

	/* prepare socket configuration to operate using TCP/IP
	 * socket */
        memset(&saddr, 0, sizeof(saddr));
	saddr.sin_addr.s_addr = ((struct in_addr *)(hostent->h_addr))->s_addr;
        saddr.sin_family    = AF_INET;
        saddr.sin_port      = htons((uint16_t) strtod (port, NULL));

	/* set non blocking status */
	nopoll_conn_set_sock_block (session, nopoll_false);
	
	/* do a tcp connect */
        if (connect (session, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) {
		if(errno != NOPOLL_EINPROGRESS && errno != NOPOLL_EWOULDBLOCK && errno != NOPOLL_ENOTCONN) { 
		        shutdown (session, SHUT_RDWR);
                        nopoll_close_socket (session);

			nopoll_log (ctx, NOPOLL_LEVEL_WARNING, "unable to connect to remote host %s:%s errno=%d",
				    host, port, errno);
			return -1;
		} /* end if */
	} /* end if */

	/* return socket created */
	return session;
}


/** 
 * @internal Allows to create a plain socket connection against the
 * host and port provided.
 *
 * @param ctx The context where the connection happens.
 *
 * @param host The host server to connect to.
 *
 * @param port The port server to connect to.
 *
 * @return A connected socket or -1 if it fails. 
 */
NOPOLL_SOCKET nopoll_conn_sock_connect (noPollCtx       * ctx,
					const char      * host,
					const char      * port)
{
	return nopoll_conn_sock_connect_opts (ctx, host, port, NULL);
}


/** 
 * @internal Function that builds the client init greetings that will
 * be send to the server according to registered implementation.
 */ 
char * __nopoll_conn_get_client_init (noPollConn * conn, noPollConnOpts * opts)
{
	/* build sec-websocket-key */
	char key[50];
	int  key_size = 50;
	char nonce[17];

	/* get the nonce */
	if (! nopoll_nonce (nonce, 16)) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Failed to get nonce, unable to produce Sec-WebSocket-Key.");
		return NULL;
	} /* end if */

	/* now base 64 */
	if (! nopoll_base64_encode (nonce, 16, key, &key_size)) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Unable to base 64 encode characters for Sec-WebSocket-Key");
		return NULL;
	}
	
	nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Created Sec-WebSocket-Key nonce: %s", key);

	/* create accept and store */
	conn->handshake = nopoll_new (noPollHandShake, 1);
	conn->handshake->expected_accept = nopoll_strdup (key);

	/* send initial handshake */
	return nopoll_strdup_printf ("GET %s HTTP/1.1"
				     "\r\nHost: %s"
				     "\r\nUpgrade: websocket"
				     "\r\nConnection: Upgrade"
				     "\r\nSec-WebSocket-Key: %s"
				     "\r\nSec-WebSocket-Version: %d"
				     "\r\nOrigin: %s"
				     "%s%s"  /* Cookie */
				     "%s%s"  /* protocol part */
				     "%s"    /* extra arbitrary headers */
				     "\r\n\r\n",
				     conn->get_url,
				     conn->host_name,
				     /* sec-websocket-key */
				     key,
				     /* sec-websocket-version */
				     conn->ctx->protocol_version,
				     /* Origin */
				     conn->origin,
				     /* Cookie */
				     (opts && opts->cookie) ? "\r\nCookie: " : "",
				     (opts && opts->cookie) ? opts->cookie : "",
				     /* protocol part */
				     conn->protocols ? "\r\nSec-WebSocket-Protocol: " : "",
				     conn->protocols ? conn->protocols : "",
				     /* extra arbitrary headers */
				     (opts && opts->extra_headers) ? opts->extra_headers : "");
}


/**
 * @internal Function that dumps all errors found on current ssl context.
 */
int nopoll_conn_log_ssl (noPollConn * conn)
{
#if defined(SHOW_DEBUG_LOG)
        noPollCtx      * ctx = conn->ctx;
#endif
	char             log_buffer [512];
	unsigned long    err;
	int              error_position;
	int              aux_position;
	
	while ((err = ERR_get_error()) != 0) {
		ERR_error_string_n (err, log_buffer, sizeof (log_buffer));
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "tls stack: %s (find reason(code) at openssl/ssl.h)", log_buffer); 

		/* find error code position */
		error_position = 0;
		while (log_buffer[error_position] != ':' && log_buffer[error_position] != 0 && error_position < 511)
			error_position++;
		error_position++;
		aux_position = error_position;
		while (log_buffer[aux_position] != 0) {
			if (log_buffer[aux_position] == ':') {
				log_buffer[aux_position] = 0;
				break;
			}
			aux_position++;
		} /* end while */
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "    details, run: openssl errstr %s", log_buffer + error_position);
	}

	recv (conn->session, log_buffer, 1, MSG_PEEK);
	nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "    noPoll id=%d, socket: %d (after testing errno: %d)",
		    conn->id, conn->session, errno);
	
	
	return (0);
}

int __nopoll_conn_tls_handle_error (noPollConn * conn, int res, const char * label, nopoll_bool * needs_retry)
{
	int ssl_err;

	(*needs_retry) = nopoll_false;

	/* get error returned */
	ssl_err = SSL_get_error (conn->ssl, res);
	switch (ssl_err) {
	case SSL_ERROR_NONE:
		/* no error, return the number of bytes read */
	        /* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "%s, ssl_err=%d, perfect, no error reported, bytes read=%d", 
		   label, ssl_err, res); */
		return res;
	case SSL_ERROR_WANT_WRITE:
	case SSL_ERROR_WANT_READ:
	case SSL_ERROR_WANT_X509_LOOKUP:
	        nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "%s, ssl_err=%d returned that isn't ready to read/write: you should retry", 
			    label, ssl_err);
		(*needs_retry) = nopoll_true;
		return -2;
	case SSL_ERROR_SYSCALL:
		if(res < 0) { /* not EOF */
			if(errno == NOPOLL_EINTR) {
				nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "%s interrupted by a signal: retrying", label);
				/* report to retry */
				return -2;
			}
			nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "SSL_read (SSL_ERROR_SYSCALL)");
			return -1;
		}
		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "SSL socket closed on %s (res=%d, ssl_err=%d, errno=%d)",
			    label, res, ssl_err, errno);
		nopoll_conn_log_ssl (conn);

		return res;
	case SSL_ERROR_ZERO_RETURN: /* close_notify received */
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "SSL closed on %s", label);
		return res;
	case SSL_ERROR_SSL:
		nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "%s function error (received SSL_ERROR_SSL) (res=%d, ssl_err=%d, errno=%d)",
			    label, res, ssl_err, errno);
		nopoll_conn_log_ssl (conn);
		return -1;
	default:
		/* nothing to handle */
		break;
	}
	nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "%s/SSL_get_error returned %d", label, res);
	return -1;
	
}

/** 
 * @internal Default connection receive until handshake is complete.
 */
int nopoll_conn_tls_receive (noPollConn * conn, char * buffer, int buffer_size)
{
	int res;
	nopoll_bool needs_retry;
	int         tries = 0;

	/* call to read content */
	while (tries < 50) {
	        res = SSL_read (conn->ssl, buffer, buffer_size);
		/* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "SSL: received %d bytes..", res); */

		/* call to handle error */
		res = __nopoll_conn_tls_handle_error (conn, res, "SSL_read", &needs_retry);
		
		if (! needs_retry)
		        break;

		/* next operation */
		tries++;
	}
	/* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "  SSL: after procesing error %d bytes..", res); */
	return res;
}

/** 
 * @internal Default connection send until handshake is complete.
 */
int nopoll_conn_tls_send (noPollConn * conn, char * buffer, int buffer_size)
{
	int         res;
	nopoll_bool needs_retry;
	int         tries = 0;

	/* call to read content */
	while (tries < 50) {
	        res = SSL_write (conn->ssl, buffer, buffer_size);
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "SSL: sent %d bytes (requested: %d)..", res, buffer_size); 

		/* call to handle error */
		res = __nopoll_conn_tls_handle_error (conn, res, "SSL_write", &needs_retry);
		/* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "   SSL: after processing error, sent %d bytes (requested: %d)..",  res, buffer_size); */

		if (! needs_retry)
		        break;

		/* next operation */
		nopoll_sleep (tries * 10000);
		tries++;
	}
	return res;
}


SSL_CTX * __nopoll_conn_get_ssl_context (noPollCtx * ctx, noPollConn * conn, noPollConnOpts * opts, nopoll_bool is_client)
{
	/* call to user defined function if the context creator is defined */
	if (ctx && ctx->context_creator) 
		return ctx->context_creator (ctx, conn, opts, is_client, ctx->context_creator_data);

	if (opts == NULL) {
		/* select a default mechanism according to what's
		 * available, starting from the most common accepted
		 * solution, which is TLSv1.0 */
#if defined(NOPOLL_HAVE_TLSv10_ENABLED)
		/* by default use TLSv1.0 */
		return SSL_CTX_new (is_client ? TLSv1_client_method () : TLSv1_server_method ());
#elif defined(NOPOLL_HAVE_TLSv11_ENABLED)
		/* if not use TLSv1.1 */
		return SSL_CTX_new (is_client ? TLSv1_1_client_method () : TLSv1_1_server_method ());
#elif defined(NOPOLL_HAVE_TLSv12_ENABLED)
		/* if not use TLSv1.2 */
		return SSL_CTX_new (is_client ? TLSv1_2_client_method () : TLSv1_2_server_method ());
#elif defined(NOPOLL_HAVE_SSLv23_ENABLED)
		/* if not use SSLv23 */
		return SSL_CTX_new (is_client ? SSLv23_client_method () : SSLv23_server_method ()); 
#elif defined(NOPOLL_HAVE_SSLv3_ENABLED)
		/* if not use SSLv3 */
		return SSL_CTX_new (is_client ? SSLv3_client_method () : SSLv3_server_method ()); 
#else
		/* no default method found */
		return NULL;
#endif
		
	} /* end if */

	switch (opts->ssl_protocol) {

#if defined(NOPOLL_HAVE_TLS_FLEXIBLE_ENABLED)
	case NOPOLL_METHOD_TLS_FLEXIBLE:
		return SSL_CTX_new (is_client ? TLS_client_method () : TLS_server_method ());
#endif		
		
#if defined(NOPOLL_HAVE_TLSv10_ENABLED)
	case NOPOLL_METHOD_TLSV1:
		return SSL_CTX_new (is_client ? TLSv1_client_method () : TLSv1_server_method ());
#endif
		
#if defined(NOPOLL_HAVE_TLSv11_ENABLED)
	case NOPOLL_METHOD_TLSV1_1:
		return SSL_CTX_new (is_client ? TLSv1_1_client_method () : TLSv1_1_server_method ()); 
#endif
		
#if defined(NOPOLL_HAVE_TLSv12_ENABLED)
	case NOPOLL_METHOD_TLSV1_2:
		return SSL_CTX_new (is_client ? TLSv1_2_client_method () : TLSv1_2_server_method ()); 
#endif
		
#if defined(NOPOLL_HAVE_SSLv3_ENABLED)
	case NOPOLL_METHOD_SSLV3:
		/* printf ("**** REPORTING SSLv3 ****\n"); */
		return SSL_CTX_new (is_client ? SSLv3_client_method () : SSLv3_server_method ()); 
#endif
#if defined(NOPOLL_HAVE_SSLv23_ENABLED)
	case NOPOLL_METHOD_SSLV23:
		/* printf ("**** REPORTING SSLv23 ****\n"); */
		return SSL_CTX_new (is_client ? SSLv23_client_method () : SSLv23_server_method ());
#endif		
	}

#if defined(NOPOLL_HAVE_TLSv10_ENABLED)
	/* reached this point, report default TLSv1 method */
	return SSL_CTX_new (is_client ? TLSv1_client_method () : TLSv1_server_method ());
#else
	return NULL;
#endif
}

noPollCtx * __nopoll_conn_ssl_ctx_debug = NULL;

int __nopoll_conn_ssl_verify_callback (int ok, X509_STORE_CTX * store) {
	char   data[256];
	X509 * cert;
#if defined(SHOW_DEBUG_LOG)
	int    depth;
	int    err;
#endif

	if (! ok) {
		cert  = X509_STORE_CTX_get_current_cert (store);
#if defined(SHOW_DEBUG_LOG)
		depth = X509_STORE_CTX_get_error_depth (store);
		err   = X509_STORE_CTX_get_error (store);
#endif

		nopoll_log (__nopoll_conn_ssl_ctx_debug, NOPOLL_LEVEL_CRITICAL, "CERTIFICATE: error=%d at depth: %d", err, depth);

		X509_NAME_oneline (X509_get_issuer_name (cert), data, 256);
		nopoll_log (__nopoll_conn_ssl_ctx_debug, NOPOLL_LEVEL_CRITICAL, "CERTIFICATE: issuer: %s", data);

		X509_NAME_oneline (X509_get_subject_name (cert), data, 256);
		nopoll_log (__nopoll_conn_ssl_ctx_debug, NOPOLL_LEVEL_CRITICAL, "CERTIFICATE: subject: %s", data);

		nopoll_log (__nopoll_conn_ssl_ctx_debug, NOPOLL_LEVEL_CRITICAL, "CERTIFICATE: error %d:%s", err, X509_verify_cert_error_string (err));
			    
	}
	return ok; /* return same value */
}

nopoll_bool __nopoll_conn_set_ssl_client_options (noPollCtx * ctx, noPollConn * conn, noPollConnOpts * options)
{
	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Checking to establish SSL options (%p)", options);

	if (options && options->ca_certificate) {
		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Setting CA certificate: %s", options->ca_certificate);
		if (SSL_CTX_load_verify_locations (conn->ssl_ctx, options->ca_certificate, NULL) != 1) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to configure CA certificate (%s), SSL_CTX_load_verify_locations () failed", options->ca_certificate);
			return nopoll_false;
		} /* end if */
		
	} /* end if */

	/* enable default verification paths */
	/* printf ("conn = %p, conn->ssl_ctx = %p\n", conn, conn->ssl_ctx); */
	if (SSL_CTX_set_default_verify_paths (conn->ssl_ctx) != 1) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to configure default verification paths, SSL_CTX_set_default_verify_paths () failed");
		return nopoll_false;
	} /* end if */

	if (options && options->chain_certificate) {
		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Setting chain certificate: %s", options->chain_certificate);
		if (SSL_CTX_use_certificate_chain_file (conn->ssl_ctx, options->chain_certificate) != 1) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to configure chain certificate (%s), SSL_CTX_use_certificate_chain_file () failed", options->chain_certificate);
			return nopoll_false;
		} /* end if */
	} /* end if */

	if (options && options->certificate) {
		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Setting certificate: %s", options->certificate);
		if (SSL_CTX_use_certificate_chain_file (conn->ssl_ctx, options->certificate) != 1) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to configure client certificate (%s), SSL_CTX_use_certificate_file () failed", options->certificate);
			return nopoll_false;
		} /* end if */
	} /* end if */

	if (options && options->private_key) {
		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Setting private key: %s", options->private_key);
		if (SSL_CTX_use_PrivateKey_file (conn->ssl_ctx, options->private_key, SSL_FILETYPE_PEM) != 1) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to configure private key (%s), SSL_CTX_use_PrivateKey_file () failed", options->private_key);
			return nopoll_false;
		} /* end if */
	} /* end if */

	if (options && options->private_key && options->certificate) {
		if (!SSL_CTX_check_private_key (conn->ssl_ctx)) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Certificate and private key do not matches, verification fails, SSL_CTX_check_private_key ()");
			return nopoll_false;
		} /* end if */
		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Certificate (%s) and private key (%s) matches", options->certificate, options->private_key);
	} /* end if */

	/* if no option and it is not disabled */
	if (options == NULL || ! options->disable_ssl_verify) {
		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Enabling certificate peer verification");
		/** really, really ugly hack to let
		 * __nopoll_conn_ssl_verify_callback to be able to get
		 * access to the context required to drop some logs */
		__nopoll_conn_ssl_ctx_debug = ctx;
		SSL_CTX_set_verify (conn->ssl_ctx, SSL_VERIFY_PEER, __nopoll_conn_ssl_verify_callback); 
		SSL_CTX_set_verify_depth (conn->ssl_ctx, 10); 
	} /* end if */

	return nopoll_true;
}


/** 
 * @internal Internal implementation used to do a connect.
 */
noPollConn * __nopoll_conn_new_common (noPollCtx       * ctx,
				       noPollConnOpts  * options,
				       nopoll_bool       enable_tls,
				       const char      * host_ip, 
				       const char      * host_port, 
				       const char      * host_name,
				       const char      * get_url, 
				       const char      * protocols,
				       const char      * origin)
{
	noPollConn     * conn;
	NOPOLL_SOCKET    session;
	char           * content;
	int              size;
	int              ssl_error;
	X509           * server_cert;
	int              iterator;
	long             remaining_timeout;

	if (! ctx || ! host_ip) {
		/* release connection options */
		__nopoll_conn_opts_release_if_needed (options);
		return NULL;
	} /* end if */

	/* set default connection port */
	if (host_port == NULL)
		host_port = "80";

	/* create socket connection in a non block manner */
	session = nopoll_conn_sock_connect_opts (ctx, host_ip, host_port, options);
	if (session == NOPOLL_INVALID_SOCKET) {
		/* release connection options */
		__nopoll_conn_opts_release_if_needed (options);
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to connect to remote host %s:%s", host_ip, host_port);
		return NULL;
	} /* end if */

	/* create the connection */
	conn = nopoll_new (noPollConn, 1);
	if (conn == NULL) {
		/* release connection options */
		__nopoll_conn_opts_release_if_needed (options);
		return NULL;
	} /* end if */

	conn->refs = 1;

	/* create mutex */
	conn->ref_mutex = nopoll_mutex_create ();

	/* register connection into context */
	if (! nopoll_ctx_register_conn (ctx, conn)) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to register connection into the context, unable to create connection");
		nopoll_free (conn);
		/* release connection options */
		__nopoll_conn_opts_release_if_needed (options);

		return NULL;
	}

	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Created noPoll conn-id=%d (ptr: %p, context: %p, socket: %d)",
		    conn->id, conn, ctx, session);
	
	/* configure context */
	conn->ctx     = ctx;
	conn->session = session;
	conn->role    = NOPOLL_ROLE_CLIENT;

	/* record host and port */
	conn->host    = nopoll_strdup (host_ip);
	conn->port    = nopoll_strdup (host_port);

	/* configure default handlers */
	conn->receive = nopoll_conn_default_receive;
	conn->send    = nopoll_conn_default_send;

	/* build host name */
	if (host_name == NULL)
		conn->host_name = nopoll_strdup (host_ip);
	else
		conn->host_name = nopoll_strdup (host_name);

	/* build origin */
	if (origin == NULL)
		conn->origin = nopoll_strdup_printf ("http://%s", conn->host_name);
	else
		conn->origin = nopoll_strdup (origin);

	/* get url */
	if (get_url == NULL)
		conn->get_url = nopoll_strdup ("/");
	else
		conn->get_url = nopoll_strdup (get_url);

	/* protocols */
	if (protocols != NULL)
		conn->protocols = nopoll_strdup (protocols);


	/* get client init payload */
	content = __nopoll_conn_get_client_init (conn, options);

	if (content == NULL) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to build client init message, unable to connect");
		nopoll_conn_shutdown (conn);

		/* release connection options */
		__nopoll_conn_opts_release_if_needed (options);

		return NULL;
	} /* end if */

	/* check for TLS support */
	if (enable_tls) {
		/* found TLS connection request, enable it */
		conn->ssl_ctx  = __nopoll_conn_get_ssl_context (ctx, conn, options, nopoll_true);
		if (conn->ssl_ctx == NULL) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to enable TLS, internal __nopoll_conn_get_ssl_context (ctx=%p, conn=%p, options=%p, nopoll_true) failed",
				    ctx, conn, options);
			goto fail_ssl_connection;
		} /* end if */

		/* check for client side SSL configuration */
		if (! __nopoll_conn_set_ssl_client_options (ctx, conn, options)) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to configure additional SSL options, unable to continue, conn->ssl_ctx=%p, conn->ssl=%p",
				    conn->ssl_ctx, conn->ssl);
			goto fail_ssl_connection;
		} /* end if */

		/* create context and check for result */
		conn->ssl      = SSL_new (conn->ssl_ctx);       
		if (conn->ssl_ctx == NULL || conn->ssl == NULL) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to create SSL context internal references are null (conn->ssl_ctx=%p, conn->ssl=%p)",
				    conn->ssl_ctx, conn->ssl);
		fail_ssl_connection:

			nopoll_free (content);
			nopoll_conn_shutdown (conn);

			/* release connection options */
			__nopoll_conn_opts_release_if_needed (options);

			return conn;
		} /* end if */
		
		/* set socket */
		SSL_set_fd (conn->ssl, conn->session);

		/* do the initial connect connect */
		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "connecting to remote TLS site");
		iterator = 0;
		while (SSL_connect (conn->ssl) <= 0) {
		
			/* get ssl error */
			ssl_error = SSL_get_error (conn->ssl, -1);
 
			switch (ssl_error) {
			case SSL_ERROR_WANT_READ:
			        nopoll_log (ctx, NOPOLL_LEVEL_WARNING, "still not prepared to continue because read wanted, conn-id=%d (%p, session: %d), errno=%d",
					    conn->id, conn, conn->session, errno);
				break;
			case SSL_ERROR_WANT_WRITE:
			        nopoll_log (ctx, NOPOLL_LEVEL_WARNING, "still not prepared to continue because write wanted, conn-id=%d (%p)",
					    conn->id, conn);
				break;
			case SSL_ERROR_SYSCALL:
				nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "syscall error while doing TLS handshake, ssl error (code:%d), conn-id: %d (%p), errno: %d, session: %d",
					    ssl_error, conn->id, conn, errno, conn->session);
				nopoll_conn_log_ssl (conn);
				nopoll_conn_shutdown (conn);
				nopoll_free (content);

				/* release connection options */
				__nopoll_conn_opts_release_if_needed (options);

				return conn;
			default:
				nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "there was an error with the TLS negotiation, ssl error (code:%d) : %s",
					    ssl_error, ERR_error_string (ssl_error, NULL));
				nopoll_conn_log_ssl (conn);
				nopoll_conn_shutdown (conn);
				nopoll_free (content);

				/* release connection options */
				__nopoll_conn_opts_release_if_needed (options);

				return conn;
			} /* end switch */

			/* try and limit max reconnect allowed */
			iterator++;

			if (iterator > 1000) {
				nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Max retry calls=%d to SSL_connect reached, shutting down connection id=%d, errno=%d",
					    iterator, conn->id, errno);
				nopoll_free (content);

				/* release connection options */
				__nopoll_conn_opts_release_if_needed (options);

				return conn;
			} /* end if */

			/* wait a bit before retry */
			nopoll_sleep (10000);

		} /* end while */

		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Client TLS handshake finished, configuring I/O handlers");

		/* check remote certificate (if it is present) */
		server_cert = SSL_get_peer_certificate (conn->ssl);
		if (server_cert == NULL) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "server side didn't set a certificate for this session, these are bad news");

			/* release connection options */
			nopoll_free (content);
			__nopoll_conn_opts_release_if_needed (options);

			return conn;
		}
		X509_free (server_cert);

		/* call to check post ssl checks after SSL finalization */
		if (conn->ctx && conn->ctx->post_ssl_check) {
			if (! conn->ctx->post_ssl_check (conn->ctx, conn, conn->ssl_ctx, conn->ssl, conn->ctx->post_ssl_check_data)) {
				/* TLS post check failed */
				nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "TLS/SSL post check function failed, dropping connection");
				nopoll_conn_shutdown (conn);
				return NULL;
			} /* end if */
		} /* end if */

		/* configure default handlers */
		conn->receive = nopoll_conn_tls_receive;
		conn->send    = nopoll_conn_tls_send;

		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "TLS I/O handlers configured");
		conn->tls_on = nopoll_true;
	} /* end if */

	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Sending websocket client init: %s", content);
	size = strlen (content);

	/* call to send content */
	remaining_timeout = ctx->conn_connect_std_timeout;
	while (remaining_timeout > 0) {
		if (size != conn->send (conn, content, size)) {
		        /* for some reason, under FreeBSD, a ENOTCONN is reported when they should be returning EINPROGRESS and/or EWOULDBLOCK */
			if (errno == NOPOLL_EWOULDBLOCK || errno == NOPOLL_EINPROGRESS || errno == NOPOLL_ENOTCONN) {
				/* nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Connection in progress (errno=%d), session: %d", errno, session); */
				nopoll_sleep (10000);
				remaining_timeout -= 10000;
				continue;
			} /* end if */

			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to send websocket init message, error code was: %d (2), closing session", errno);
			nopoll_conn_shutdown (conn);
		} /* end if */

		break;
	}

	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Web socket initial client handshake sent");

	/* release content */
	nopoll_free (content);

	/* release connection options */
	__nopoll_conn_opts_release_if_needed (options);

	/* return connection created */
	return conn;
}


/** 
 * @brief Creates a new Websocket connection to the provided
 * destination, physically located at host_ip and host_port.
 *
 * @param ctx The noPoll context to which this new connection will be associated.
 *
 * @param host_ip The websocket server address to connect to.
 *
 * @param host_port The websocket server port to connect to. If NULL
 * is provided, port 80 is used.
 *
 * @param host_name This is the Host: header value that will be
 * sent. This header is used by the websocket server to activate the
 * right virtual host configuration. If null is provided, Host: will
 * use host_ip value.
 *
 * @param get_url As part of the websocket handshake, an url is passed
 * to the remote server inside a GET method. This parameter allows to
 * configure this. If NULL is provided, then / will be used.
 *
 * @param origin Websocket origin to be notified to the server.
 *
 * @param protocols Optional protocols requested to be activated for
 * this connection (an string of list of strings separated by a white
 * space). If the server accepts the connection you can use \ref
 * nopoll_conn_get_accepted_protocol to get the protocol accepted by
 * the server.
 *
 * @return A reference to the connection created or NULL if it
 * fails. Keep in mind the connection reported may not be connected at
 * the time is returned by this function. You can use \ref
 * nopoll_conn_is_ready and \ref nopoll_conn_is_ok to ensure it can be
 * used. There is also a helper function (NOTE it is blocking) that
 * can help you implement a very simple wait until ready operation:
 * \ref nopoll_conn_wait_until_connection_ready (however, it is not
 * recommended for any serious, non-command line programming).
 *
 * <b>Note 1: Is \ref nopoll_conn_new blocking?</b>
 *
 * Partially. It is blocking with a timeout but just for sending the
 * client init (the initial packet that a WebSocket client must send),
 * but the function does not block the caller until a reply from the
 * server is received and the handshake is completed (which is where
 * the blocking it is likely to happen).
 *
 * So, it essence, \ref nopoll_conn_new (and all its variants) is not
 * blocking (unless you have a problem with network connection causing
 * send() API to fail to send the WebSocket init message by returning
 * EWOULD_BLOCK, which is very likely to not happen),
 * 
 * In that, the connection (noPollConn) reported by this function have big
 * chances to be not ready (that's why you have to use \ref nopoll_conn_is_ready
 * or the blocking one \ref nopoll_conn_wait_until_connection_ready to ensure
 * you successfully connected),
 *
 * <b>Note 2: Controll connect timeout</b>
 *
 * To control timeout for sending the initial message (and to ensure
 * the engine sends it), you can use the following functions:
 *
 *  - \ref nopoll_conn_connect_timeout
 *  - \ref nopoll_conn_get_connect_timeout
 */
noPollConn * nopoll_conn_new (noPollCtx  * ctx,
			      const char * host_ip, 
			      const char * host_port, 
			      const char * host_name,
			      const char * get_url, 
			      const char * protocols,
			      const char * origin)
{
	/* call common implementation */
	return __nopoll_conn_new_common (ctx, NULL, nopoll_false, 
					 host_ip, host_port, host_name, 
					 get_url, protocols, origin);
}

/** 
 * @brief Creates a new Websocket connection to the provided
 * destination, physically located at host_ip and host_port and
 * allowing to provide a noPollConnOpts object.
 *
 * See \ref nopoll_conn_new for more information.
 *
 * @param ctx The noPoll context to which this new connection will be associated.
 *
 * @param opts The connection options to use during the connection and
 * the usage of this connection.
 *
 * @param host_ip The websocket server address to connect to.
 *
 * @param host_port The websocket server port to connect to. If NULL
 * is provided, port 80 is used.
 *
 * @param host_name This is the Host: header value that will be
 * sent. This header is used by the websocket server to activate the
 * right virtual host configuration. If null is provided, Host: will
 * use host_ip value.
 *
 * @param get_url As part of the websocket handshake, an url is passed
 * to the remote server inside a GET method. This parameter allows to
 * configure this. If NULL is provided, then / will be used.
 *
 * @param origin Websocket origin to be notified to the server.
 *
 * @param protocols Optional protocols requested to be activated for
 * this connection (an string of list of strings separated by a white
 * space). If the server accepts the connection you can use \ref
 * nopoll_conn_get_accepted_protocol to get the protocol accepted by
 * the server.
 *
 * @return A reference to the connection created or NULL if it
 * fails. Keep in mind the connection reported may not be connected at
 * the time is returned by this function. You can use \ref
 * nopoll_conn_is_ready and \ref nopoll_conn_is_ok to ensure it can be
 * used. There is also a helper function (NOTE it is blocking) that
 * can help you implement a very simple wait until ready operation:
 * \ref nopoll_conn_wait_until_connection_ready (however, it is not
 * recommended for any serious, non-command line programming).
 */
noPollConn * nopoll_conn_new_opts (noPollCtx       * ctx,
				   noPollConnOpts  * opts,
				   const char      * host_ip, 
				   const char      * host_port, 
				   const char      * host_name,
				   const char      * get_url, 
				   const char      * protocols,
				   const char      * origin)
{
	/* call common implementation */
	return __nopoll_conn_new_common (ctx, opts, nopoll_false, 
					 host_ip, host_port, host_name, 
					 get_url, protocols, origin);
}

nopoll_bool __nopoll_tls_was_init = nopoll_false;

/** 
 * @brief Allows to create a client WebSocket connection over TLS.
 *
 * The function works like \ref nopoll_conn_new with the same
 * semantics but providing a way to create a WebSocket session under
 * TLS supervision. See \ref nopoll_conn_new for more information.
 *
 * @param ctx The context where the operation will take place.
 *
 * @param options Optional configuration object. See \ref nopoll_conn_opts_new and \ref nopoll_conn_opts_set_ssl_protocol (for example).
 *
 * @param host_ip The websocket server address to connect to.
 *
 * @param host_port The websocket server port to connect to. If NULL
 * is provided, port 443 is used.
 *
 * @param host_name This is the Host: header value that will be
 * sent. This header is used by the websocket server to activate the
 * right virtual host configuration. If null is provided, Host: will
 * use host_ip value.
 *
 * @param get_url As part of the websocket handshake, an url is passed
 * to the remote server inside a GET method. This parameter allows to
 * configure this. If NULL is provided, then / will be used.
 *
 * @param origin Websocket origin to be notified to the server.
 *
 * @param protocols Optional protocols requested to be activated for
 * this connection (an string of list of strings separated by a white
 * space). If the server accepts the connection you can use \ref
 * nopoll_conn_get_accepted_protocol to get the protocol accepted by
 * the server.
 *
 * @return A reference to the connection created or NULL if it
 * fails. Keep in mind the connection reported may not be connected at
 * the time is returned by this function. You can use \ref
 * nopoll_conn_is_ready and \ref nopoll_conn_is_ok to ensure it can be
 * used. There is also a helper function (NOTE it is blocking) that
 * can help you implement a very simple wait until ready operation:
 * \ref nopoll_conn_wait_until_connection_ready (however, it is not
 * recommended for any serious, non-command line programming).
 * 
 */
noPollConn * nopoll_conn_tls_new (noPollCtx  * ctx,
				  noPollConnOpts  * options,
				  const char * host_ip, 
				  const char * host_port, 
				  const char * host_name,
				  const char * get_url, 
				  const char * protocols,
				  const char * origin)
{
	/* init ssl ciphers and engines */
	if (! __nopoll_tls_was_init) {
		__nopoll_tls_was_init = nopoll_true;
		SSL_library_init ();
	} /* end if */

	/* call common implementation */
	return __nopoll_conn_new_common (ctx, options, nopoll_true, 
					 host_ip, host_port, host_name, 
					 get_url, protocols, origin);
}

/** 
 * @brief Allows to acquire a reference to the provided connection.
 *
 * @param conn The conection to be referenced
 *
 * @return nopoll_true In the case the function acquired a reference
 * otherwise nopoll_false is returned.
 */
nopoll_bool    nopoll_conn_ref (noPollConn * conn)
{
	if (conn == NULL)
		return nopoll_false;

	/* lock the mutex */
	nopoll_mutex_lock (conn->ref_mutex);
	conn->refs++;
	nopoll_mutex_unlock (conn->ref_mutex);
		
	/* report */
	return conn->refs > 1;
}

/** 
 * @brief Allows to get current reference counting state for the
 * provided connection.
 *
 * @param conn The connection queried for its reference counting.
 *
 * @return The reference counting or -1 if it fails.
 */
int            nopoll_conn_ref_count (noPollConn * conn)
{
	if (! conn)
		return -1;
	return conn->refs;
}

/** 
 * @brief Allows to check if the provided connection is in connected
 * state (just to the connection). This is different to be ready to send and receive content
 * because the session needs to be first established. 
 *
 * You can use \ref nopoll_conn_is_ready to ensure the connection is
 * ready to be used (read or write operation can be done because
 * handshake has finished).
 *
 * For example, you might connect to a raw socket server and
 * nopoll_conn_is_ok will report that everything is ok because the
 * socket is indeed connected but because you are connecting to a
 * non-websocket server, it will not work because the WebSocket
 * session establishment didn't take place and hence
 * nopoll_conn_is_ready will fail.
 *
 * Please, see the following link for a complete example that connects
 * and ensure the connection is ready (for a client):  http://www.aspl.es/nopoll/html/nopoll_core_library_manual.html#creating_basic_web_socket_client
 *
 * @param conn The websocket connection to be checked.
 *
 * @return nopoll_true in the case the connection is working otherwise
 * nopoll_false is returned.
 */
nopoll_bool    nopoll_conn_is_ok (noPollConn * conn)
{
	if (conn == NULL)
		return nopoll_false;

	/* return current socket status */
	return conn->session != NOPOLL_INVALID_SOCKET;
}

/** 
 * @brief Allows to check if the connection is ready to be used
 * (handshake completed).
 *
 * Note this function may block the caller in the case the hanshake is
 * not completed.
 *
 * @param conn The connection to be checked.
 *
 * @return nopoll_true in the case the handshake was completed
 * otherwise nopoll_false is returned. The function also returns
 * nopoll_false in case \ref nopoll_conn_is_ok is failing.
 */
nopoll_bool    nopoll_conn_is_ready (noPollConn * conn)
{
	if (conn == NULL)
		return nopoll_false;
	if (conn->session == NOPOLL_INVALID_SOCKET)
		return nopoll_false;
	if (! conn->handshake_ok) {
		/* acquire here handshake mutex */
		nopoll_mutex_lock (conn->ref_mutex);

		/* complete handshake */
		nopoll_conn_complete_handshake (conn);

		/* release here handshake mutex */
		nopoll_mutex_unlock (conn->ref_mutex);
	}
	return conn->handshake_ok;
}

/** 
 * @brief Allows to check if the provided connection is working under a TLS session.
 *
 * @param conn The connection where the TLS is being queired to be enabled.
 *
 * @return nopoll_true in the case TLS is enabled, otherwise* nopoll_false is returned. Note the function also returns* nopoll_false when the reference received is NULL.
 */
nopoll_bool    nopoll_conn_is_tls_on (noPollConn * conn)
{
	if (! conn)
		return nopoll_false;

	/* return TLS on */
	return conn->tls_on;
}

/** 
 * @brief Allows to get the socket associated to this nopoll
 * connection.
 *
 * @return The socket reference or -1 if it fails.
 */
NOPOLL_SOCKET nopoll_conn_socket (noPollConn * conn)
{
	if (conn == NULL)
		return -1;
	return conn->session;
}

/** 
 * @brief Allows to set up the socket reference to be used by this
 * noPollConn.
 *
 * @param conn The connection to setup with a new socket.
 *
 * @param _socket The socket that will be configured.
 */
void           nopoll_conn_set_socket (noPollConn * conn, NOPOLL_SOCKET _socket)
{
	if (conn == NULL)
		return;
	conn->session = _socket;
	return;
}

/** 
 * @brief Allows to get the connection id from the provided
 * connection.
 *
 * @param conn The connection from where the unique identifier will be
 * returned.
 *
 * @return The identifier or -1 if it fails.
 */
int           nopoll_conn_get_id (noPollConn * conn)
{
	if (conn == NULL)
		return -1;
	return conn->id;
}

/**
 * @brief Allows to get the get_url from the connection
 *
 * @param conn The connection from where the url will be obtained
 *
 * @return The get_url or NULL
 */
const char * nopoll_conn_get_requested_url (noPollConn * conn)
{
        if (!conn)
            return NULL;
        return (conn->get_url == NULL ? "/" : conn->get_url);
}


/** 
 * @brief Allows to get the noPollCtx context object associated to the
 * connection (or where the connection is working).
 *
 * @param conn The connection that is requested to return its context.
 *
 * @return A reference to the context or NULL if it fails.
 */
noPollCtx   * nopoll_conn_ctx    (noPollConn * conn)
{
	if (conn == NULL)
		return NULL;
	return conn->ctx;
}

/** 
 * @brief Allows to get the connection role.
 *
 * @return The connection role, see \ref noPollRole for details.
 */
noPollRole    nopoll_conn_role   (noPollConn * conn)
{
	if (conn == NULL)
		return NOPOLL_ROLE_UNKNOWN;
	return conn->role;
}

/** 
 * @brief Returns the host location this connection connects to or it is
 * listening (according to the connection role \ref noPollRole).
 *
 * If you are looking for a way to get the Host: header value received
 * for this connection during the handshake, use: \ref
 * nopoll_conn_get_host_header.
 *
 * @param conn The connection to check for the host value.
 *
 * @return The host location value or NULL if it fails.
 */
const char  * nopoll_conn_host   (noPollConn * conn)
{
	if (conn == NULL)
		return NULL;
	return conn->host;
}

/** 
 * @brief Allows to get the connection Origin header content received.
 *
 * @param conn The websocket connection where the operation takes place.
 *
 * @return The Origin value received during the handshake for this
 * connection or NULL if it fails to get this value.
 */
const char  * nopoll_conn_get_origin (noPollConn * conn)
{
	if (conn == NULL || conn->handshake == NULL)
		return NULL;
	return conn->origin;
} /* end if */

/** 
 * @brief Allows to get the Host: header value that was received for
 * this connection during the handshake.
 *
 * @param conn The websocket connection where the operation takes place.
 *
 * @return The Host value received during the handshake for this
 * connection or NULL if it fails to get this value (or it wasn't
 * defined).
 */
const char  * nopoll_conn_get_host_header (noPollConn * conn)
{
	if (conn == NULL || conn->host_name == NULL)
		return NULL;
	return conn->host_name;
}

/** 
 * @brief Allows to get cookie header content received during
 * handshake (if received).
 *
 * @param conn The websocket connection where the operation takes place.
 *
 * @return A reference to the cookie value or NULL if nothing is
 * configured or if failed to get it.
 */
const char  * nopoll_conn_get_cookie (noPollConn * conn)
{
	
        if (conn == NULL || conn->handshake == NULL)
                return NULL;
        return conn->handshake->cookie;
}

/** 
 * @brief Allows to get accepted protocol in the case a protocol was
 * requested during connection.
 *
 * @param conn The connection where the operation is taking place.
 *
 * @return A reference to the protocol accepted or NULL if no protocol
 * was mentioned during the handshake.
 */
const char  * nopoll_conn_get_accepted_protocol (noPollConn * conn)
{
	if (conn == NULL)
		return NULL;
	return conn->accepted_protocol; /* report accepted protocol */
}

/** 
 * @brief Allows to get requested protocols for the provided
 * connection
 *
 * @param conn The connection where the operation is taking place.
 *
 * @return A reference to the protocol or list of protocols requested
 * by this connection.
 */
const char  * nopoll_conn_get_requested_protocol (noPollConn * conn)
{
	if (conn == NULL)
		return NULL;
	return conn->protocols; /* report requested protocol */
}

/** 
 * @brief Allows to configure accepted protocol on the provided
 * connection.
 *
 * @param conn The connection where the accepted protocol will be
 * notified. This function is only useful at server side to notify
 * accepted protocol to the connecting client. Caller is entirely
 * responsible on setting an appropriate value that is understood by
 * the client. No especial check is done on the value provided to this function.
 *
 * @param protocol The protocol to configure in the reply, that is,
 * the protocol accepted by the server. 
 *
 * If no protocol is configured and the client requests a protocol,
 * the server will reply by default the same accepting it.
 */
void          nopoll_conn_set_accepted_protocol (noPollConn * conn, const char * protocol)
{
	if (conn == NULL || protocol == NULL)
		return;

	/* set accepted protocol */
	conn->accepted_protocol = nopoll_strdup (protocol);
	return;
}

/** 
 * @brief Returns the port location this connection connects to or it
 * is listening (according to the connection role \ref noPollRole).
 *
 * @param conn The connection to check for the port value.
 *
 * @return The port location value or NULL if it fails.
 */
const char  * nopoll_conn_port   (noPollConn * conn)
{
	if (conn == NULL)
		return NULL;
	return conn->port;
}

/** 
 * @brief Optionally, remote peer can close the connection providing an
 * error code (\ref nopoll_conn_get_close_status) and a reason (\ref nopoll_conn_get_close_reason).
 *
 * These functions are used to get those values.
 *
 * @param conn The connection where the peer reported close status is being asked.
 *
 * @return Status code reported by the remote peer or 0 if nothing was reported.
 */ 
int           nopoll_conn_get_close_status (noPollConn * conn)
{
	if (conn == NULL)
		return 0;
	return conn->peer_close_status;
}

/** 
 * @brief Optionally, remote peer can close the connection providing an
 * error code (\ref nopoll_conn_get_close_status) and a reason (\ref nopoll_conn_get_close_reason).
 *
 * These functions are used to get those values.
 *
 * @param conn The connection where the peer reported close reason is being asked.
 *
 * @return Status close reason reported by the remote peer or NULL if nothing was reported.
 */ 
const char *  nopoll_conn_get_close_reason (noPollConn * conn)
{
	if (conn == NULL)
		return NULL;
	return conn->peer_close_reason;
}

/** 
 * @brief Call to close the connection immediately without going
 * through websocket close negotiation.
 *
 * @param conn The connection to be shutted down.
 */
void          nopoll_conn_shutdown (noPollConn * conn)
{
#if defined(SHOW_DEBUG_LOG)
	const char * role = NULL;
#endif

	if (conn == NULL)
		return;

	/* report connection close */
#if defined(SHOW_DEBUG_LOG)
	if (conn->role == NOPOLL_ROLE_LISTENER)
		role = "listener";
	else if (conn->role == NOPOLL_ROLE_MAIN_LISTENER)
		role = "master-listener";
	else if (conn->role == NOPOLL_ROLE_UNKNOWN)
		role = "unknown";
	else if (conn->role == NOPOLL_ROLE_CLIENT)
		role = "client";
	else
		role = "unknown";

	nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "shutting down connection id=%d (session: %d, role: %s)", 
		    conn->id, conn->session, role);
#endif

	/* call to on close handler if defined */
	if (conn->session != NOPOLL_INVALID_SOCKET && conn->on_close)
	        conn->on_close (conn->ctx, conn, conn->on_close_data);

	/* shutdown connection here */
	if (conn->session != NOPOLL_INVALID_SOCKET) {
	        shutdown (conn->session, SHUT_RDWR);
		nopoll_close_socket (conn->session);
	}
	conn->session = NOPOLL_INVALID_SOCKET;

	return;
}

/** 
 * @brief Allows to close an opened \ref noPollConn no matter its role
 * (\ref noPollRole).
 *
 * @param conn The connection to close.
 *
 * @param status Optional status code to send to remote side. If
 * status is < 0, no status code is sent.
 *
 * @param reason Pointer to the content to be sent.
 *
 * @param reason_size The amount of bytes that should be used from content pointer.
 */ 
void          nopoll_conn_close_ext  (noPollConn  * conn, int status, const char * reason, int reason_size)
{
	int    refs;
	char * content;
#if defined(SHOW_DEBUG_LOG)
	const char * role = "unknown";
#endif

	/* check input data */
	if (conn == NULL)
		return;

#if defined(SHOW_DEBUG_LOG)
	if (conn->role == NOPOLL_ROLE_LISTENER)
		role = "listener";
	else if (conn->role == NOPOLL_ROLE_MAIN_LISTENER)
		role = "master-listener";
	else if (conn->role == NOPOLL_ROLE_UNKNOWN)
		role = "unknown";
	else if (conn->role == NOPOLL_ROLE_CLIENT)
		role = "client";
	
	nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Calling to close close id=%d (session %d, refs: %d, role: %s)", 
		    conn->id, conn->session, conn->refs, role);
#endif
	if (conn->session != NOPOLL_INVALID_SOCKET) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "requested proper connection close id=%d (session %d)", conn->id, conn->session);

		/* build reason indication */
		content = NULL;
		if (reason && reason_size > 0) {
			/* send content */
			content = nopoll_new (char, reason_size + 3);
			if (content) {
				nopoll_set_16bit (status, content);
				memcpy (content + 2, reason, reason_size);
			} /* end if */
		} /* end if */

		/* send close without reason */
		nopoll_conn_send_frame (conn, nopoll_true /* has_fin */, 
					/* masked */
					conn->role == NOPOLL_ROLE_CLIENT, NOPOLL_CLOSE_FRAME, 
					/* content size and content */
					reason_size > 0 ? reason_size + 2 : 0, content, 
					/* sleep in header */
					0);

		/* release content (if defined) */
		nopoll_free (content);

		/* call to shutdown connection */
		nopoll_conn_shutdown (conn);
	} /* end if */

	/* unregister connection from context */
	refs = nopoll_conn_ref_count (conn);
	nopoll_ctx_unregister_conn (conn->ctx, conn);

	/* avoid calling next unref in the case not enough references
	 * are found */
	if (refs <= 1)
		return;

	/* call to unref connection */
	nopoll_conn_unref (conn);

	return;	
}

/** 
 * @brief Allows to close an opened \ref noPollConn no matter its role
 * (\ref noPollRole).
 *
 * @param conn The connection to close.
 *
 * There is available an alternative extended version that allows to
 * send the status code and the error message: \ref
 * nopoll_conn_close_ext
 */ 
void          nopoll_conn_close  (noPollConn  * conn)
{
	/* call to close without providing a reason */
	nopoll_conn_close_ext (conn, 0, NULL, 0);
	return;
}

/** 
 * @brief Allows to define a user level pointer associated to this
 * connection. This pointer can be later be retrieved using \ref
 * nopoll_conn_get_hook.
 *
 * @param conn The connection where the pointer will be associated.
 *
 * @param ptr The pointer to associate to the connection.
 */
void          nopoll_conn_set_hook (noPollConn * conn, noPollPtr ptr)
{
	if (conn == NULL)
		return;
	conn->hook = ptr;
	return;
}


/** 
 * @brief Allows to get the user level pointer defined by \ref
 * nopoll_conn_get_hook.
 *
 * @param conn The connection where the uesr level pointer was stored.
 *
 * @return A reference to the pointer stored.
 */
noPollPtr     nopoll_conn_get_hook (noPollConn * conn)
{
	if (conn == NULL)
		return NULL;
	return conn->hook;
}

/** 
 * @brief Allows to unref connection reference acquired via \ref
 * nopoll_conn_ref.
 *
 * @param conn The connection to be unrefered.
 */
void nopoll_conn_unref (noPollConn * conn)
{
	int value;
	
	if (conn == NULL)
		return;

	/* acquire here the mutex */
	nopoll_mutex_lock (conn->ref_mutex);

	conn->refs--;
	value = conn->refs;
	
	nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Releasing connection id %d reference, current ref count status is: %d", 
		    conn->id, value);
	/* release here the mutex */
	nopoll_mutex_unlock (conn->ref_mutex);
	
	if (value != 0) 
		return;

	/* release message */
	if (conn->pending_msg)
		nopoll_msg_unref (conn->pending_msg);

	/* release ctx */
	if (conn->ctx) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Released context refs, now: %d", conn->ctx->refs);
		nopoll_ctx_unref (conn->ctx);
	} /* end if */
	conn->ctx = NULL;

	/* free all internal strings */
	nopoll_free (conn->host);
	nopoll_free (conn->port);
	nopoll_free (conn->host_name);
	nopoll_free (conn->origin);
	nopoll_free (conn->protocols);
	nopoll_free (conn->accepted_protocol);
	nopoll_free (conn->get_url);

	/* close reason if any */
	nopoll_free (conn->peer_close_reason);

	/* release TLS certificates */
	nopoll_free (conn->certificate);
	nopoll_free (conn->private_key);
	nopoll_free (conn->chain_certificate);

	/* release uncomplete message */
	if (conn->previous_msg) 
		nopoll_msg_unref (conn->previous_msg);

	if (conn->ssl)
		SSL_free (conn->ssl);
	if (conn->ssl_ctx)
		SSL_CTX_free (conn->ssl_ctx);

	/* release handshake internal data */
	if (conn->handshake) {
		nopoll_free (conn->handshake->websocket_key);
		nopoll_free (conn->handshake->websocket_version);
		nopoll_free (conn->handshake->websocket_accept);
		nopoll_free (conn->handshake->expected_accept);
		nopoll_free (conn->handshake->cookie);
		nopoll_free (conn->handshake);
	} /* end if */

	/* release connection options if defined and reuse flag is not defined */
	if (conn->opts && ! conn->opts->reuse)
		nopoll_conn_opts_free (conn->opts);

	/* release pending write buffer */
	nopoll_free (conn->pending_write);

	/* release mutex */
	nopoll_mutex_destroy (conn->ref_mutex);

	nopoll_free (conn);	

	return;
}

/** 
 * @internal Default connection receive until handshake is complete.
 */
int nopoll_conn_default_receive (noPollConn * conn, char * buffer, int buffer_size)
{
	return recv (conn->session, buffer, buffer_size, 0);
}

/** 
 * @internal Default connection send until handshake is complete.
 */
int nopoll_conn_default_send (noPollConn * conn, char * buffer, int buffer_size)
{
	return send (conn->session, buffer, buffer_size, 0);
}

/** 
 * @internal Read the next line, byte by byte until it gets a \n or
 * maxlen is reached. Some code errors are used to manage exceptions
 * (see return values)
 * 
 * @param connection The connection where the read operation will be done.
 *
 * @param buffer A buffer to store content read from the network.
 *
 * @param maxlen max content to read from the network.
 * 
 * @return  values returned by this function follows:
 *  0 - remote peer have closed the connection
 * -1 - an error have happened while reading
 * -2 - could read because this connection is on non-blocking mode and there is no data.
 *  n - some data was read.
 * 
 **/
int          nopoll_conn_readline (noPollConn * conn, char  * buffer, int  maxlen)
{
	int         n, rc;
	int         desp;
	char        c, *ptr;
#if defined(SHOW_DEBUG_LOG)
# if !defined(SHOW_FORMAT_BUGS)
	noPollCtx * ctx = conn->ctx;
# endif
#endif

	/* clear the buffer received */
	/* memset (buffer, 0, maxlen * sizeof (char ));  */

	/* check for pending line read */
	desp         = 0;
	if (conn->pending_line) {
		/* get size and check exceeded values */
		desp = strlen (conn->pending_line);
		if (desp >= maxlen) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, 
				    "found fragmented frame line header but allowed size was exceeded (desp:%d >= maxlen:%d)",
				    desp, maxlen);
			nopoll_conn_shutdown (conn);
			return -1;
		} /* end if */

		/* now store content into the buffer */
		memcpy (buffer, conn->pending_line, desp);

		/* clear from the conn the line */
		nopoll_free (conn->pending_line);
		conn->pending_line = NULL;
	}

	/* read current next line */
	ptr = (buffer + desp);
	for (n = 1; n < (maxlen - desp); n++) {
	nopoll_readline_again:
		if (( rc = conn->receive (conn, &c, 1)) == 1) {
			*ptr++ = c;
			if (c == '\x0A')
				break;
		}else if (rc == 0) {
			if (n == 1)
				return 0;
			else
				break;
		} else {
			if (errno == NOPOLL_EINTR) 
				goto nopoll_readline_again;
			if ((errno == NOPOLL_EWOULDBLOCK) || (errno == NOPOLL_EAGAIN) || (rc == -2)) {
				if (n > 0) {
					/* store content read until now */
					if ((n + desp - 1) > 0) {
						buffer[n+desp - 1] = 0;
						conn->pending_line = nopoll_strdup (buffer);
					} /* end if */
				} /* end if */
				return -2;
			}

			if (nopoll_conn_is_ok (conn) && errno == 0 && rc == 0) {
				nopoll_log (ctx, NOPOLL_LEVEL_WARNING, "unable to read line, but errno is 0, and connection is ok, return to keep on trying..");
				return -2;
			}
			
			/* if the conn is closed, just return
			 * without logging a message */
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "unable to read line, error code errno: %d, rc: %d (%s)", 
				    errno, rc, strerror (errno));
			return (-1);
		}
	}
	*ptr = 0;
	return (n + desp);

}

/** 
 * @brief Allows to get the master listener that was used to accept
 * the provided connection that represents a listener connection.
 *
 * @param conn A listener connection that was accepted for which we
 * want to get the master listener connection. The connection must be
 * a \ref NOPOLL_ROLE_LISTENER (as reported by \ref nopoll_conn_role).
 *
 * @return A reference to the master listener connection or NULL if it
 * fails. It can only fail when NULL reference is received or the
 * connection is not a \ref NOPOLL_ROLE_LISTENER.
 */
noPollConn  * nopoll_conn_get_listener (noPollConn * conn)
{
	/* check if the incoming connection is a NOPOLL_ROLE_LISTENER
	 * that is an accepted connection */
	if (conn == NULL || conn->role != NOPOLL_ROLE_LISTENER)
		return NULL;

	return conn->listener;
}

void __nopoll_pack_content (char * buffer, int start, int bytes)
{
	int iterator = 0;
	while (iterator < bytes) {
		/* copy bytes to the begining of the array */
		buffer[iterator] = buffer[start + iterator];
		
		/* next position */
		iterator++;
	} /* end while */

	return;
}

/** 
 * @internal Function used to read bytes from the wire. 
 *
 * @return The function returns the number of bytes read, 0 when no
 * bytes were available and -1 when it fails.
 */
int         __nopoll_conn_receive  (noPollConn * conn, char  * buffer, int  maxlen)
{
	int         nread;

	if (conn->pending_buf_bytes > 0) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Calling with bytes we can reuse (%d), requested: %d",
			    conn->pending_buf_bytes, maxlen);

		if (conn->pending_buf_bytes >= maxlen) {
			/* we have more in the buffer to serve than
			 * requested, ok, copy into the buffer and
			 * pack */

			memcpy (buffer, conn->pending_buf, maxlen);
			__nopoll_pack_content (conn->pending_buf, maxlen, conn->pending_buf_bytes - maxlen);
			conn->pending_buf_bytes -= maxlen;
			return maxlen;
		} 

		/* ok, we don't have enough bytes to serve
		 * directly, so copy what we have */
		memcpy (buffer, conn->pending_buf, conn->pending_buf_bytes);

		/* copy number of bytes served to reduce next request */
		nread = conn->pending_buf_bytes;
		conn->pending_buf_bytes = 0;

		/* call again to get bytes reducing the request in the
		 * amount of bytes served */
		return __nopoll_conn_receive (conn, buffer + nread, maxlen - nread) + nread;
		
	} /* end if */

 keep_reading:
	/* clear buffer */
	/* memset (buffer, 0, maxlen * sizeof (char )); */
#if defined(NOPOLL_OS_UNIX)
	errno = 0;
#endif
	if ((nread = conn->receive (conn, buffer, maxlen)) == NOPOLL_SOCKET_ERROR) {
		/* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, " returning errno=%d (%s)", errno, strerror (errno)); */
		if (errno == NOPOLL_EAGAIN) 
			return 0;
		if (errno == NOPOLL_EWOULDBLOCK) 
			return 0;
		if (errno == NOPOLL_EINTR) 
			goto keep_reading;
		
		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "unable to readn=%d, error code was: %d (%s) (shutting down connection)", maxlen, errno, strerror (errno));
		nopoll_conn_shutdown (conn);
		return -1;
	}

	/* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, " returning bytes read = %d", nread); */
	if (nread == 0) {
		/* check for blocking operations */
		if (errno == NOPOLL_EAGAIN || errno == NOPOLL_EWOULDBLOCK) {
			nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "unable to read from conn-id=%d (%s:%s), connection is not ready (errno: %d : %s)",
				    conn->id, conn->host, conn->port, errno, strerror (errno));
			return 0;
		} /* end if */

		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "received connection close while reading from conn id %d (errno=%d : %s) (%d, %d, %d), shutting down connection..", 
			    conn->id, errno, strerror (errno),
			    NOPOLL_EAGAIN, NOPOLL_EWOULDBLOCK, NOPOLL_EINTR);
		nopoll_conn_shutdown (conn);
	} /* end if */

	/* ensure we don't access outside the array */
	if (nread < 0) 
		nread = 0;

	buffer[nread] = 0;
	return nread;
}

nopoll_bool nopoll_conn_get_http_url (noPollConn * conn, const char * buffer, int buffer_size, const char * method, char ** url)
{
	int          iterator;
	int          iterator2;
#if defined(SHOW_DEBUG_LOG)
# if ! defined(SHOW_FORMAT_BUGS)
	noPollCtx  * ctx = conn->ctx;
# endif
#endif

	/* check if we already received method */
	if (conn->get_url) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received GET method declartion when it was already received during handshake..closing session");
		nopoll_conn_shutdown (conn);
		return nopoll_false;
	} /* end if */
	
	/* the get url must have a minimum size: GET / HTTP/1.1\r\n 16 (15 if only \n) */
	if (buffer_size < 15) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received uncomplete GET method during handshake, closing session");
		nopoll_conn_shutdown (conn);
		return nopoll_false;
	} /* end if */
	
	/* skip white spaces */
	iterator = 4;
	while (iterator <  buffer_size && buffer[iterator] == ' ') 
		iterator++;
	if (buffer_size == iterator) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received a %s method without an starting request url, closing session", method);
		nopoll_conn_shutdown (conn);
		return nopoll_false;
	} /* end if */

	/* now check url format */
	if (buffer[iterator] != '/') {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received a %s method with a request url that do not start with /, closing session", method);
		nopoll_conn_shutdown (conn);
		return nopoll_false;
	}
	
	/* ok now find the rest of the url content util the next white space */
	iterator2 = (iterator + 1);
	while (iterator2 <  buffer_size && buffer[iterator2] != ' ') 
		iterator2++;
	if (buffer_size == iterator2) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received a %s method with an uncomplate request url, closing session", method);
		nopoll_conn_shutdown (conn);
		return nopoll_false;
	} /* end if */
	
	(*url) = nopoll_new (char, iterator2 - iterator + 1);
	memcpy (*url, buffer + iterator, iterator2 - iterator);
	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Found url method: '%s'", *url);

	/* now check final HTTP header */
	iterator = iterator2 + 1;
	while (iterator <  buffer_size && buffer[iterator] == ' ') 
		iterator++;
	if (buffer_size == iterator) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received a %s method with an uncomplate request url, closing session", method);
		nopoll_conn_shutdown (conn);
		return nopoll_false;
	} /* end if */

	/* now check trailing content */
	return nopoll_cmp (buffer + iterator, "HTTP/1.1\r\n") || nopoll_cmp (buffer + iterator, "HTTP/1.1\n");
}

/** 
 * @internal Function that parses the mime header found on the
 * provided buffer.
 */
nopoll_bool nopoll_conn_get_mime_header (noPollCtx * ctx, noPollConn * conn, const char * buffer, int buffer_size, char ** header, char ** value)
{
	int iterator = 0;
	int iterator2 = 0;

	if (buffer_size < 3) {
	        nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Expected to find mime header content (but buffer size %d was found)", buffer_size);
		return nopoll_false;
	}

	/* nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Processing content: %s", buffer); */
	
	/* ok, find the first : */
	while (iterator < buffer_size && buffer[iterator] && buffer[iterator] != ':')
		iterator++;
	if (buffer[iterator] != ':') {
	        /* ensure print always ends */
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Expected to find mime header separator : but it wasn't found, buffer_size=%d, iterator=%d..",
			    buffer_size, iterator);
		return nopoll_false;
	} 

	/* copy the header value */
	(*header) = nopoll_new (char, iterator + 1);
	memcpy (*header, buffer, iterator);
	
	/* now get the mime header value */
	iterator2 = iterator + 1;
	while (iterator2 < buffer_size && buffer[iterator2] && buffer[iterator2] != '\n')
		iterator2++;
	if (buffer[iterator2] != '\n') {
	        nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, 
			    "Expected to find mime header value end (13) but it wasn't found (iterator=%d, iterator2=%d, for header: [%s], found value: [%d])..",
			    iterator, iterator2, (*header), (int)buffer[iterator2]);
		nopoll_free (*header);
		(*header) = NULL;
		(*value)  = NULL;
		return nopoll_false;
	} 

	/* copy the value */
	(*value) = nopoll_new (char, (iterator2 - iterator) + 1);
	memcpy (*value, buffer + iterator + 1, iterator2 - iterator);

	/* trim content */
	nopoll_trim (*value, NULL);
	nopoll_trim (*header, NULL);
	
	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Found MIME header: '%s' -> '%s'", *header, *value);
	return nopoll_true;
}

/** 
 * @internal Function that ensures we don't receive any 
 */ 
nopoll_bool nopoll_conn_check_mime_header_repeated (noPollConn   * conn, 
						    char         * header, 
						    char         * value, 
						    const char   * ref_header, 
						    noPollPtr      check)
{
	if (strcasecmp (ref_header, header) == 0) {
		if (check) {
			nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Provided header %s twice, closing connection", header);
			nopoll_free (header);
			nopoll_free (value);
			nopoll_conn_shutdown (conn);
			return nopoll_true;
		} /* end if */
	} /* end if */
	return nopoll_false;
}

char * nopoll_conn_produce_accept_key (noPollCtx * ctx, const char * websocket_key)
{
	const char    * static_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";	
	char          * accept_key;	
	int             accept_key_size;
	int             key_length;
	unsigned char   buffer[EVP_MAX_MD_SIZE];
	EVP_MD_CTX      mdctx;
	const EVP_MD  * md = NULL;
	unsigned int    md_len = EVP_MAX_MD_SIZE;

	if (websocket_key == NULL)
		return NULL;

	key_length  = strlen (websocket_key);

	accept_key_size = key_length + 36 + 1;
	accept_key      = nopoll_new (char, accept_key_size);

	memcpy (accept_key, websocket_key, key_length);
	memcpy (accept_key + key_length, static_guid, 36);
	
	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "base key value: %s", accept_key);

	/* now sha-1 */
	md = EVP_sha1 ();
	EVP_DigestInit (&mdctx, md);
	EVP_DigestUpdate (&mdctx, accept_key, strlen (accept_key));
	EVP_DigestFinal (&mdctx, buffer, &md_len);

	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Sha-1 length is: %u", md_len);
	/* now convert into base64 */
	if (! nopoll_base64_encode ((const char *) buffer, md_len, (char *) accept_key, &accept_key_size)) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to base64 sec-websocket-key value..");
		return NULL;
	}

	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Returning Sec-Websocket-Accept: %s", accept_key);
	
	return accept_key;
	
}

nopoll_bool nopoll_conn_complete_handshake_check_listener (noPollCtx * ctx, noPollConn * conn)
{
	char                 * reply;
	int                    reply_size;
	char                 * accept_key;
	noPollActionHandler    on_ready;
	noPollPtr              on_ready_data;
	const char           * protocol;
	nopoll_bool            origin_check;

	/* call to check listener handshake */
	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Checking client handshake data..");

	/* update default origin check (Origin: header must be
	 * defined, event though RFC says SHOULD it should have been
	 * MUST because other wise, server side cannot check this
	 * value which is crucial for security reasons */
	origin_check = conn->origin != NULL;

	/* now, update this origin_check in the case it is not defined
	 * and origin header skip was enabled */
	if ( conn->listener && conn->listener->opts && conn->listener->opts->skip_origin_header_check && conn->origin == NULL)
		origin_check = nopoll_true;
	

	/* ensure we have all minumum data */
	if (! conn->handshake->upgrade_websocket ||
	    ! conn->handshake->connection_upgrade ||
	    ! conn->handshake->websocket_key ||
	    ! origin_check ||  /* see above */
	    ! conn->handshake->websocket_version) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Client from %s:%s didn't provide all websocket handshake values required, closing session (Upgraded: websocket %d, Connection: upgrade%d, Sec-WebSocket-Key: %p, Origin: %p, Sec-WebSocket-Version: %p)",
			    conn->host, conn->port,
			    conn->handshake->upgrade_websocket,
			    conn->handshake->connection_upgrade,
			    conn->handshake->websocket_key,
			    conn->origin,
			    conn->handshake->websocket_version);
		return nopoll_false;
	} /* end if */

	/* check protocol support */
	if (strtod (conn->handshake->websocket_version, NULL) != ctx->protocol_version) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received request for an unsupported protocol version: %s, expected: %d",
			    conn->handshake->websocket_version, ctx->protocol_version);
		return nopoll_false;
	} /* end if */
	
	/* now call the user app level to accept the websocket
	   connection */
	if (ctx->on_open) {
		if (! ctx->on_open (ctx, conn, ctx->on_open_data)) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Client from %s:%s was denied by application level (on open handler %p), clossing session", 
				    conn->host, conn->port, ctx->on_open);
			nopoll_conn_shutdown (conn);
			return nopoll_false;
		}
	} /* end if */
	
	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Client from %s:%s was accepted, replying accept",
		    conn->host, conn->port);

	/* produce accept key */
	accept_key = nopoll_conn_produce_accept_key (ctx, conn->handshake->websocket_key);
	
	/* ok, send handshake reply */
	if (conn->protocols || conn->accepted_protocol) {
		/* set protocol in the reply taking preference by the
		   value configured at conn->accepted_protocol */
		protocol = conn->accepted_protocol;
		if (! protocol)
			protocol = conn->protocols;

		/* send accept header accepting protocol requested by the user */
		reply = nopoll_strdup_printf ("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\nSec-WebSocket-Protocol: %s\r\n\r\n", 
					      accept_key, protocol);
	} else {
		/* send accept header without telling anything about protocols */
		reply = nopoll_strdup_printf ("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\n\r\n", 
					      accept_key);
	}
		
	nopoll_free (accept_key);
	if (reply == NULL) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to build reply, closing session");
		return nopoll_false;
	} /* end if */
	
	reply_size = strlen (reply);
	if (reply_size != conn->send (conn, reply, reply_size)) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to send reply, there was a failure, error code was: %d", errno);
		nopoll_free (reply);
		return nopoll_false;
	} /* end if */
	
	/* free reply */
	nopoll_free (reply);

	/* now call the user app level to accept the websocket
	   connection */
	if (ctx->on_ready || conn->on_ready) {
		/* set on ready handler, first considering conn->on_ready and then ctx->on_ready */
		on_ready      = conn->on_ready;
		on_ready_data = conn->on_ready_data;
		if (! on_ready) {
			on_ready      = ctx->on_ready;
			on_ready_data = ctx->on_ready_data;
		} /* end if */

		if (on_ready &&  ! on_ready (ctx, conn, on_ready_data)) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Client from %s:%s was denied by application level (on ready handler: %p), clossing session", 
				    conn->host, conn->port, on_ready);
			nopoll_conn_shutdown (conn);
			return nopoll_false;
		}
	} /* end if */
	
	return nopoll_true; /* signal handshake was completed */
}

nopoll_bool nopoll_conn_complete_handshake_check_client (noPollCtx * ctx, noPollConn * conn)
{
	char         * accept;
	nopoll_bool    result;

	/* check all data received */
	if (! conn->handshake->websocket_accept ||
	    ! conn->handshake->upgrade_websocket ||
	    ! conn->handshake->connection_upgrade) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received uncomplete listener handshake reply (%p %d %d)",
			    conn->handshake->websocket_accept, conn->handshake->upgrade_websocket, conn->handshake->connection_upgrade);
		return nopoll_false;
	} /* end if */

	/* check accept value here */
	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Checking accept key from listener..");
	accept = nopoll_conn_produce_accept_key (ctx, conn->handshake->websocket_key);
	
	result = nopoll_cmp (accept, conn->handshake->websocket_key);
	if (! result) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to accept connection Sec-Websocket-Accept %s is not expected %s, closing session",
			    accept, conn->handshake->websocket_key);
		nopoll_conn_shutdown (conn);
	}
	nopoll_free (accept);

	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Sec-Websocket-Accept matches expected value..nopoll_conn_complete_handshake_check_client (%p, %p)=%d",
		    ctx, conn, result);

	return result;
}


/** 
 * @internal Function used to validate all handshake received until
 * now.
 */
void nopoll_conn_complete_handshake_check (noPollConn * conn)
{
	noPollCtx    * ctx    = conn->ctx;
	nopoll_bool    result = nopoll_false;

	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "calling to check handshake received on connection id %d role %d..",
		    conn->id, conn->role);

	if (conn->role == NOPOLL_ROLE_LISTENER) {
		result = nopoll_conn_complete_handshake_check_listener (ctx, conn);
	} else if (conn->role == NOPOLL_ROLE_CLIENT) {
		result = nopoll_conn_complete_handshake_check_client (ctx, conn);
	} /* end if */

	/* flag connection as ready: now we can get messages */
	if (result) {
		conn->handshake_ok = nopoll_true;
	} else {
		nopoll_conn_shutdown (conn);
	} /* end if */

	return;
}

/** 
 * @internal Handler that implements one step of the websocket
 * listener handshake received from client, until it is completed.
 *
 * @return Returns 0 to return (because error or because no data is
 * available) or 1 to signal the caller continue reading if it is
 * possible.
 */
int nopoll_conn_complete_handshake_listener (noPollCtx * ctx, noPollConn * conn, char * buffer, int buffer_size)
{
	char * header;
	char * value;

	/* handle content */
	if (nopoll_ncmp (buffer, "GET ", 4)) {
		/* get url method */
		nopoll_conn_get_http_url (conn, buffer, buffer_size, "GET", &conn->get_url);
		return 1;
	} /* end if */

	/* get mime header */
	if (! nopoll_conn_get_mime_header (ctx, conn, buffer, buffer_size, &header, &value)) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to acquire mime header from remote peer during handshake, closing connection");
		nopoll_conn_shutdown (conn);
		return 0;
	}
		
	/* ok, process here predefined headers */
	if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Host", conn->host_name))
		return 0;
	if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Upgrade", INT_TO_PTR (conn->handshake->upgrade_websocket))) 
		return 0;
	if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Connection", INT_TO_PTR (conn->handshake->connection_upgrade))) 
		return 0;
	if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Sec-WebSocket-Key", conn->handshake->websocket_key)) 
		return 0;
	if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Origin", conn->origin)) 
		return 0;
	if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Sec-WebSocket-Protocol", conn->protocols)) 
		return 0;
	if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Sec-WebSocket-Version", conn->handshake->websocket_version)) 
		return 0;
	if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Cookie", conn->handshake->cookie)) 
		return 0;
	
	/* set the value if required */
	if (strcasecmp (header, "Host") == 0)
		conn->host_name = value;
	else if (strcasecmp (header, "Sec-Websocket-Key") == 0)
		conn->handshake->websocket_key = value;
	else if (strcasecmp (header, "Origin") == 0)
		conn->origin = value;
	else if (strcasecmp (header, "Sec-Websocket-Protocol") == 0)
		conn->protocols = value;
	else if (strcasecmp (header, "Sec-Websocket-Version") == 0)
		conn->handshake->websocket_version = value;
	else if (strcasecmp (header, "Upgrade") == 0) {
		conn->handshake->upgrade_websocket = 1;
		nopoll_free (value);
	} else if (strcasecmp (header, "Connection") == 0) {
		conn->handshake->connection_upgrade = 1;
		nopoll_free (value);
	} else if (strcasecmp (header, "Cookie") == 0) {
		/* record cookie so it can be used by the application level */
		conn->handshake->cookie = value;
	} else {
		/* release value, no body claimed it */
		nopoll_free (value);
	} /* end if */
	
	/* release the header */
	nopoll_free (header);

	return 1; /* continue reading lines */
}

/** 
 * @internal Handler that implements one step of the websocket
 * client handshake received from the server, until it is completed. 
 *
 * @return Returns 0 to return (because error or because no data is
 * available) or 1 to signal the caller continue reading if it is
 * possible.
 */
int nopoll_conn_complete_handshake_client (noPollCtx * ctx, noPollConn * conn, char * buffer, int buffer_size)
{
	char * header;
	char * value;
	int    iterator;

	/* handle content */
	if (! conn->handshake->received_101 && nopoll_ncmp (buffer, "HTTP/1.1 ", 9)) {
		iterator = 9;
		while (iterator < buffer_size && buffer[iterator] && buffer[iterator] == ' ')
			iterator++;
		if (! nopoll_ncmp (buffer + iterator, "101", 3)) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "websocket server denied connection with: %s", buffer + iterator);
			return 0; /* do not continue */
		} /* end if */

		/* flag that we have received HTTP/1.1 101 indication */
		conn->handshake->received_101 = nopoll_true;

		return 1;
	} /* end if */

	/* get mime header */
	if (! nopoll_conn_get_mime_header (ctx, conn, buffer, buffer_size, &header, &value)) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to acquire mime header from remote peer during handshake, closing connection");
		nopoll_conn_shutdown (conn);
		return 0;
	}
		
	/* ok, process here predefined headers */
	if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Upgrade", INT_TO_PTR (conn->handshake->upgrade_websocket))) 
		return 0;
	if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Connection", INT_TO_PTR (conn->handshake->connection_upgrade))) 
		return 0;
	if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Sec-WebSocket-Accept", conn->handshake->websocket_accept)) 
		return 0;
	if (nopoll_conn_check_mime_header_repeated (conn, header, value, "Sec-WebSocket-Protocol", conn->accepted_protocol)) 
		return 0;
	
	/* set the value if required */
	if (strcasecmp (header, "Sec-Websocket-Accept") == 0)
		conn->handshake->websocket_accept = value;
	else if (strcasecmp (header, "Sec-Websocket-Protocol") == 0)
		conn->accepted_protocol = value;
	else if (strcasecmp (header, "Upgrade") == 0) {
		conn->handshake->upgrade_websocket = 1;
		nopoll_free (value);
	} else if (strcasecmp (header, "Connection") == 0) {
		conn->handshake->connection_upgrade = 1;
		nopoll_free (value);
	} else {
		/* release value, no body claimed it */
		nopoll_free (value);
	} /* end if */
	
	/* release the header */
	nopoll_free (header);

	return 1; /* continue reading lines */
}

/** 
 * @internal Function that completes the handshake in an non-blocking
 * manner taking into consideration the connection type (listener or
 * client).
 */
void nopoll_conn_complete_handshake (noPollConn * conn)
{
	char        buffer[1024];
	int         buffer_size;
	noPollCtx * ctx = conn->ctx;

	/* ensure we didn't complete handshake */
	if (conn->handshake_ok)
		return;

	nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Checking to complete conn-id=%d WebSocket handshake, role %d", conn->id, conn->role);

	/* ensure handshake object is created */
	if (conn->handshake == NULL)
		conn->handshake = nopoll_new (noPollHandShake, 1);

	/* get lines and complete the handshake data */
	while (nopoll_true) {
		/* clear buffer for debugging functions */
		buffer[0] = 0;
		/* get next line to process */
		buffer_size = nopoll_conn_readline (conn, buffer, 1024);
		if (buffer_size == 0 || buffer_size == -1) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unexpected connection close during handshake..closing connection");
			nopoll_conn_shutdown (conn);
			return;
		} /* end if */

		/* no data at this moment, return to avoid consuming data */
		if (buffer_size == -2) {
			nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "No more data available on connection id %d", conn->id);
			return;
		}

		/* drop a debug line */
		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Bytes read %d from connection id %d: %s", buffer_size, conn->id, buffer);  
			
		/* check if we have received the end of the
		   websocket client handshake */
		if (buffer_size == 2 && nopoll_ncmp (buffer, "\r\n", 2)) {
			nopoll_conn_complete_handshake_check (conn);
			return;
		}

		if (conn->role == NOPOLL_ROLE_LISTENER) {
			/* call to complete listener handshake */
			if (nopoll_conn_complete_handshake_listener (ctx, conn, buffer, buffer_size) == 1)
				continue;
		} else if (conn->role == NOPOLL_ROLE_CLIENT) {
			/* call to complete listener handshake */
			if (nopoll_conn_complete_handshake_client (ctx, conn, buffer, buffer_size) == 1)
				continue;
		} else {
			nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Called to handle connection handshake on a connection with an unexpected role: %d, closing session",
				    conn->role);
			nopoll_conn_shutdown (conn);
			return;
		}
	} /* end while */

	return;
}

void nopoll_conn_mask_content (noPollCtx * ctx, char * payload, int payload_size, char * mask, int desp)
{
	int iter       = 0;
	int mask_index = 0;

	while (iter < payload_size) {
		/* rotate mask and apply it */
		mask_index = (iter + desp) % 4;
		payload[iter] ^= mask[mask_index];
		iter++;
	} /* end while */

	return;
} 


/** 
 * @brief Allows to get the next message available on the provided
 * connection. The function returns NULL in the case no message is
 * still ready to be returned. 
 *
 * The function do not block. 
 *
 * @param conn The connection where the read operation will take
 * place.
 * 
 * @return A reference to a noPollMsg object or NULL if there is
 * nothing available. In case the function returns NULL, check
 * connection status with \ref nopoll_conn_is_ok.
 */
noPollMsg   * nopoll_conn_get_msg (noPollConn * conn)
{
	char        buffer[20];
	int         bytes;
	noPollMsg * msg;
	int         ssl_error;
	int         header_size;
#if defined(SHOW_DEBUG_LOG)
	long        result;
#endif
#if defined(NOPOLL_64BIT_PLATFORM)
	unsigned char *len;
#endif

	if (conn == NULL)
		return NULL;

	nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, 
		    "=== START: conn-id=%d (errno=%d, session: %d, conn->handshake_ok: %d, conn->pending_ssl_accept: %d) ===", 
		    conn->id, errno, conn->session, conn->handshake_ok, conn->pending_ssl_accept);
	
	/* check for accept SSL connection */
	if (conn->pending_ssl_accept) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Received connect over a connection (id %d) with TLS handshake pending to be finished, processing..",
			    conn->id);

		/* get ssl error */
		ssl_error = SSL_accept (conn->ssl);
		if (ssl_error == -1) {
			/* get error */
			ssl_error = SSL_get_error (conn->ssl, -1);
 
			nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "accept function have failed (for listener side) ssl_error=%d : dumping error stack..", ssl_error);
 
			switch (ssl_error) {
			case SSL_ERROR_WANT_READ:
			        nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "still not prepared to continue because read wanted conn-id=%d (%p, session %d)",
					    conn->id, conn, conn->session);
				return NULL;
			case SSL_ERROR_WANT_WRITE:
			        nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "still not prepared to continue because write wanted conn-id=%d (%p)",
					    conn->id, conn);
				return NULL;
			default:
				break;
			} /* end switch */

			/* TLS-fication process have failed */
			nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "there was an error while accepting TLS connection");
			nopoll_conn_log_ssl (conn);
			nopoll_conn_shutdown (conn);
			return NULL;
		} /* end if */

		/* ssl accept */
		conn->pending_ssl_accept = nopoll_false;
		nopoll_conn_set_sock_block (conn->session, nopoll_false);

#if defined(SHOW_DEBUG_LOG)
		result = SSL_get_verify_result (conn->ssl);
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Completed TLS operation from %s:%s (conn id %d, ssl veriry result: %d)",
			    conn->host, conn->port, conn->id, (int) result);
#endif

		/* configure default handlers */
		conn->receive = nopoll_conn_tls_receive;
		conn->send    = nopoll_conn_tls_send;

		/* call to check post ssl checks after SSL finalization */
		if (conn->ctx && conn->ctx->post_ssl_check) {
			if (! conn->ctx->post_ssl_check (conn->ctx, conn, conn->ssl_ctx, conn->ssl, conn->ctx->post_ssl_check_data)) {
				/* TLS post check failed */
				nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "TLS/SSL post check function failed, dropping connection");
				nopoll_conn_shutdown (conn);
				return NULL;
			} /* end if */
		} /* end if */

		/* set this connection has TLS ok */
		conn->tls_on  = nopoll_true;

#if defined(NOPOLL_OS_UNIX)
		/* report NULL because this was a call to complete TLS */
		errno = NOPOLL_EWOULDBLOCK; /* simulate there is no data available to stop
					       here. If there is no data indeed, on next
					       call it will not fail */
#endif
		return NULL;
		
	} /* end if */

	/* check connection status */
	if (! conn->handshake_ok) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Connection id %d handshake is not complete, running..", conn->id);
		/* acquire here handshake mutex */
		nopoll_mutex_lock (conn->ref_mutex);

		/* complete handshake */
		nopoll_conn_complete_handshake (conn);

		/* release here handshake mutex */
		nopoll_mutex_unlock (conn->ref_mutex);

		if (! conn->handshake_ok) 
			return NULL;
	} /* end if */

	if (conn->previous_msg) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Reading bytes (previously read %d) from a previous unfinished frame (pending: %d) over conn-id=%d",
			    conn->previous_msg->payload_size, conn->previous_msg->remain_bytes, conn->id);

		if (conn->read_pending_header) {
			/* detected pending read header, continue from here */
			msg                       = conn->previous_msg;
			conn->previous_msg        = NULL;
			conn->read_pending_header = nopoll_false;
			goto read_pending_header;
		} /* end if */
		
		/* build next message holder to continue with this content */
		if (conn->previous_msg->payload_size > 0) {
			msg = nopoll_msg_new ();
			if (msg == NULL) {
				nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Failed to allocate memory for received message, closing session id: %d", 
					    conn->id);
				nopoll_conn_shutdown (conn);
				return NULL;
			} /* end if */

			/* flag this message as a fragment */
			msg->is_fragment = nopoll_true;

			/* get fin bytes */
			msg->has_fin      = 1; /* for now final message */
			msg->op_code      = 0; /* continuation frame */

			/* copy initial mask indication */
			msg->is_masked    = conn->previous_msg->is_masked;
			msg->payload_size = conn->previous_msg->remain_bytes;
			msg->unmask_desp  = conn->previous_msg->unmask_desp;

			/* copy mask */
			memcpy (msg->mask, conn->previous_msg->mask, 4);
			
			if (msg->is_masked) {
				nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Reusing mask value = %d from previous frame (%d)", nopoll_get_32bit (msg->mask));
				nopoll_show_byte (conn->ctx, msg->mask[0], "mask[0]");
				nopoll_show_byte (conn->ctx, msg->mask[1], "mask[1]");
				nopoll_show_byte (conn->ctx, msg->mask[2], "mask[2]");
				nopoll_show_byte (conn->ctx, msg->mask[3], "mask[3]");
			}

			/* release previous reference because de don't need it anymore */
			nopoll_msg_unref (conn->previous_msg);
		} else {
			/* reuse reference */
			msg = conn->previous_msg;

			/* update remaining bytes */
			msg->payload_size = msg->remain_bytes;
			nopoll_free (msg->payload);
			nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "reusing noPollMsg reference (%p) since last payload read was 0, remaining: %d", msg,
				    msg->payload_size);
		}

		/* nullify references */
		conn->previous_msg      = NULL;

		goto read_payload;
		
	} /* end if */

	/*
	  0                   1                   2                   3
	  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
	  +-+-+-+-+-------+-+-------------+-------------------------------+
	  |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
	  |I|S|S|S|  (4)  |A|     (7)     |             (16/63)           |
	  |N|V|V|V|       |S|             |   (if payload len==126/127)   |
	  | |1|2|3|       |K|             |                               |
	  +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
	  |     Extended payload length continued, if payload len == 127  |
	  + - - - - - - - - - - - - - - - +-------------------------------+
	  |                               |Masking-key, if MASK set to 1  |
	  +-------------------------------+-------------------------------+
	  | Masking-key (continued)       |          Payload Data         |
	  +-------------------------------- - - - - - - - - - - - - - - - +
	  :                     Payload Data continued ...                :
	  + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
	  |                     Payload Data continued ...                |
	  +---------------------------------------------------------------+
	*/
	/* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Found data in opened connection id %d..", conn->id);*/ 

	/* get the first 2 bytes from the websocket header */
	bytes = __nopoll_conn_receive (conn, buffer, 2);
	if (bytes == 0) {
		/* connection not ready */
		nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Connection id=%d without data, errno=%d : %s, returning no message", 
			    conn->id, errno, strerror (errno));
		return NULL;
	}

	if (bytes <= 0) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Received connection close, finishing connection session");
		nopoll_conn_shutdown (conn);
		return NULL;
	} /* end if */

	if (bytes != 2) { 
		/* ok, store content read into the pending buffer for next call */
		memcpy (conn->pending_buf + conn->pending_buf_bytes, buffer, bytes);
		conn->pending_buf_bytes += bytes;
		
		nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, 
			    "Expected to receive complete websocket frame header but found only %d bytes over conn-id=%d, saving to reuse later",
			    bytes, conn->id);
		return NULL;
	} /* end if */

	/* record header size */
	header_size = 2;

	nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Received %d bytes for websocket header", bytes);
	nopoll_show_byte (conn->ctx, buffer[0], "header[0]");
	nopoll_show_byte (conn->ctx, buffer[1], "header[1]");

	/* build next message */
	msg = nopoll_msg_new ();
	if (msg == NULL) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Failed to allocate memory for received message, closing session id: %d", 
			    conn->id);
		nopoll_conn_shutdown (conn);
		return NULL;
	} /* end if */

	/* get fin bytes */
	msg->has_fin      = nopoll_get_bit (buffer[0], 7);
	msg->op_code      = buffer[0] & 0x0F;
	msg->is_masked    = nopoll_get_bit (buffer[1], 7);
	msg->payload_size = buffer[1] & 0x7F;

	/* ensure FIN = 1 in case we are listener */
	if (conn->role == NOPOLL_ROLE_LISTENER && ! msg->is_masked) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Received websocket frame with mask bit set to zero, closing session id: %d", 
			    conn->id);
		nopoll_msg_unref (msg);
		nopoll_conn_shutdown (conn);
		return NULL;
	} /* end if */

	/* check payload size value */
	if (msg->payload_size < 0) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Received wrong payload size at first 7 bits, closing session id: %d", 
			    conn->id);
		nopoll_msg_unref (msg);
		nopoll_conn_shutdown (conn);
		return NULL;
	} /* end if */

 read_pending_header:
	nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "interim payload size received: %d", (int) msg->payload_size);

	/* read the rest */
	if (msg->payload_size < 126) {
		/* nothing to declare here */

	} else if (msg->payload_size == 126) {
		/* get extended 2 bytes length as unsigned 16 bit
		   unsigned integer */
		bytes = __nopoll_conn_receive (conn, buffer + 2, 2);
		if (bytes != 2) {
			nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Failed to get next 2 bytes to read header from the wire, but received=%d, failed to received content, shutting down id=%d the connection, errno=%d (%s)",
				    bytes, conn->id, errno, strerror (errno));
			if (errno == NOPOLL_EWOULDBLOCK || errno == 0) { 
				/* connection is not ready at this point */
				conn->previous_msg = msg;
				conn->read_pending_header = nopoll_true;

				/* check amount of bytes to reuse them */
				nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Detected broken WebSocket peer sending header content using different frames, trying to save and resume later");
				if (bytes > 0) {
					/* ok, store content read into the pending buffer for next call */
					memcpy (conn->pending_buf + conn->pending_buf_bytes, buffer + 2, bytes);
					conn->pending_buf_bytes += bytes;
				}
				
				return NULL;
			}

			nopoll_msg_unref (msg);
			nopoll_conn_shutdown (conn);
			return NULL; 	
		} /* end if */

		/* add to the header bytes read */
		header_size += bytes;
			
		msg->payload_size = nopoll_get_16bit (buffer + 2);

		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Received (%d) bytes in header (size %d) for payload size indication, which finally is: %d", bytes, header_size,(int) msg->payload_size);
		
	} else if (msg->payload_size == 127) {
#if defined(NOPOLL_64BIT_PLATFORM)
		/* read more content (next 8 bytes) */
		if ((bytes = __nopoll_conn_receive (conn, buffer, 8)) != 8) {
			nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, 
				    "Expected to receive next 6 bytes for websocket frame header but found only %d bytes, closing session: %d",
				    bytes, conn->id);
			nopoll_conn_shutdown (conn);
			return NULL;
		} /* end if */

                len = (unsigned char*)buffer;
		msg->payload_size = 0;
		msg->payload_size |= ((long)(len[0]) << 56);
		msg->payload_size |= ((long)(len[1]) << 48);
		msg->payload_size |= ((long)(len[2]) << 40);
		msg->payload_size |= ((long)(len[3]) << 32);
		msg->payload_size |= ((long)(len[4]) << 24);
		msg->payload_size |= ((long)(len[5]) << 16);
		msg->payload_size |= ((long)(len[6]) << 8);
		msg->payload_size |= len[7];
#else
		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "noPoll doesn't support messages bigger than 65k on this plataform (support for 64bit not found)");
		nopoll_msg_unref (msg);
		nopoll_conn_shutdown (conn);
		return NULL;
#endif
	} /* end if */

	if (msg->op_code == NOPOLL_PONG_FRAME) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "PONG received over connection id=%d", conn->id);
		nopoll_msg_unref (msg);
		return NULL;
	} /* end if */

	if (msg->op_code == NOPOLL_CLOSE_FRAME) {
		if (msg->payload_size == 0) {
			/* nothing more to add here, close frame
			   without content received, so we have no
			   reason to keep on reading */
			nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Proper connection close frame received id=%d, shutting down", conn->id);
			nopoll_msg_unref (msg);
			nopoll_conn_shutdown (conn);
			return NULL;
		} /* end if */

		/* received close frame with content, try to read the content */
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Proper connection close frame received id=%d with content bytes=%d, reading reason..", 
			    conn->id, msg->payload_size);
	} /* end if */

	if (msg->op_code == NOPOLL_PING_FRAME) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "PING received over connection id=%d, replying PONG", conn->id);
		nopoll_msg_unref (msg);

		/* call to send pong */
		nopoll_conn_send_pong (conn);

		return NULL;
	} /* end if */

	/* get more bytes */
	if (msg->is_masked) {
		bytes = __nopoll_conn_receive (conn, (char *) msg->mask, 4);
		if (bytes != 4) {
			/* record header read so far */
			memcpy (conn->pending_buf, buffer, header_size);
			conn->pending_buf_bytes = header_size;
			/* record mask read so far if required */
			if (bytes > 0) {
				memcpy (conn->pending_buf + header_size, msg->mask, bytes);
				conn->pending_buf_bytes += bytes;
			} /* end if */

			/* release message because it not available here */
			nopoll_msg_unref (msg);
			if (bytes >= 0 && nopoll_conn_is_ok (conn)) {
				nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, 
					    "Expected to receive incoming mask after header (4 bytes) but found %d bytes on conn-id=%d, saving %d for future operations ", 
					    bytes, conn->id, conn->pending_buf_bytes);
				return NULL;
			} /* end if */

			nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Expected to receive incoming mask after header (4 bytes) but found %d bytes, shutting down id=%d the connection", 
				    bytes, conn->id);
			nopoll_conn_shutdown (conn);
			return NULL; 
		} /* end if */
		
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Received mask value = %d", nopoll_get_32bit (msg->mask));
		nopoll_show_byte (conn->ctx, msg->mask[0], "mask[0]");
		nopoll_show_byte (conn->ctx, msg->mask[1], "mask[1]");
		nopoll_show_byte (conn->ctx, msg->mask[2], "mask[2]");
		nopoll_show_byte (conn->ctx, msg->mask[3], "mask[3]");
	} /* end if */

	/* check payload size */
	if (msg->payload_size == 0) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Found incoming frame with payload size 0, shutting down id=%d the connection", conn->id);
		nopoll_msg_unref (msg);
		nopoll_conn_shutdown (conn);
		return NULL; 	
	} /* end if */

	nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Detected incoming websocket frame: fin(%d), op_code(%d), is_masked(%d), payload size(%ld), mask=%d", 
		    msg->has_fin, msg->op_code, msg->is_masked, msg->payload_size, nopoll_get_32bit (msg->mask));

	/* check here for the limit of message we are willing to accept */
	/* FIX SECURITY ISSUE */

read_payload:

	/* copy payload received */
	msg->payload = nopoll_new (char, msg->payload_size + 1);
	if (msg->payload == NULL) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Unable to acquire memory to read the incoming frame, dropping connection id=%d", conn->id);
		nopoll_msg_unref (msg);
		nopoll_conn_shutdown (conn);
		return NULL;		
	} /* end if */

	bytes = __nopoll_conn_receive (conn, (char *) msg->payload, msg->payload_size);
	if (bytes < 0) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Connection lost during message reception, dropping connection id=%d, bytes=%d, errno=%d : %s", 
			    conn->id, bytes, errno, strerror (errno));
		nopoll_msg_unref (msg);
		nopoll_conn_shutdown (conn);
		return NULL;		
	} /* end if */

	if (bytes != msg->payload_size) {
		/* record we've got content pending to be read */
		msg->remain_bytes = msg->payload_size - bytes;

		/* set connection in remaining data to read */
		nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Received fewer bytes than expected (bytes: %d < payload size: %d)", 
			    bytes, (int) msg->payload_size);
		msg->payload_size = bytes;

		/* grab a reference to previous message to reuse itsdata but only when bytes > 0 
		   because when bytes == 0, the reference is reused since it is not returned
		   to the caller (see next lines) */
		if (bytes > 0)
			nopoll_msg_ref (msg);
		conn->previous_msg = msg;

		/* flag this message as a fragment */
		msg->is_fragment = nopoll_true;

		/* flag that this message doesn't have FIN = 0 because
		 * we wasn't able to read it entirely */
		/* msg->has_fin = 0; */
	} /* end if */

	/* flag the message was being a fragment according to previous flag */
	msg->is_fragment = msg->is_fragment || conn->previous_was_fragment || msg->has_fin == 0;

	/* update was a fragment */
	conn->previous_was_fragment = msg->is_fragment && msg->has_fin == 0;

	/* do not notify any frame since no content was found */
	if (bytes == 0 && msg == conn->previous_msg) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "bytes == %d, msg (%p) == conn->previous_msg (%p)",
			    bytes, msg, conn->previous_msg);
		return NULL;
	} /* end if */

	/* now unmask content (if required) */
	if (msg->is_masked) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Unmasking (payload size %d, mask: %d, msg: %p, desp: %d)", 
			    msg->payload_size, nopoll_get_32bit (msg->mask), msg, msg->unmask_desp);
		nopoll_conn_mask_content (conn->ctx, (char*) msg->payload, msg->payload_size, (char*) msg->mask, msg->unmask_desp);

		/* flag what was unmasked */
		msg->unmask_desp += msg->payload_size;
	} /* end if */

	/* check here close frame with reason */
	if (msg->op_code == NOPOLL_CLOSE_FRAME) {
		/* try to read reason and report those values */
		if (msg->payload_size >= 2) {
			nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Close frame received id=%d with content bytes=%d, peer status=%d, peer reason=%s, reading reason..", 
				    conn->id, msg->payload_size, nopoll_get_16bit (msg->payload), msg->payload + 2);

			/* get values so the user can get them */
			conn->peer_close_status = nopoll_get_16bit ((const char *) msg->payload);
			conn->peer_close_reason = nopoll_strdup ((const char *) msg->payload + 2);
		} /* end if */

		/* release message, close the connection and return
		   NULL to notify caller nothing to read for the
		   application */
		nopoll_msg_unref (msg);
		nopoll_conn_shutdown (conn);
		return NULL;
	}

	return msg;
}

/** 
 * @internal Implementation to send Frames according to various
 * parameters passed in into the function. This is the core function
 * used to send frames in noPoll.
 *
 * @param conn The connection where the content will be sent.
 *
 * @param content The content that will be sent (user level content)
 *
 * @param length The length of such content to be sent.
 *
 * @param has_fin nopoll_true/nopoll_false to signal FIN header flag 
 *
 * @param sleep_in_header Optional hacking option that allows to
 * include a pause between sending the header and the rest of the
 * content.
 */
int           __nopoll_conn_send_common (noPollConn * conn, const char * content, long length, nopoll_bool has_fin, long sleep_in_header, noPollOpCode frame_type)
{
	if (conn == NULL || content == NULL || length == 0 || length < -1)
		return -1;

	if (conn->role == NOPOLL_ROLE_MAIN_LISTENER) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Trying to send content over a master listener connection");
		return -1;
	} /* end if */

	if (length == -1) {
		if (NOPOLL_BINARY_FRAME == frame_type) {
			nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Received length == -1 for binary frame. Unable to guess length");
			return -1;
		} /* end if */

		length = strlen (content);
	}
	nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "nopoll_conn_send_text: Attempting to send %d bytes", (int) length);

	/* sending content as client */
	if (conn->role == NOPOLL_ROLE_CLIENT) {
		return nopoll_conn_send_frame (conn, /* fin */ has_fin, /* masked */ nopoll_true, 
					       frame_type, length, (noPollPtr) content, sleep_in_header);
	} /* end if */

	/* sending content as listener */
	return nopoll_conn_send_frame (conn, /* fin */ has_fin, /* masked */ nopoll_false, 
				       frame_type, length, (noPollPtr) content, sleep_in_header);	
}

/** 
 * @brief Allows to send an UTF-8 text (op code 1) message over the
 * provided connection with the provided length.
 *
 * @param conn The connection where the message will be sent.
 *
 * @param content The content to be sent (it should be utf-8 content
 * or the function will fail).
 *
 * @param length Amount of bytes to take from the content to be
 * sent. If provided -1, it is assumed you are passing in a C-like
 * string nul terminated, so, that's the content to be sent.
 *
 * @return The number of bytes written otherwise < 0 is returned in
 * case of failure. The function will fail if some parameter is NULL
 * or undefined, or the content provided is not UTF-8. In the case of
 * failure, also check errno variable to know more what went wrong.
 *
 * See \ref nopoll_manual_retrying_write_operations to know more about error codes and when it is possible to retry write operations.
 */
int           nopoll_conn_send_text (noPollConn * conn, const char * content, long length)
{
	/* do a send common operation with FIN = 1 */
	return __nopoll_conn_send_common (conn, content, length, nopoll_true, 0, NOPOLL_TEXT_FRAME);
}

/** 
 * @brief Allows to send an UTF-8 text (op code 1) message over the
 * provided connection with the provided length but flagging the frame
 * sent as not complete (more frames to come, that is, FIN = 0).
 *
 * @param conn The connection where the message will be sent.
 *
 * @param content The content to be sent (it should be utf-8 content
 * or the function will fail).
 *
 * @param length Amount of bytes to take from the content to be
 * sent. If provided -1, it is assumed you are passing in a C-like
 * string nul terminated, so, that's the content to be sent.
 *
 * @return The number of bytes written otherwise < 0 is returned in
 * case of failure. The function will fail if some parameter is NULL
 * or undefined, or the content provided is not UTF-8. In the case of
 * failure, also check errno variable to know more what went wrong.
 *
 * See \ref nopoll_manual_retrying_write_operations to know more about
 * error codes and when it is possible to retry write operations.
 */
int           nopoll_conn_send_text_fragment (noPollConn * conn, const char * content, long length)
{
	/* do a send common operation with FIN = 0 */
	return __nopoll_conn_send_common (conn, content, length, nopoll_false, 0, NOPOLL_TEXT_FRAME);
}

/** 
 * @brief Allows to send a binary (op code 2) message over the
 * provided connection with the provided length.
 *
 * @param conn The connection where the message will be sent.
 *
 * @param content The content to be sent (it should be utf-8 content
 * or the function will fail).
 *
 * @param length Amount of bytes to take from the content to be
 * sent. Note you cannot pass in -1 (unlike \ref nopoll_conn_send_text).
 *
 * @return The number of bytes written otherwise < 0 is returned in
 * case of failure. The function will fail if some parameter is NULL
 * or undefined. In the case of failure, also check errno variable to
 * know more what went wrong.
 *
 * See \ref nopoll_manual_retrying_write_operations to know more about error codes and when it is possible to retry write operations.
 */
int           nopoll_conn_send_binary (noPollConn * conn, const char * content, long length)
{
	return __nopoll_conn_send_common (conn, content, length, nopoll_true, 0, NOPOLL_BINARY_FRAME);
}


/** 
 * @brief Allows to send a binary (op code 2) message over the
 * provided connection with the provided length but flagging the frame
 * sent as not complete (more frames to come, that is, FIN = 0).
 *
 * @param conn The connection where the message will be sent.
 *
 * @param content The content to be sent (it should be utf-8 content
 * or the function will fail).
 *
 * @param length Amount of bytes to take from the content to be
 * sent. Note you cannot pass in -1 (unlike \ref
 * nopoll_conn_send_text).
 *
 * @return The number of bytes written otherwise < 0 is returned in
 * case of failure. The function will fail if some parameter is NULL
 * or undefined. In the case of failure, also check errno variable to
 * know more what went wrong.
 *
 * See \ref nopoll_manual_retrying_write_operations to know more about
 * error codes and when it is possible to retry write operations.
 */
int           nopoll_conn_send_binary_fragment (noPollConn * conn, const char * content, long length)
{
	return __nopoll_conn_send_common (conn, content, length, nopoll_true, 0, NOPOLL_BINARY_FRAME);
}


/** 
 * @brief Allows to read the provided amount of bytes from the
 * provided connection, leaving the content read on the buffer
 * provided.
 *
 * Optionally, the function allows blocking the caller until the
 * amount of bytes requested are satisfied. Also, the function allows
 * to timeout the operation after provided amount of time.
 *
 * @param conn The connection where the read operation will take place.
 *
 * @param buffer The buffer where the result is returned. Memory
 * buffer must be enough to hold bytes requested and must be acquired
 * by the caller. 
 *
 * @param bytes Number of bytes to be read from the connection.
 *
 * @param block If nopoll_true, the caller will be blocked until the
 * amount of bytes requested are satisfied or until the timeout is
 * reached (if enabled). If nopoll_false is provided, the function
 * won't block and will return all bytes available at this moment.
 *
 * @param timeout (milliseconds 1sec = 1000ms) If provided a value
 * higher than 0, a timeout will be enabled to complete the
 * operation. If the timeout is reached, the function will return the
 * bytes read so far. Please note that the function has a precision of
 * 10ms.
 *
 * @return Number of bytes read or -1 if it fails. The function
 * returns -1 when no content is available to be read and you pass
 * block == nopoll_false
 *
 * Note that the function doesn't clear the buffer received. Only
 * memory (bytes) notified by the value returned by this function
 * should be accessed by the caller. In the same direction you can't
 * use the buffer as a nul-terminated string because the function
 * doesn't add the final \0 to the content read.
 *
 */
int           nopoll_conn_read (noPollConn * conn, char * buffer, int bytes, nopoll_bool block, long int timeout)
{
	long int           wait_slice = 0;
	noPollMsg        * msg        = NULL;
	struct  timeval    start;
	struct  timeval    stop;
	struct  timeval    diff;
	long               ellapsed   = 0;
	int                desp       = 0;
	int                amount;
	int                total_read = 0;
	int                total_pending = 0;

	/* report error value */
	if (conn == NULL || buffer == NULL || bytes <= 0)
		return -1;
	
	if (timeout > 1000)
		wait_slice = 100;
	else if (timeout > 100)
		wait_slice = 50;
	else if (timeout > 10)
		wait_slice = 10;

	if (timeout > 0)
#if defined(NOPOLL_OS_WIN32)
		nopoll_win32_gettimeofday (&start, NULL);
#else
		gettimeofday (&start, NULL);
#endif

	/* clear the buffer */
	memset (buffer, 0, bytes);

	/* check here if we have a pending message to read */
	if (conn->pending_msg)  {
		/* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "nopoll_conn_read (found pending content: %d, requested %d)", conn->pending_diff, bytes); */
		/* get references to pending data */
		amount = conn->pending_diff;
		msg    = conn->pending_msg;
		if (amount > bytes) {
			/* check if bytes requested are bigger the
			 * conn->pending_diff */
			if (bytes < conn->pending_diff) {
				conn->pending_diff -= bytes;
			} else {
				/* update values */
				bytes = conn->pending_diff;
				conn->pending_diff = 0;
			} /* end if */

			amount = bytes;
		} else {
			conn->pending_diff = 0;
		}

		/* read content */
		memcpy (buffer, ((unsigned char *) nopoll_msg_get_payload (msg)) + conn->pending_desp, amount);
		total_read += amount;
		desp        = amount;
		/* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "nopoll_conn_read total amount satisfied is not %d, requested %d", total_read, bytes); */
		
		/* increase pending desp */
		conn->pending_desp += amount;

		/* now release internally the content if consumed the message */
		if (conn->pending_diff == 0) {
			nopoll_msg_unref (conn->pending_msg);
			conn->pending_msg = NULL;
		} /* end if */

		/* see if we have finished */
		if (total_read == bytes || ! block) {
			if (total_read == 0 && ! block) 
				return -1;

			return total_read;
		}
		
		/* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "### ====> Read %d bytes from previous pending frame, requested %d. Pending diff %d", total_read, bytes, conn->pending_diff); */
	} /* end if */


	/* for for the content */
	while (nopoll_true) {
		/* call to get next message */
		msg = nopoll_conn_get_msg (conn);
		if (msg == NULL) {
			if (! nopoll_conn_is_ok (conn)) {
			        nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Received websocket conn-id=%d close during wait reply..",
					    conn->id);
				if (total_read == 0 && ! block)
					return -1;
				return total_read;
			} /* end if */

			if (! block) {
				if (total_read == 0 && ! block) 
					return -1;
				return total_read;
			} /* end if */
			
		} /* end if */

		/* get the message content into the buffer */
		if (msg) {
			/* get the amount of bytes we can read */
			amount = nopoll_msg_get_payload_size (msg);
			total_pending = bytes - total_read;
			nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "(New Frame) received %d bytes (pending requested %d bytes, desp: %d)", amount, total_pending, desp);
			if (amount > total_pending) {
				/* save here the difference between
				 * what we have read and remaining data */
				conn->pending_desp = total_pending;
				conn->pending_diff = amount - total_pending;
				conn->pending_msg  = msg;
				amount = total_pending;

				/* acquire a reference to the message */
				nopoll_msg_ref (msg);
			} /* end if */
			/* copy data */
			memcpy (buffer + desp, nopoll_msg_get_payload (msg), amount);
			total_read += amount;
			desp       += amount;
			/* nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "nopoll_conn_read total amount satisfied is not %d, requested %d, desp: %d",
			   total_read, bytes, desp); */

			/* release message */
			nopoll_msg_unref (msg);

			/* return amount read */
			if (total_read == bytes || ! block) {
				nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Finishing nopoll_conn_read because block=%d or total bytes requested=%d satisfied=%d ", 
					    block, bytes, total_read);

				if (total_read == 0 && ! block) 
					return -1;
				return total_read;
			}
		}

		/* check to stop due to timeout */
		if (timeout > 0) {
#if defined(NOPOLL_OS_WIN32)
			nopoll_win32_gettimeofday (&stop, NULL);
#else
			gettimeofday (&stop, NULL);
#endif
			nopoll_timeval_substract (&stop, &start, &diff);
			
			ellapsed = (diff.tv_sec * 1000) + (diff.tv_usec / 1000);
			if (ellapsed > (timeout)) 
				break;
		} /* end if */
		
		nopoll_sleep (wait_slice);
	} /* end while */

	/* reached this point, return that timeout was reached */
	nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Finishing nopoll_conn_read timeout reached=%ld ms , returning total bytes requested=%d satisfied=%d ", 
		    timeout, bytes, total_read);

	if (total_read == 0 && ! block)
		return -1;
	return total_read;
}

/** 
 * @brief Allows to send a ping message over the Websocket connection
 * provided. The function will not block the caller.
 *
 * @param conn The connection where the PING operation will be sent.
 *
 * @return nopoll_true if the operation was sent without any error,
 * otherwise nopoll_false is returned.
 */
nopoll_bool      nopoll_conn_send_ping (noPollConn * conn)
{
	return nopoll_conn_send_frame (conn, nopoll_true, nopoll_false, NOPOLL_PING_FRAME, 0, NULL, 0) > 0;
}

/** 
 * @brief Allows to configure an on message handler on the provided
 * connection that overrides the one configured at \ref noPollCtx.
 *
 * @param conn The connection to be configured with a particular on message handler.
 *
 * @param on_msg The on message handler configured.
 *
 * @param user_data User defined pointer to be passed in into the on message handler when it is called.
 * 
 */
void          nopoll_conn_set_on_msg (noPollConn              * conn,
				      noPollOnMessageHandler    on_msg,
				      noPollPtr                 user_data)
{
	if (conn == NULL)
		return;

	/* configure on message handler */
	conn->on_msg      = on_msg;
	conn->on_msg_data = user_data;

	return;
}

/** 
 * @brief Allows to configure a handler that is called when the
 * connection provided is ready to send and receive because all
 * WebSocket handshake protocol finished OK.
 *
 * Unlike handlers configured at \ref nopoll_ctx_set_on_open and \ref
 * nopoll_ctx_set_on_accept which get notified when the connection
 * isn't still working (because WebSocket handshake wasn't finished
 * yet), on read handlers configured here will get called just after
 * the WebSocket handshake has taken place.
 *
 * @param conn The connection to configure.
 *
 * @param on_ready The handler to be called when a connection is fully
 * ready to send and receive content because WebSocket handshake has
 * finished. The function must return nopoll_true to accept the
 * connection. By returning nopoll_false the handler is signalling to
 * terminate the connection.
 *
 * @param user_data Optional user data pointer passed to the on ready
 * handler.
 * 
 */
void           nopoll_conn_set_on_ready (noPollConn            * conn,
					 noPollActionHandler     on_ready,
					 noPollPtr               user_data)
{
	if (conn == NULL)
		return;

	/* set the handler */
	conn->on_ready = on_ready;
	if (conn->on_ready == NULL)
		conn->on_ready_data = NULL;
	else
		conn->on_ready_data = user_data;
	return;
}

/** 
 * @brief Allows to configure an OnClose handler that will be called
 * when the connection is closed.
 *
 * @param conn The connection to configure with the on close handle.
 *
 * @param on_close The handler to be configured.
 *
 * @param user_data A reference pointer to be passed in into the handler.
 */
void          nopoll_conn_set_on_close (noPollConn            * conn,
					noPollOnCloseHandler    on_close,
					noPollPtr               user_data)
{
	if (conn == NULL)
		return;

	/* configure on close handler */
	conn->on_close      = on_close;
	conn->on_close_data = user_data;

        return;
}

/** 
 * @internal Allows to send a pong message over the Websocket
 * connection provided. The function will not block the caller. This
 * function is not intended to be used by normal API consumer.
 *
 * @param conn The connection where the PING operation will be sent.
 *
 * @param nopoll_true if the operation was sent without any error,
 * otherwise nopoll_false is returned.
 */
nopoll_bool      nopoll_conn_send_pong (noPollConn * conn)
{
	if (conn == NULL)
		return nopoll_false; /* do not pong if wrong reference received */

	return nopoll_conn_send_frame (conn, nopoll_true, conn->role == NOPOLL_ROLE_CLIENT, NOPOLL_PONG_FRAME, 0, NULL, 0);
}

/** 
 * @brief Allows to call to complete last pending write process that may be
 * pending from a previous uncompleted write operation. The function
 * returns the number of bytes that were written.
 *
 * @param conn The connection where the pending write operation
 * operation will take place. In the case conn == NULL is received, 0
 * is returned. Keep in mind this.
 *
 * @return In the case no pending write is in place, the function
 * returns 0. Otherwise, the function returns the pending bytes that
 * were written. The function returns -1 in the case of failure.
 *
 * You can call this function as many times as you want until you get
 * a 0. You can view it as a flush operation.
 */
int nopoll_conn_complete_pending_write (noPollConn * conn)
{
	int    bytes_written = 0;
	char * reference;
	int    pending_bytes;

	if (conn == NULL || conn->pending_write == NULL)
		return 0;

	/* simple implementation */
	bytes_written = conn->send (conn, conn->pending_write, conn->pending_write_bytes);
	if (bytes_written == conn->pending_write_bytes) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Completed pending write operation with bytes=%d", bytes_written);
		nopoll_free (conn->pending_write);
		conn->pending_write = NULL;
		return bytes_written;
	} /* end if */

	if (bytes_written > 0) {
		/* bytes written but not everything */
		pending_bytes = conn->pending_write_bytes - bytes_written;
		reference     = nopoll_new (char, pending_bytes);
		memcpy (reference, conn->pending_write + bytes_written, pending_bytes);
		nopoll_free (conn->pending_write);
		conn->pending_write = reference;
		return bytes_written;
	}

	nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Found complete write operation didn't finish well, result=%d, errno=%d, conn-id=%d",
		    bytes_written, errno, conn->id);
	return bytes_written;
}

/** 
 * @brief Allows to check if there are pending write bytes. The
 * function returns the number of pending write bytes that are waiting
 * to be flushed. To do so you must call \ref nopoll_conn_complete_pending_write.
 *
 * @param conn The connection to be checked to have pending bytes to be written.
 *
 * @return The number of bytes pending to be written. The function
 * also returns 0 when conn reference received is NULL.
 */
int           nopoll_conn_pending_write_bytes (noPollConn * conn)
{
	if (conn == NULL || conn->pending_write == NULL)
		return 0;

	return conn->pending_write_bytes;
}

/** 
 * @brief Ready to use function that checks for pending write
 * operations and flush them waiting until they are done or until the
 * timeout provided by the user is reached.
 *
 * This function uses \ref nopoll_conn_pending_write_bytes and \ref
 * nopoll_conn_complete_pending_write to check and complete pending
 * write operations. 
 *
 * Because writing pending bytes is a common operation, this function
 * is provided as a ready to use function to call after a write operation (for
 * example \ref nopoll_conn_send_text).
 *
 * @param conn The connection where pending bytes must be written. 
 *
 * @param timeout Timeout in milliseconds to limit the flush operation.
 *
 * @param previous_result Optional parameter that can receive the
 * number of bytes optionally read before this call. The value
 * received on this function will be added to the result, checking
 * first it contains a value higher than 0. This is an option to
 * clarify the interface. If you don't have the value to be passed to
 * this function at the time needed, just pass 0.
 *
 * @return Bytes that were written. If no pending bytes must be written, the function returns 0.
 */
int nopoll_conn_flush_writes (noPollConn * conn, long timeout, int previous_result)
{
	int iterator = 0;
	int bytes_written;
	int total = 0;
	int multiplier = 1;
	long wait_implemented = 0;

	/* check for errno and pending write operations */
	if (errno != NOPOLL_EWOULDBLOCK || nopoll_conn_pending_write_bytes (conn) == 0) {
	        nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "called flush but nothing is pending=%d or errno=%d isn't %d",
		            nopoll_conn_pending_write_bytes (conn), errno, NOPOLL_EWOULDBLOCK);
		return previous_result > 0 ? previous_result : 0;
	}
		
	while (iterator < 100 && nopoll_conn_pending_write_bytes (conn) > 0) {

		/* stop operation if timeout reached */
		if (wait_implemented >= timeout)
			break;

		nopoll_sleep (100000 * multiplier);
		wait_implemented += (100000 * multiplier);

		/* write content pending */
		bytes_written = nopoll_conn_complete_pending_write (conn);

		if (bytes_written > 0)
			total += bytes_written;

		/* next position */
		iterator++;
		multiplier++;
	} /* end while */

	nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "finishing flush operation, total written=%d, added to previous result=%d",
		    total, previous_result);

	/* add value received */
	if (previous_result > 0) 
		return total + previous_result;

	/* just bytes written */
	return total;
}


/** 
 * @internal Function used to send a frame over the provided
 * connection.
 *
 * @param conn The connection where the send operation will hapen.
 *
 * @param fin If the frame to be sent must be flagged as a fin frame.
 *
 * @param masked The frame to be sent is masked or not.
 *
 * @param op_code The frame op code to be configured.
 *
 * @param length The frame payload length.
 *
 * @param content Pointer to the data to be sent in the frame.
 */
int nopoll_conn_send_frame (noPollConn * conn, nopoll_bool fin, nopoll_bool masked,
			    noPollOpCode op_code, long length, noPollPtr content, long sleep_in_header)

{
	char               header[14];
	int                header_size;
	char             * send_buffer;
	int                bytes_written = 0;
	char               mask[4];
	unsigned int       mask_value = 0;
	int                desp = 0;
	int                tries;
#if defined(SHOW_DEBUG_LOG)
	noPollDebugLevel   level;
#endif

	/* check for pending send operation */
	if (nopoll_conn_complete_pending_write (conn) != 0)
		return -1;

	/* clear header */
	memset (header, 0, 14);

	/* set header codes */
	if (fin) 
		nopoll_set_bit (header, 7);
	
	if (masked) {
		nopoll_set_bit (header + 1, 7);
		
		/* define a random mask */
#if defined(NOPOLL_OS_WIN32)
		mask_value = (unsigned int) rand ();
#else
		mask_value = (unsigned int) random ();
#endif
		memset (mask, 0, 4);
		nopoll_set_32bit (mask_value, mask);
	} /* end if */

	if (op_code) {
		/* set initial 4 bits */
		header[0]   |= op_code & 0x0f;
	}

	/* set default header size */
	header_size  = 2;

	/* according to message length */
	if (length < 126) {
		header[1] |= length;
	} else if (length < 65535) {
		/* set the next header length is at least 65535 */
		header[1] |= 126;
		header_size += 2;
		/* set length into the next bytes */
		nopoll_set_16bit (length, header + 2);
#if defined(NOPOLL_64BIT_PLATFORM)
	} else if (length < 9223372036854775807) {
		/* not supported yet */
		return -1;
#else
	} else {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Unable to send the requested message, this requested is bigger than the value that can be supported by this platform (it should be < 65k)");
		return -1;
#endif
	}

	/* place mask */
	if (masked) {
		nopoll_set_32bit (mask_value, header + header_size);
		header_size += 4;
	} /* end if */

	/* allocate enough memory to send content */
	send_buffer = nopoll_new (char, length + header_size + 2);
	if (send_buffer == NULL) {
		nopoll_log (conn->ctx, NOPOLL_LEVEL_CRITICAL, "Unable to allocate memory to implement send operation");
		return -1;
	} /* end if */
	
	/* copy content to be sent */
	nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Copying into the buffer %d bytes of header (total memory allocated: %d)", 
		    header_size, (int) length + header_size + 1);
	memcpy (send_buffer, header, header_size);
	if (length > 0) {
		memcpy (send_buffer + header_size, content, length);

		/* mask content before sending if requested */
		if (masked) {
			nopoll_conn_mask_content (conn->ctx, send_buffer + header_size, length, mask, 0);
		}
	} /* end if */

	
	/* send content */
	nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Mask used for this delivery: %d (about to send %d bytes)",
		    nopoll_get_32bit (send_buffer + header_size - 2), (int) length + header_size);

	/* clear errno status before writting */
	desp  = 0;
	tries = 0;

	/***** BEGIN INTERNAL debug code for test_30, test_31, test_32, test_33, test_34, test_35 : nopoll-regression-client.c ******/
	if ((conn->__force_stop_after_header > 0) && (conn->__force_stop_after_header < (length + header_size))) {
		
		nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Sending broken header (just %d bytes) and implement a pause on purpose...", conn->__force_stop_after_header);

		/* send just 2 bytes for the header and then implement a very long pause */
		bytes_written = conn->send (conn, send_buffer, conn->__force_stop_after_header);
		desp          = conn->__force_stop_after_header;
		if (bytes_written != conn->__force_stop_after_header) {
			nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Requested to write %d bytes for the header but %d were written",
				    conn->__force_stop_after_header, bytes_written);
			desp  = 0;
		} /* end if */

		/* sleep after header ... */
		nopoll_sleep (5000000); /* 5 seconds */

	} /* end if */
	/****** END INTERNAL debug code for test_30 : nopoll-regression-client.c ******/

	while (nopoll_true) {
		/* try to write bytes */
		if (sleep_in_header == 0) {
			bytes_written = conn->send (conn, send_buffer + desp, length + header_size - desp);
		} else {
			nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Found sleep in header indication, sending header: %d bytes (waiting %ld)", header_size, sleep_in_header);
			bytes_written = conn->send (conn, send_buffer, header_size);
			if (bytes_written == header_size) {
				/* sleep after header ... */
				nopoll_sleep (sleep_in_header);
				
				/* now send the rest of the content (without the header) */
				bytes_written = conn->send (conn, send_buffer + header_size, length);
				nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Rest of content written %d (header size: %d, length: %d)", 
					    bytes_written, header_size, length);
				bytes_written = length + header_size;
				nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "final bytes_written %d", bytes_written);
			} else {
				nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Requested to write %d bytes for the header but %d were written",
					    header_size, bytes_written);
				return -1;
			} /* end if */
		} /* end if */
		
		if ((bytes_written + desp) != (length + header_size)) {
			nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, 
				    "Requested to write %d bytes but found %d written (masked? %d, mask: %u, header size: %d, length: %d), errno = %d : %s", 
				    (int) length + header_size - desp, bytes_written, masked, mask_value, header_size, (int) length, errno, strerror (errno));
		} else {
			/* accomulate bytes written to continue */
			if (bytes_written > 0)
				desp += bytes_written;

			nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Bytes written to the wire %d (masked? %d, mask: %u, header size: %d, length: %d)", 
				    bytes_written, masked, mask_value, header_size, (int) length);
			break;
		} /* end if */

		/* accomulate bytes written to continue */
		if (bytes_written > 0)
			desp += bytes_written;

		/* increase tries */
		tries++;

		if ((errno != 0) || tries > 50) {
			nopoll_log (conn->ctx, NOPOLL_LEVEL_WARNING, "Found errno=%d (%s) value while trying to bytes to the WebSocket conn-id=%d or max tries reached=%d",
				    errno, strerror (errno), conn->id, tries);
			break;
		} /* end if */

		/* wait a bit */
		nopoll_sleep (100000);

	} /* end while */

	/* record pending write bytes */
	conn->pending_write_bytes = length + header_size - desp;

#if defined(SHOW_DEBUG_LOG)
	level = NOPOLL_LEVEL_DEBUG;
	if (desp != (length + header_size))
		level = NOPOLL_LEVEL_CRITICAL;
	else if (errno == NOPOLL_EWOULDBLOCK && conn->pending_write_bytes > 0)
		level = NOPOLL_LEVEL_WARNING;

	nopoll_log (conn->ctx, level, 
		    "Write operation finished with with last result=%d, bytes_written=%d, requested=%d, remaining=%d (conn-id=%d)",
		    /* report want we are going to report: result */
		    bytes_written <= 0 ? bytes_written : desp - header_size,
		    /* bytes written */
		    desp - header_size, 
		    length, conn->pending_write_bytes, conn->id);
#endif

	/* check pending bytes for the next operation */
	if (conn->pending_write_bytes > 0) {
		conn->pending_write = nopoll_new (char, conn->pending_write_bytes);
		memcpy (conn->pending_write, send_buffer + length + header_size - conn->pending_write_bytes, conn->pending_write_bytes);
		
		nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Stored %d bytes starting from %d out of %d bytes (header size: %d)", 
			    conn->pending_write_bytes, length + header_size - conn->pending_write_bytes, length + header_size, header_size);
	} /* end if */
	
		    
	/* release memory */
	nopoll_free (send_buffer);

	/* report at least what was written */
	if (desp - header_size > 0)
		return desp - header_size;

	/* report last operation */
	return bytes_written;
}

/** 
 * @brief Allows to accept a new incoming WebSocket connection on the
 * provided listener.
 *
 * @param ctx The context where the operation will take place.
 *
 * @param listener The WebSocket listener that is receiving a new incoming connection.
 *
 * @return A newly created \ref noPollConn reference or NULL if it
 * fails.
 */
noPollConn * nopoll_conn_accept (noPollCtx * ctx, noPollConn * listener)
{
	NOPOLL_SOCKET   session;

	nopoll_return_val_if_fail (ctx, ctx && listener, NULL);

	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Calling to accept web socket connection over master id=%d, socket=%d",
		    listener->id, listener->session);

	/* recevied a new connection: accept the
	 * connection and ask the app level to accept
	 * or not */
	session = nopoll_listener_accept (listener->session);
	if (session == NOPOLL_INVALID_SOCKET) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received invalid socket value from accept(2): %d, error code errno=: %d", 
			    session, errno);
		return NULL;
	} /* end if */

	return nopoll_conn_accept_socket (ctx, listener, session);
}


/** 
 * @brief Allows to accept a new incoming WebSocket connection on the
 * provided listener but with a socket already accepted.
 *
 * @param ctx The context where the operation will take place.
 *
 * @param listener The WebSocket listener that is receiving a new incoming connection.
 *
 * @param session An already accepted socket from the provided
 * listener.
 *
 * @return A newly created \ref noPollConn reference or NULL if it
 * fails.
 */
noPollConn * nopoll_conn_accept_socket (noPollCtx * ctx, noPollConn * listener, NOPOLL_SOCKET session)
{
	noPollConn * conn;

	nopoll_return_val_if_fail (ctx, ctx && listener, NULL);

	/* create the connection */
	conn = nopoll_listener_from_socket (ctx, session);
	if (conn == NULL) {
		nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Received NULL pointer after calling to create listener from session..");
		return NULL;
	} /* end if */

	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Accepted new WebSocket conn-id=%d, socket=%d, over master id=%d, socket=%d",
		    conn->id, conn->session, listener->id, listener->session);

	/* configure the listener reference that accepted this
	 * connection */
	conn->listener = listener;

	if (! nopoll_conn_accept_complete (ctx, listener, conn, session, listener->tls_on))
		return NULL;

	/* report listener created */
	return conn;
}

/**
 * @internal Function to support accept listener operations.
 */
nopoll_bool __nopoll_conn_accept_complete_common (noPollCtx * ctx, noPollConnOpts * options, noPollConn * listener, noPollConn * conn, NOPOLL_SOCKET session, nopoll_bool tls_on) {

	const char          * certificateFile  = NULL;
	const char          * privateKey       = NULL;
	const char          * chainCertificate = NULL;
	const char          * serverName       = NULL;

	/* check input parameters */
	if (! (ctx && listener && conn && session != NOPOLL_INVALID_SOCKET)) {
		nopoll_conn_shutdown (conn);
		nopoll_ctx_unregister_conn (ctx, conn);

		/* release connection options */
		__nopoll_conn_opts_release_if_needed (options);

		return nopoll_false;
	} /* end if */

	/* configure non blocking mode */
	nopoll_conn_set_sock_block (session, nopoll_true);
	
	/* now check for accept handler */
	if (ctx->on_accept) {
		/* call to on accept */
		if (! ctx->on_accept (ctx, conn, ctx->on_accept_data)) {
			nopoll_log (ctx, NOPOLL_LEVEL_WARNING, "Application level denied accepting connection from %s:%s, closing", 
				    conn->host, conn->port);
			nopoll_conn_shutdown (conn);
			nopoll_ctx_unregister_conn (ctx, conn);

			/* release connection options */
			__nopoll_conn_opts_release_if_needed (options);

			return nopoll_false;
		} /* end if */
	} /* end if */

	nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Connection received and accepted from %s:%s (conn refs: %d, ctx refs: %d)", 
		    listener->host, listener->port, listener->refs, ctx->refs);

	if (listener->tls_on || tls_on) {
		/* reached this point, ensure tls is enabled on this
		 * session */
		conn->tls_on = nopoll_true;

		/* get here SNI to query about the serverName */

		/* 1) GET FROM OPTIONS: detect here if we have
		 * certificates provided through options */
		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Starting TLS process, options=%p, listener=%p", options, listener);

		if (options) {
			certificateFile = options->certificate;
			privateKey      = options->private_key;
		} /* end if */
		if (certificateFile == NULL || privateKey == NULL) {

			/* 2) GET FROM LISTENER: get references to currently configured certificate file */
			certificateFile = listener->certificate;
			privateKey      = listener->private_key;
			if (certificateFile == NULL || privateKey == NULL) {
				/* 3) GET FROM STORE: check if the
				 * certificate is already installed */
				nopoll_ctx_find_certificate (ctx, serverName, &certificateFile, &privateKey, &chainCertificate);
			}
		} /* end if */

		/* check certificates and private key */
		if (certificateFile == NULL || privateKey == NULL) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to accept secure web socket connection, certificate file %s and/or key file %s isn't defined",
				    certificateFile ? certificateFile : "<not defined>", 
				    privateKey ? privateKey : "<not defined>");
			nopoll_conn_shutdown (conn);
			nopoll_ctx_unregister_conn (ctx, conn);

			/* release connection options */
			__nopoll_conn_opts_release_if_needed (options);

			return nopoll_false;
		} /* end if */

		/* init ssl ciphers and engines */
		if (! __nopoll_tls_was_init) {
			__nopoll_tls_was_init = nopoll_true;
			SSL_library_init ();
		} /* end if */

		/* now configure chainCertificate */
		if (listener->chain_certificate) 
			chainCertificate = listener->chain_certificate;
		else if (options && options->chain_certificate)
			chainCertificate = options->chain_certificate;

		/* create ssl context */
		conn->ssl_ctx  = __nopoll_conn_get_ssl_context (ctx, conn, listener->opts, nopoll_false);

		/* Configure ca certificate in the case it is defined */
		if (options && options->ca_certificate) {
			nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Setting up CA certificate: %s", options->ca_certificate);
			if (SSL_CTX_load_verify_locations (conn->ssl_ctx, options->ca_certificate, NULL) != 1) {
				nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to configure CA certificate (%s), SSL_CTX_load_verify_locations () failed", options->ca_certificate);
				return nopoll_false;
			} /* end if */

		} /* end if */

		/* enable default verification paths */
		if (SSL_CTX_set_default_verify_paths (conn->ssl_ctx) != 1) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to configure default verification paths, SSL_CTX_set_default_verify_paths () failed");
			return nopoll_false;
		} /* end if */

		/* configure chain certificate */
		if (chainCertificate) {
			nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Setting up chain certificate: %s", chainCertificate);
			if (SSL_CTX_use_certificate_chain_file (conn->ssl_ctx, chainCertificate) != 1) {
				nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Failed to configure chain certificate (%s), SSL_CTX_use_certificate_chain_file () failed", chainCertificate);
				return nopoll_false;
			} /* end if */
		} /* end if */

		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Using certificate file: %s (with ssl context ref: %p)", certificateFile, conn->ssl_ctx);
		if (conn->ssl_ctx == NULL || SSL_CTX_use_certificate_chain_file (conn->ssl_ctx, certificateFile) != 1) {
			/* drop an error log */
			if (conn->ssl_ctx == NULL)
				nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to accept incoming connection, failed to create SSL context. Context creator returned NULL pointer");
			else 
				nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "there was an error while setting certificate file into the SSl context, unable to start TLS profile. Failure found at SSL_CTX_use_certificate_file function. Tried certificate file: %s", 
					    certificateFile);

			/* dump error stack */
			nopoll_conn_shutdown (conn);
			nopoll_ctx_unregister_conn (ctx, conn);

			/* release connection options */
			__nopoll_conn_opts_release_if_needed (options);

			return nopoll_false;
		} /* end if */

		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Using certificate key: %s", privateKey);
		if (SSL_CTX_use_PrivateKey_file (conn->ssl_ctx, privateKey, SSL_FILETYPE_PEM) != 1) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, 
				    "there was an error while setting private file into the SSl context, unable to start TLS profile. Failure found at SSL_CTX_use_PrivateKey_file function. Tried private file: %s", 
				    privateKey);
			/* dump error stack */
			nopoll_conn_shutdown (conn);
			nopoll_ctx_unregister_conn (ctx, conn);

			/* release connection options */
			__nopoll_conn_opts_release_if_needed (options);

			return nopoll_false;
		}

		/* check for private key and certificate file to match. */
		if (! SSL_CTX_check_private_key (conn->ssl_ctx)) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, 
				    "seems that certificate file and private key doesn't match!, unable to start TLS profile. Failure found at SSL_CTX_check_private_key function. Used certificate %s, and key: %s",
				    certificateFile, privateKey);
			/* dump error stack */
			nopoll_conn_shutdown (conn);

			/* release connection options */
			__nopoll_conn_opts_release_if_needed (options);

			return nopoll_false;
		} /* end if */

		if (options != NULL && ! options->disable_ssl_verify) {
			nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Enabling certificate client peer verification from server");
			/** really, really ugly hack to let
			 * __nopoll_conn_ssl_verify_callback to be able to get
			 * access to the context required to drop some logs */
			__nopoll_conn_ssl_ctx_debug = ctx;
			SSL_CTX_set_verify (conn->ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, __nopoll_conn_ssl_verify_callback); 
			SSL_CTX_set_verify_depth (conn->ssl_ctx, 5);
		} /* end if */


		/* create SSL context */
		conn->ssl = SSL_new (conn->ssl_ctx);       
		if (conn->ssl == NULL) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "error while creating TLS transport, SSL_new (%p) returned NULL", conn->ssl_ctx);
			nopoll_conn_shutdown (conn);
			nopoll_ctx_unregister_conn (ctx, conn);

			/* release connection options */
			__nopoll_conn_opts_release_if_needed (options);

			return nopoll_false;
		} /* end if */

		/* set the file descriptor */
		SSL_set_fd (conn->ssl, conn->session);

		/* don't complete here the operation but flag it as
		 * pending */
		conn->pending_ssl_accept = nopoll_true;
		nopoll_conn_set_sock_block (conn->session, nopoll_false);

		nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Prepared TLS session to be activated on next reads (conn id %d)", conn->id);
		
	} /* end if */

	/* release connection options */
	__nopoll_conn_opts_release_if_needed (options);

	return nopoll_true;
}

/** 
 * @brief Allows to complete accept operation by setting up all I/O
 * handlers required to make the WebSocket connection to work.
 *
 * @param ctx The context where the operation takes place.
 *
 * @param listener The listener where the connection was accepted.
 *
 * @param conn The connection that was accepted.
 *
 * @param session The socket associated to the listener accepted.
 *
 * @param tls_on A boolean indication if the TLS interface should be
 * enabled or not.
 *
 * @return nopoll_true if the listener was accepted otherwise nopoll_false is returned.
 */
nopoll_bool nopoll_conn_accept_complete (noPollCtx * ctx, noPollConn * listener, noPollConn * conn, NOPOLL_SOCKET session, nopoll_bool tls_on) {

	if (listener->opts) {
		if (! nopoll_conn_opts_ref (listener->opts)) {
			nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Unable to acquire a reference to the connection option at nopoll_conn_accept_complete() function nopoll_conn_opts_ref () failed..");
			return nopoll_false;
		} /* end if */
	} /* end if */

	return __nopoll_conn_accept_complete_common (ctx, listener->opts, listener, conn, session, tls_on);
}

/** 
 * @brief Allows to implement a wait operation until the provided
 * connection is ready or the provided timeout is reached.
 *
 * @param conn The connection that is being waited to be created.
 *
 * @param timeout The timeout operation to limit the wait
 * operation. Timeout is provided in seconds.
 *
 * @return The function returns when the timeout was reached or the
 * connection is ready. In the case the connection is ready when the
 * function finished nopoll_true is returned, otherwise nopoll_false.
 */
nopoll_bool      nopoll_conn_wait_until_connection_ready (noPollConn * conn,
							  int          timeout)
{
	long int total_timeout = timeout * 1000000;

	/* check if the connection already finished its connection
	   handshake */
	while (! nopoll_conn_is_ready (conn) && total_timeout > 0) {

		/* check if the connection is ok */
		if (! nopoll_conn_is_ok (conn)) 
			return nopoll_false;

		/* wait a bit 0,5ms */
		nopoll_sleep (500);

		/* reduce the amount of time we have to wait */
		total_timeout = total_timeout - 500;
	} /* end if */

	/* report if the connection is ok */
	return nopoll_conn_is_ok (conn) && nopoll_conn_is_ready (conn);
}

/* @} */
