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

803 lines
23 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
*/
#include <nopoll_ctx.h>
#include <nopoll_private.h>
//#include <signal.h>
/**
* \defgroup nopoll_ctx noPoll Context: context handling functions used by the library
*/
/**
* \addtogroup nopoll_ctx
* @{
*/
void __nopoll_ctx_sigpipe_do_nothing (int _signal)
{
#if !defined(NOPOLL_OS_WIN32)
/* do nothing sigpipe handler to be able to manage EPIPE error
* returned by write ops. */
/* the following line is to ensure ancient glibc version that
* restores to the default handler once the signal handling is
* executed. */
// signal (SIGPIPE, __nopoll_ctx_sigpipe_do_nothing);
#endif
return;
}
/**
* @brief Creates an empty Nopoll context.
*/
noPollCtx * nopoll_ctx_new (void) {
noPollCtx * result = nopoll_new (noPollCtx, 1);
if (result == NULL)
return NULL;
#if defined(NOPOLL_OS_WIN32)
if (! nopoll_win32_init (result))
return NULL;
#endif
/* set initial reference */
result->conn_id = 1;
result->refs = 1;
result->conn_id = 1;
/* 20 seconds for connection timeout */
result->conn_connect_std_timeout = 20000000;
/* default log initialization */
result->not_executed = nopoll_true;
result->debug_enabled = nopoll_false;
/* colored log */
result->not_executed_color = nopoll_true;
result->debug_color_enabled = nopoll_false;
/* default back log */
result->backlog = 5;
/* current list length */
result->conn_length = 0;
#if !defined(NOPOLL_OS_WIN32)
/* install sigpipe handler */
// signal (SIGPIPE, __nopoll_ctx_sigpipe_do_nothing);
#endif
/* setup default protocol version */
result->protocol_version = 13;
/* create mutexes */
result->ref_mutex = nopoll_mutex_create ();
return result;
}
/**
* @brief Allows to acquire a reference to the provided context. This
* reference is released by calling to \ref nopoll_ctx_unref.
*
* @param ctx The context to acquire a reference.
*
* @return The function returns nopoll_true in the case the reference
* was acquired, otherwise nopoll_false is returned.
*/
nopoll_bool nopoll_ctx_ref (noPollCtx * ctx)
{
/* return false value */
nopoll_return_val_if_fail (ctx, ctx, nopoll_false);
/* acquire mutex here */
nopoll_mutex_lock (ctx->ref_mutex);
ctx->refs++;
/* release mutex here */
nopoll_mutex_unlock (ctx->ref_mutex);
return nopoll_true;
}
/**
* @brief allows to release a reference acquired to the provided
* noPoll context.
*
* @param ctx The noPoll context reference to release..
*/
void nopoll_ctx_unref (noPollCtx * ctx)
{
noPollCertificate * cert;
int iterator;
nopoll_return_if_fail (ctx, ctx);
/* acquire mutex here */
nopoll_mutex_lock (ctx->ref_mutex);
ctx->refs--;
if (ctx->refs != 0) {
/* release mutex here */
nopoll_mutex_unlock (ctx->ref_mutex);
return;
}
/* release mutex here */
nopoll_mutex_unlock (ctx->ref_mutex);
nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Releasing no poll context %p (%d, conns: %d)", ctx, ctx->refs, ctx->conn_length);
iterator = 0;
while (iterator < ctx->certificates_length) {
/* get reference */
cert = &(ctx->certificates[iterator]);
/* release */
nopoll_free (cert->serverName);
nopoll_free (cert->certificateFile);
nopoll_free (cert->privateKey);
nopoll_free (cert->optionalChainFile);
/* next position */
iterator++;
} /* end while */
/* release mutex */
nopoll_mutex_destroy (ctx->ref_mutex);
/* release all certificates buckets */
nopoll_free (ctx->certificates);
/* release connection */
nopoll_free (ctx->conn_list);
ctx->conn_length = 0;
nopoll_free (ctx);
return;
}
/**
* @brief Allows to get current reference counting for the provided
* context.
*
* @param ctx The context the reference counting is being requested.
*
* @return The reference counting or -1 if it fails.
*/
int nopoll_ctx_ref_count (noPollCtx * ctx)
{
int result;
if (! ctx)
return -1;
/* lock */
nopoll_mutex_lock (ctx->ref_mutex);
result = ctx->refs;
/* unlock */
nopoll_mutex_unlock (ctx->ref_mutex);
return result;
}
/**
* @internal Function used to register the provided connection on the
* provided context.
*
* @param ctx The context where the connection will be registered.
*
* @param conn The connection to be registered.
*
* @return nopoll_true if the connection was registered, otherwise
* nopoll_false is returned.
*/
nopoll_bool nopoll_ctx_register_conn (noPollCtx * ctx,
noPollConn * conn)
{
int iterator;
nopoll_return_val_if_fail (ctx, ctx && conn, nopoll_false);
/* acquire mutex here */
nopoll_mutex_lock (ctx->ref_mutex);
/* get connection */
conn->id = ctx->conn_id;
ctx->conn_id ++;
/* register connection */
iterator = 0;
while (iterator < ctx->conn_length) {
/* register reference */
if (ctx->conn_list[iterator] == 0) {
ctx->conn_list[iterator] = conn;
/* update connection list number */
ctx->conn_num++;
nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "registered connection id %d, role: %d", conn->id, conn->role);
/* release */
nopoll_mutex_unlock (ctx->ref_mutex);
/* acquire reference */
nopoll_ctx_ref (ctx);
/* acquire a reference to the conection */
nopoll_conn_ref (conn);
/* release mutex here */
return nopoll_true;
}
iterator++;
} /* end while */
/* if reached this place it means no more buckets are
* available, acquire more memory (increase 10 by 10) */
ctx->conn_length += 10;
ctx->conn_list = (noPollConn**) nopoll_realloc (ctx->conn_list, sizeof (noPollConn *) * (ctx->conn_length));
if (ctx->conn_list == NULL) {
/* release mutex */
nopoll_mutex_unlock (ctx->ref_mutex);
nopoll_log (ctx, NOPOLL_LEVEL_CRITICAL, "General connection registration error, memory acquisition failed..");
return nopoll_false;
} /* end if */
/* clear new positions */
iterator = (ctx->conn_length - 10);
while (iterator < ctx->conn_length) {
ctx->conn_list[iterator] = 0;
/* next position */
iterator++;
} /* end while */
/* release mutex here */
nopoll_mutex_unlock (ctx->ref_mutex);
/* ok, now register connection because we have memory */
return nopoll_ctx_register_conn (ctx, conn);
}
/**
* @internal Function used to register the provided connection on the
* provided context.
*
* @param ctx The context where the connection will be registered.
*
* @param conn The connection to be registered.
*/
void nopoll_ctx_unregister_conn (noPollCtx * ctx,
noPollConn * conn)
{
int iterator;
nopoll_return_if_fail (ctx, ctx && conn);
/* acquire mutex here */
nopoll_mutex_lock (ctx->ref_mutex);
/* find the connection and remove it from the array */
iterator = 0;
while (iterator < ctx->conn_length) {
/* check the connection reference */
if (ctx->conn_list && ctx->conn_list[iterator] && ctx->conn_list[iterator]->id == conn->id) {
/* remove reference */
ctx->conn_list[iterator] = NULL;
/* update connection list number */
ctx->conn_num--;
/* release */
nopoll_mutex_unlock (ctx->ref_mutex);
/* acquire a reference to the conection */
nopoll_conn_unref (conn);
break;
} /* end if */
iterator++;
} /* end while */
/* release mutex here */
nopoll_mutex_unlock (ctx->ref_mutex);
return;
}
/**
* @brief Allows to get number of connections currently registered.
*
* @param ctx The context where the operation is requested.
*
* @return Number of connections registered on this context or -1 if it fails.
*/
int nopoll_ctx_conns (noPollCtx * ctx)
{
nopoll_return_val_if_fail (ctx, ctx, -1);
return ctx->conn_num;
}
/**
* @brief Allows to find the certificate associated to the provided serverName.
*
* @param ctx The context where the operation will take place.
*
* @param serverName the servername to use as pattern to find the
* right certificate. If NULL is provided the first certificate not
* refering to any serverName will be returned.
*
* @param certificateFile If provided a reference and the function
* returns nopoll_true, it will contain the certificateFile found.
*
* @param privateKey If provided a reference and the function
* returns nopoll_true, it will contain the privateKey found.
*
* @param optionalChainFile If provided a reference and the function
* returns nopoll_true, it will contain the optionalChainFile found.
*
* @return nopoll_true in the case the certificate was found,
* otherwise nopoll_false is returned.
*/
nopoll_bool nopoll_ctx_find_certificate (noPollCtx * ctx,
const char * serverName,
const char ** certificateFile,
const char ** privateKey,
const char ** optionalChainFile)
{
noPollCertificate * cert;
int iterator = 0;
nopoll_return_val_if_fail (ctx, ctx, nopoll_false);
nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Finding a certificate for serverName=%s", serverName ? serverName : "<not defined>");
while (iterator < ctx->certificates_length) {
/* get cert */
cert = &(ctx->certificates[iterator]);
if (cert) {
/* found a certificate */
nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, " certificate stored associated to serverName=%s", cert->serverName ? cert->serverName : "<not defined>");
if ((serverName == NULL && cert->serverName == NULL) ||
(nopoll_cmp (serverName, cert->serverName))) {
if (certificateFile)
(*certificateFile) = cert->certificateFile;
if (privateKey)
(*privateKey) = cert->privateKey;
if (optionalChainFile)
(*optionalChainFile) = cert->optionalChainFile;
return nopoll_true;
} /* end if */
} /* end if */
/* next position */
iterator++;
}
/* check for default certificate when serverName isn't defined */
if (serverName == NULL) {
/* requested a certificate for an undefined serverName */
iterator = 0;
while (iterator < ctx->certificates_length) {
/* get cert */
cert = &(ctx->certificates[iterator]);
if (cert) {
/* found a certificate */
nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, " serverName not defined, selecting first certificate from the list");
if (certificateFile)
(*certificateFile) = cert->certificateFile;
if (privateKey)
(*privateKey) = cert->privateKey;
if (optionalChainFile)
(*optionalChainFile) = cert->optionalChainFile;
return nopoll_true;
} /* end if */
} /* end if */
/* next position */
iterator++;
} /* end if */
return nopoll_false;
}
/**
* @brief Allows to install a certificate to be used in general by all
* listener connections working under the provided context.
*
* @param ctx The context where the certificate will be installed.
*
* @param serverName The optional server name to to limit the use of
* this certificate to the value provided here. Provide a NULL value
* to make the certificate provide to work under any server notified
* (Host: header) or via SNI (server name identification associated to
* the TLS transport).
*
* @param certificateFile The certificate file to be installed.
*
* @param privateKey The private key file to use used.
*
* @param optionalChainFile Optional chain file with additional
* material to complete the certificate definition.
*
* @return nopoll_true if the certificate was installed otherwise
* nopoll_false. The function returns nopoll_false when ctx, certificateFile or privateKey are NULL.
*/
nopoll_bool nopoll_ctx_set_certificate (noPollCtx * ctx,
const char * serverName,
const char * certificateFile,
const char * privateKey,
const char * optionalChainFile)
{
int length;
noPollCertificate * cert;
/* check values before proceed */
nopoll_return_val_if_fail (ctx, ctx && certificateFile && privateKey, nopoll_false);
/* check if the certificate is already installed */
if (nopoll_ctx_find_certificate (ctx, serverName, NULL, NULL, NULL))
return nopoll_true;
/* update certificate storage to hold all values */
ctx->certificates_length++;
length = ctx->certificates_length;
if (length == 1)
ctx->certificates = nopoll_new (noPollCertificate, 1);
else
ctx->certificates = (noPollCertificate *) nopoll_realloc (ctx->certificates, sizeof (noPollCertificate) * (length));
/* hold certificate */
cert = &(ctx->certificates[length - 1]);
cert->serverName = NULL;
if (serverName)
cert->serverName = nopoll_strdup (serverName);
cert->certificateFile = NULL;
if (certificateFile)
cert->certificateFile = nopoll_strdup (certificateFile);
cert->privateKey = NULL;
if (privateKey)
cert->privateKey = nopoll_strdup (privateKey);
cert->optionalChainFile = NULL;
if (optionalChainFile)
cert->optionalChainFile = nopoll_strdup (optionalChainFile);
return nopoll_true;
}
/**
* @brief Allows to configure the on open handler, the handler that is
* called when it is received an incoming websocket connection and all
* websocket client handshake data was received (but still not required).
*
* This handler differs from \ref nopoll_ctx_set_on_accept this
* handler is called after all client handshake data was received.
*
* Note the connection is still not fully working at this point
* because the handshake hasn't been sent to the remote peer yet. This
* means that attempting to send any content inside this handler (for
* example by using \ref nopoll_conn_send_text) will cause a protocol
* violation (because remote side is expecting a handshake reply but
* received something different).
*
* In the case you want to sent content right away after receiving a
* connection (on a listener), you can use \ref
* nopoll_ctx_set_on_ready "On Ready" handler which is called just
* after the connection has been fully accepted and handshake reply is
* fully written.
*
* @param ctx The context that will be configured.
*
* @param on_open The handler to be configured on this context.
*
* @param user_data User defined pointer to be passed to the on open
* handler
*/
void nopoll_ctx_set_on_open (noPollCtx * ctx,
noPollActionHandler on_open,
noPollPtr user_data)
{
nopoll_return_if_fail (ctx, ctx && on_open);
/* set the handler */
ctx->on_open = on_open;
if (ctx->on_open == NULL)
ctx->on_open_data = NULL;
else
ctx->on_open_data = user_data;
return;
}
/**
* @brief Allows to configure a handler that is called when a
* connection is received and it 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 ctx The context that will be configured.
*
* @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_ctx_set_on_ready (noPollCtx * ctx,
noPollActionHandler on_ready,
noPollPtr user_data)
{
nopoll_return_if_fail (ctx, ctx && on_ready);
/* set the handler */
ctx->on_ready = on_ready;
if (ctx->on_ready == NULL)
ctx->on_ready_data = NULL;
else
ctx->on_ready_data = user_data;
return;
}
/**
* @brief Allows to configure the accept handler that will be called
* when a connection is received but before any handshake takes place.
*
* @param ctx The context that will be configured.
*
* @param on_accept The handler to be called when a connection is
* received. Here the handler must return nopoll_true to accept the
* connection, otherwise nopoll_false should be returned.
*
* @param user_data Optional user data pointer passed to the on accept
* handler.
*
*/
void nopoll_ctx_set_on_accept (noPollCtx * ctx,
noPollActionHandler on_accept,
noPollPtr user_data)
{
nopoll_return_if_fail (ctx, ctx && on_accept);
/* set the handler */
ctx->on_accept = on_accept;
if (ctx->on_accept == NULL)
ctx->on_accept_data = NULL;
else
ctx->on_accept_data = user_data;
return;
}
/**
* @brief Allows to set a general handler to get notifications about a
* message received over any connection that is running under the
* provided context (noPollCtx).
*
* @param ctx The context where the notification will happen
*
* @param on_msg The handler to be called when an incoming message is
* received.
*
* @param user_data User defined pointer that is passed in into the
* handler when called.
*
* Note that the handler configured here will be overriden by the handler configured by \ref nopoll_conn_set_on_msg
*
*/
void nopoll_ctx_set_on_msg (noPollCtx * ctx,
noPollOnMessageHandler on_msg,
noPollPtr user_data)
{
nopoll_return_if_fail (ctx, ctx);
/* set new handler */
ctx->on_msg = on_msg;
ctx->on_msg_data = user_data;
return;
}
/**
* @brief Allows to configure the handler that will be used to let
* user land code to define OpenSSL SSL_CTX object.
*
* By default, SSL_CTX (SSL Context) object is created by default
* settings that works for most of the cases. In the case you want to
* configure particular configurations that should be enabled on the
* provided SSL_CTX that is going to be used by the client ---while
* connecting--- or server ---while receiving a connection--- then use
* this function to setup your creator handler.
*
* See \ref noPollSslContextCreator for more information about this
* handler.
*
*/
void nopoll_ctx_set_ssl_context_creator (noPollCtx * ctx,
noPollSslContextCreator context_creator,
noPollPtr user_data)
{
if (ctx == NULL)
return;
/* set handlers as indicated by the caller */
ctx->context_creator = context_creator;
ctx->context_creator_data = user_data;
return;
}
/**
* @brief Allows to configure a function that will implement an post SSL/TLS check.
*
* See the following function to get more information: \ref noPollSslPostCheck
*
* @param ctx The context where the operation is taking place.
*
* @param post_ssl_check The handler that is going to be called
* everything a new connection with SSL is established by a client or
* received by a server. The handler is executed right after the SSL
* handshake finishes without error.
*
* @param user_data A reference to user defined pointer that will be
* passed in to the handler.
*/
void nopoll_ctx_set_post_ssl_check (noPollCtx * ctx,
noPollSslPostCheck post_ssl_check,
noPollPtr user_data)
{
if (ctx == NULL)
return;
/* set handlers as indicated by the caller */
ctx->post_ssl_check = post_ssl_check;
ctx->post_ssl_check_data = user_data;
return;
}
/**
* @brief Allows to iterate over all connections currently registered
* on the provided context, optionally stopping the foreach process,
* returning the connection reference selected if the foreach handler
* returns nopoll_true.
*
* @param ctx The nopoll context where the foreach operation will take
* place.
*
* @param foreach The foreach handler to be called for each connection
* registered.
*
* @param user_data An optional reference to a pointer that will be
* passed to the handler.
*
* @return Returns the connection selected (in the case the foreach
* function returns nopoll_false) or NULL in the case all foreach
* executions returned nopoll_true. Keep in mind the function also
* returns NULL if ctx or foreach parameter is NULL.
*
* See \ref noPollForeachConn for a signature example.
*/
noPollConn * nopoll_ctx_foreach_conn (noPollCtx * ctx,
noPollForeachConn foreach,
noPollPtr user_data)
{
noPollConn * result;
int iterator;
nopoll_return_val_if_fail (ctx, ctx && foreach, NULL);
/* acquire here the mutex to protect connection list */
nopoll_mutex_lock (ctx->ref_mutex);
/* nopoll_log (ctx, NOPOLL_LEVEL_DEBUG, "Doing foreach over conn_length array (%p): %d", ctx, ctx->conn_length); */
/* find the connection and remove it from the array */
iterator = 0;
while (iterator < ctx->conn_length) {
/* check the connection reference */
if (ctx->conn_list[iterator]) {
/* call to notify connection */
if (foreach (ctx, ctx->conn_list[iterator], user_data)) {
/* get a reference to avoid races
* after releasing the mutex */
result = ctx->conn_list[iterator];
/* release */
nopoll_mutex_unlock (ctx->ref_mutex);
/* release here the mutex to protect connection list */
return result;
} /* end if */
} /* end if */
iterator++;
} /* end while */
/* release here the mutex to protect connection list */
nopoll_mutex_unlock (ctx->ref_mutex);
return NULL;
}
/**
* @brief Allows to change the protocol version that is send in all
* client connections created under the provided context and the
* protocol version accepted by listener created under this context
* too.
*
* This is a really basic (mostly fake) protocol version support
* because it only allows to change the version string sent (but
* nothing more for now). It is useful for testing purposes.
*
* @param ctx The noPoll context where the protocol version change
* will be applied.
*
* @param version The value representing the protocol version. By
* default this function isn't required to be called because it
* already has the right protocol value configured (13).
*/
void nopoll_ctx_set_protocol_version (noPollCtx * ctx, int version)
{
/* check input data */
nopoll_return_if_fail (ctx, ctx || version);
/* setup the new protocol version */
ctx->protocol_version = version;
return;
}
/* @} */