esp8266-std/ESP8266_RTOS_SDK/third_party/nopoll/nopoll_conn.c

4157 lines
137 KiB
C

/*
* LibNoPoll: A websocket library
* Copyright (C) 2013 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>
/**
* @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 */
/**
* @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)
{
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);
/* 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) strtol (port, NULL, 10));
/* 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 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 |cookie |prot | */
return nopoll_strdup_printf ("GET %s HTTP/1.1\r\nHost: %s\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: %s\r\nOrigin: %s\r\n%s%s%s%s%s%s%s%sSec-WebSocket-Version: %d\r\n\r\n",
conn->get_url, conn->host_name,
/* sec-websocket-key */
key,
/* Origin */
conn->origin,
/* Cookie */
(opts && opts->cookie) ? "Cookie" : "",
(opts && opts->cookie) ? ": " : "",
(opts && opts->cookie) ? opts->cookie : "",
(opts && opts->cookie) ? "\r\n" : "",
/* protocol part */
conn->protocols ? "Sec-WebSocket-Protocol" : "",
conn->protocols ? ": " : "",
conn->protocols ? conn->protocols : "",
conn->protocols ? "\r\n" : "",
conn->ctx->protocol_version);
}
/**
* @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) {
/* printf ("**** REPORTING TLSv1 ****\n"); */
return (SSL_CTX *)SSL_CTX_new (is_client ? TLSv1_client_method () : TLSv1_server_method ());
} /* end if */
switch (opts->ssl_protocol) {
case NOPOLL_METHOD_TLSV1:
return (SSL_CTX *)SSL_CTX_new (is_client ? TLSv1_client_method () : TLSv1_server_method ());
//#if defined(TLSv1_1_client_method)
case NOPOLL_METHOD_TLSV1_1:
/* printf ("**** REPORTING TLSv1.1 ****\n"); */
return (SSL_CTX *)SSL_CTX_new (is_client ? TLSv1_1_client_method () : TLSv1_1_server_method ());
//#endif
case NOPOLL_METHOD_SSLV3:
/* printf ("**** REPORTING SSLv3 ****\n"); */
return (SSL_CTX *)SSL_CTX_new (is_client ? SSLv3_client_method () : SSLv3_server_method ());
case NOPOLL_METHOD_SSLV23:
/* printf ("**** REPORTING SSLv23 ****\n"); */
return (SSL_CTX *)SSL_CTX_new (is_client ? SSLv23_client_method () : SSLv23_server_method ());
}
/* reached this point, report default TLSv1 method */
return (SSL_CTX *)SSL_CTX_new (is_client ? TLSv1_client_method () : TLSv1_server_method ());
}
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 *)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 */
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 (ctx, host_ip, host_port);
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->sends = 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);
SSL_CTX_set_default_read_buffer_len(conn->ssl_ctx, 4096);
/* 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, conn->ssl);
goto fail_ssl_connection;
} /* end if */
/* create context and check for result */
conn->ssl = (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 > 100) {
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 = (X509*)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;
}
/* 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->sends = 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->sends (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).
*/
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.
*
* @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 nopoll_tls_conn_new with the same semantics
* but providing a way to create a WebSocket session under the
* supervision of TLS.
*
* @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);
if (conn->refs <= 0) {
/* unlock the mutex */
nopoll_mutex_unlock (conn->ref_mutex);
return nopoll_false;
}
conn->refs++;
/* release here the mutex */
nopoll_mutex_unlock (conn->ref_mutex);
return nopoll_true;
}
/**
* @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).
*
* @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 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)
{
if (conn == NULL)
return;
/* acquire here the mutex */
nopoll_mutex_lock (conn->ref_mutex);
conn->refs--;
nopoll_log (conn->ctx, NOPOLL_LEVEL_DEBUG, "Releasing connection id %d reference, current ref count status is: %d",
conn->id, conn->refs);
if (conn->refs != 0) {
/* release here the mutex */
nopoll_mutex_unlock (conn->ref_mutex);
return;
}
/* release here the mutex */
nopoll_mutex_unlock (conn->ref_mutex);
/* 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;
/* 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] != ':') {
nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "Expected to find mime header separator : but it wasn't found (buffer_size=%d, iterator=%d, content: [%s]..",
buffer_size, iterator, buffer);
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]), inside content: [%s]..",
iterator, iterator2, (*header), (int)buffer[iterator2], buffer);
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;
if (websocket_key == NULL)
return NULL;
key_length = strlen (websocket_key);
unsigned char buffer[EVP_MAX_MD_SIZE];
EVP_MD_CTX mdctx;
const EVP_MD * md = NULL;
unsigned int md_len = EVP_MAX_MD_SIZE;
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_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;
/* call to check listener handshake */
nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Checking client handshake data..");
/* ensure we have all minumum data */
if (! conn->handshake->upgrade_websocket ||
! conn->handshake->connection_upgrade ||
! conn->handshake->websocket_key ||
! conn->origin ||
! 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 (strtol (conn->handshake->websocket_version, NULL, 10) != 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->sends (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->sends = 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);
/* 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 4 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 */
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, failed to received content, shutting down id=%d the connection", conn->id);
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);
} 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 ((char*)msg->payload), (char*)msg->payload + 2);
/* get values so the user can get them */
conn->peer_close_status = nopoll_get_16bit (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);
}
/**
* @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)
{
return nopoll_conn_send_frame (conn, nopoll_true, nopoll_false, 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->sends (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) os_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 < 9223372036854775807ULL) {
/* 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;
while (nopoll_true) {
/* try to write bytes */
if (sleep_in_header == 0) {
bytes_written = conn->sends (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->sends (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->sends (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*)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);
}
/* @} */