aboutsummaryrefslogtreecommitdiffstats
path: root/vnfs/VES/code/evel_library/evel_event_mgr.c
diff options
context:
space:
mode:
Diffstat (limited to 'vnfs/VES/code/evel_library/evel_event_mgr.c')
-rw-r--r--vnfs/VES/code/evel_library/evel_event_mgr.c1053
1 files changed, 1053 insertions, 0 deletions
diff --git a/vnfs/VES/code/evel_library/evel_event_mgr.c b/vnfs/VES/code/evel_library/evel_event_mgr.c
new file mode 100644
index 00000000..0cc049f5
--- /dev/null
+++ b/vnfs/VES/code/evel_library/evel_event_mgr.c
@@ -0,0 +1,1053 @@
+/**************************************************************************//**
+ * @file
+ * Event Manager
+ *
+ * Simple event manager that is responsible for taking events (Heartbeats,
+ * Faults and Measurements) from the ring-buffer and posting them to the API.
+ *
+ * License
+ * -------
+ *
+ * Copyright(c) <2016>, AT&T Intellectual Property. All other rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement: This product includes
+ * software developed by the AT&T.
+ * 4. Neither the name of AT&T nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AT&T INTELLECTUAL PROPERTY ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AT&T INTELLECTUAL PROPERTY BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*****************************************************************************/
+
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+#include <curl/curl.h>
+
+#include "evel.h"
+#include "evel_internal.h"
+#include "ring_buffer.h"
+#include "evel_throttle.h"
+
+/**************************************************************************//**
+ * How long we're prepared to wait for the API service to respond in
+ * seconds.
+ *****************************************************************************/
+static const int EVEL_API_TIMEOUT = 5;
+
+/*****************************************************************************/
+/* Prototypes of locally scoped functions. */
+/*****************************************************************************/
+static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp);
+static void * event_handler(void *arg);
+static bool evel_handle_response_tokens(const MEMORY_CHUNK * const chunk,
+ const jsmntok_t * const json_tokens,
+ const int num_tokens,
+ MEMORY_CHUNK * const post);
+static bool evel_tokens_match_command_list(const MEMORY_CHUNK * const chunk,
+ const jsmntok_t * const json_token,
+ const int num_tokens);
+static bool evel_token_equals_string(const MEMORY_CHUNK * const chunk,
+ const jsmntok_t * const json_token,
+ const char * check_string);
+
+/**************************************************************************//**
+ * Buffers for error strings from libcurl.
+ *****************************************************************************/
+static char curl_err_string[CURL_ERROR_SIZE] = "<NULL>";
+
+/**************************************************************************//**
+ * Handle for the API into libcurl.
+ *****************************************************************************/
+static CURL * curl_handle = NULL;
+
+/**************************************************************************//**
+ * Special headers that we send.
+ *****************************************************************************/
+static struct curl_slist * hdr_chunk = NULL;
+
+/**************************************************************************//**
+ * Message queue for sending events to the API.
+ *****************************************************************************/
+static ring_buffer event_buffer;
+
+/**************************************************************************//**
+ * Single pending priority post, which can be generated as a result of a
+ * response to an event. Currently only used to respond to a commandList.
+ *****************************************************************************/
+static MEMORY_CHUNK priority_post;
+
+/**************************************************************************//**
+ * The thread which is responsible for handling events off of the ring-buffer
+ * and posting them to the Event Handler API.
+ *****************************************************************************/
+static pthread_t evt_handler_thread;
+
+/**************************************************************************//**
+ * Variable to convey to the event handler thread what the foreground wants it
+ * to do.
+ *****************************************************************************/
+static EVT_HANDLER_STATE evt_handler_state = EVT_HANDLER_UNINITIALIZED;
+
+/**************************************************************************//**
+ * The configured API URL for event and throttling.
+ *****************************************************************************/
+static char * evel_event_api_url;
+static char * evel_throt_api_url;
+
+/**************************************************************************//**
+ * Initialize the event handler.
+ *
+ * Primarily responsible for getting CURL ready for use.
+ *
+ * @param[in] event_api_url
+ * The URL where the Vendor Event Listener API is expected
+ * to be.
+ * @param[in] throt_api_url
+ * The URL where the Throttling API is expected to be.
+ * @param[in] username The username for the Basic Authentication of requests.
+ * @param[in] password The password for the Basic Authentication of requests.
+ * @param verbosity 0 for normal operation, positive values for chattier
+ * logs.
+ *****************************************************************************/
+EVEL_ERR_CODES event_handler_initialize(const char * const event_api_url,
+ const char * const throt_api_url,
+ const char * const username,
+ const char * const password,
+ int verbosity)
+{
+ int rc = EVEL_SUCCESS;
+ CURLcode curl_rc = CURLE_OK;
+
+ EVEL_ENTER();
+
+ /***************************************************************************/
+ /* Check assumptions. */
+ /***************************************************************************/
+ assert(event_api_url != NULL);
+ assert(throt_api_url != NULL);
+ assert(username != NULL);
+ assert(password != NULL);
+
+ /***************************************************************************/
+ /* Store the API URLs. */
+ /***************************************************************************/
+ evel_event_api_url = strdup(event_api_url);
+ assert(evel_event_api_url != NULL);
+ evel_throt_api_url = strdup(throt_api_url);
+ assert(evel_throt_api_url != NULL);
+
+ /***************************************************************************/
+ /* Start the CURL library. Note that this initialization is not threadsafe */
+ /* which imposes a constraint that the EVEL library is initialized before */
+ /* any threads are started. */
+ /***************************************************************************/
+ curl_rc = curl_global_init(CURL_GLOBAL_SSL);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libCURL. Error code=%d", curl_rc);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* Get a curl handle which we'll use for all of our output. */
+ /***************************************************************************/
+ curl_handle = curl_easy_init();
+ if (curl_handle == NULL)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to get libCURL handle");
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* Prime the library to give friendly error codes. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle,
+ CURLOPT_ERRORBUFFER,
+ curl_err_string);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libCURL to provide friendly errors. "
+ "Error code=%d", curl_rc);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* If running in verbose mode generate more output. */
+ /***************************************************************************/
+ if (verbosity > 0)
+ {
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libCURL to be verbose. "
+ "Error code=%d", curl_rc);
+ goto exit_label;
+ }
+ }
+
+ /***************************************************************************/
+ /* Set the URL for the API. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, event_api_url);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libCURL with the API URL. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+ EVEL_INFO("Initializing CURL to send events to: %s", event_api_url);
+
+ /***************************************************************************/
+ /* send all data to this function. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle,
+ CURLOPT_WRITEFUNCTION,
+ evel_write_callback);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libCURL with the write callback. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* some servers don't like requests that are made without a user-agent */
+ /* field, so we provide one. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle,
+ CURLOPT_USERAGENT,
+ "libcurl-agent/1.0");
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libCURL to upload. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* Specify that we are going to POST data. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libCURL to upload. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* we want to use our own read function. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, read_callback);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libCURL to upload using read "
+ "function. Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* All of our events are JSON encoded. We also suppress the */
+ /* Expect: 100-continue header that we would otherwise get since it */
+ /* confuses some servers. */
+ /* */
+ /* @TODO: do AT&T want this behavior? */
+ /***************************************************************************/
+ hdr_chunk = curl_slist_append(hdr_chunk, "Content-type: application/json");
+ hdr_chunk = curl_slist_append(hdr_chunk, "Expect:");
+
+ /***************************************************************************/
+ /* set our custom set of headers. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, hdr_chunk);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libCURL to use custom headers. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* Set the timeout for the operation. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle,
+ CURLOPT_TIMEOUT,
+ EVEL_API_TIMEOUT);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libCURL for API timeout. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* Set that we want Basic authentication with username:password Base-64 */
+ /* encoded for the operation. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libCURL for Basic Authentication. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_USERNAME, username);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libCURL with username. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_PASSWORD, password);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libCURL with password. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* Initialize a message ring-buffer to be used between the foreground and */
+ /* the thread which sends the messages. This can't fail. */
+ /***************************************************************************/
+ ring_buffer_initialize(&event_buffer, EVEL_EVENT_BUFFER_DEPTH);
+
+ /***************************************************************************/
+ /* Initialize the priority post buffer to empty. */
+ /***************************************************************************/
+ priority_post.memory = NULL;
+
+exit_label:
+ EVEL_EXIT();
+
+ return(rc);
+}
+
+/**************************************************************************//**
+ * Run the event handler.
+ *
+ * Spawns the thread responsible for handling events and sending them to the
+ * API.
+ *
+ * @return Status code.
+ * @retval ::EVEL_SUCCESS if everything OK.
+ * @retval One of ::EVEL_ERR_CODES if there was a problem.
+ *****************************************************************************/
+EVEL_ERR_CODES event_handler_run()
+{
+ EVEL_ERR_CODES rc = EVEL_SUCCESS;
+ int pthread_rc = 0;
+
+ EVEL_ENTER();
+
+ /***************************************************************************/
+ /* Start the event handler thread. */
+ /***************************************************************************/
+ evt_handler_state = EVT_HANDLER_INACTIVE;
+ pthread_rc = pthread_create(&evt_handler_thread, NULL, event_handler, NULL);
+ if (pthread_rc != 0)
+ {
+ rc = EVEL_PTHREAD_LIBRARY_FAIL;
+ log_error_state("Failed to start event handler thread. "
+ "Error code=%d", pthread_rc);
+ }
+
+ EVEL_EXIT()
+ return rc;
+}
+
+/**************************************************************************//**
+ * Terminate the event handler.
+ *
+ * Shuts down the event handler thread in as clean a way as possible. Sets the
+ * global exit flag and then signals the thread to interrupt it since it's
+ * most likely waiting on the ring-buffer.
+ *
+ * Having achieved an orderly shutdown of the event handler thread, clean up
+ * the cURL library's resources cleanly.
+ *
+ * @return Status code.
+ * @retval ::EVEL_SUCCESS if everything OK.
+ * @retval One of ::EVEL_ERR_CODES if there was a problem.
+ *****************************************************************************/
+EVEL_ERR_CODES event_handler_terminate()
+{
+ EVEL_ERR_CODES rc = EVEL_SUCCESS;
+
+ EVEL_ENTER();
+ EVENT_INTERNAL *event = NULL;
+
+ /***************************************************************************/
+ /* Make sure that we were initialized before trying to terminate the */
+ /* event handler thread. */
+ /***************************************************************************/
+ if (evt_handler_state != EVT_HANDLER_UNINITIALIZED)
+ {
+ /*************************************************************************/
+ /* Make sure that the event handler knows it's time to die. */
+ /*************************************************************************/
+ event = evel_new_internal_event(EVT_CMD_TERMINATE);
+ if (event == NULL)
+ {
+ /***********************************************************************/
+ /* We failed to get an event, but we don't bail out - we will just */
+ /* clean up what we can and continue on our way, since we're exiting */
+ /* anyway. */
+ /***********************************************************************/
+ EVEL_ERROR("Failed to get internal event - perform dirty exit instead!");
+ }
+ else
+ {
+ /***********************************************************************/
+ /* Post the event then wait for the Event Handler to exit. Set the */
+ /* global command, too, in case the ring-buffer is full. */
+ /***********************************************************************/
+ EVEL_DEBUG("Sending event to Event Hander to request it to exit.");
+ evt_handler_state = EVT_HANDLER_REQUEST_TERMINATE;
+ evel_post_event((EVENT_HEADER *) event);
+ pthread_join(evt_handler_thread, NULL);
+ EVEL_DEBUG("Event Handler thread has exited.");
+ }
+ }
+ else
+ {
+ EVEL_DEBUG("Event handler was not initialized, so no need to kill it");
+ }
+
+ /***************************************************************************/
+ /* Clean-up the cURL library. */
+ /***************************************************************************/
+ if (curl_handle != NULL)
+ {
+ curl_easy_cleanup(curl_handle);
+ curl_handle = NULL;
+ }
+ if (hdr_chunk != NULL)
+ {
+ curl_slist_free_all(hdr_chunk);
+ hdr_chunk = NULL;
+ }
+
+ /***************************************************************************/
+ /* Free off the stored API URL strings. */
+ /***************************************************************************/
+ if (evel_event_api_url != NULL)
+ {
+ free(evel_event_api_url);
+ evel_event_api_url = NULL;
+ }
+ if (evel_throt_api_url != NULL)
+ {
+ free(evel_throt_api_url);
+ evel_throt_api_url = NULL;
+ }
+
+ EVEL_EXIT();
+ return rc;
+}
+
+/**************************************************************************//**
+ * Post an event.
+ *
+ * @note So far as the caller is concerned, successfully posting the event
+ * relinquishes all responsibility for the event - the library will take care
+ * of freeing the event in due course.
+
+ * @param event The event to be posted.
+ *
+ * @returns Status code
+ * @retval EVEL_SUCCESS On success
+ * @retval "One of ::EVEL_ERR_CODES" On failure.
+ *****************************************************************************/
+EVEL_ERR_CODES evel_post_event(EVENT_HEADER * event)
+{
+ int rc = EVEL_SUCCESS;
+
+ EVEL_ENTER();
+
+ /***************************************************************************/
+ /* Check preconditions. */
+ /***************************************************************************/
+ assert(event != NULL);
+
+ /***************************************************************************/
+ /* We need to make sure that we are either initializing or running */
+ /* normally before writing the event into the buffer so that we can */
+ /* guarantee that the ring-buffer empties properly on exit. */
+ /***************************************************************************/
+ if ((evt_handler_state == EVT_HANDLER_ACTIVE) ||
+ (evt_handler_state == EVT_HANDLER_INACTIVE) ||
+ (evt_handler_state == EVT_HANDLER_REQUEST_TERMINATE))
+ {
+ if (ring_buffer_write(&event_buffer, event) == 0)
+ {
+ log_error_state("Failed to write event to buffer - event dropped!");
+ rc = EVEL_EVENT_BUFFER_FULL;
+ evel_free_event(event);
+ }
+ }
+ else
+ {
+ /*************************************************************************/
+ /* System is not in active operation, so reject the event. */
+ /*************************************************************************/
+ log_error_state("Event Handler system not active - event dropped!");
+ rc = EVEL_EVENT_HANDLER_INACTIVE;
+ evel_free_event(event);
+ }
+
+ EVEL_EXIT();
+ return (rc);
+}
+
+/**************************************************************************//**
+ * Post an event to the Vendor Event Listener API.
+ *
+ * @returns Status code
+ * @retval EVEL_SUCCESS On success
+ * @retval "One of ::EVEL_ERR_CODES" On failure.
+ *****************************************************************************/
+static EVEL_ERR_CODES evel_post_api(char * msg, size_t size)
+{
+ int rc = EVEL_SUCCESS;
+ CURLcode curl_rc = CURLE_OK;
+ MEMORY_CHUNK rx_chunk;
+ MEMORY_CHUNK tx_chunk;
+ int http_response_code = 0;
+
+ EVEL_ENTER();
+
+ /***************************************************************************/
+ /* Create the memory chunk to be used for the response to the post. The */
+ /* will be realloced. */
+ /***************************************************************************/
+ rx_chunk.memory = malloc(1);
+ assert(rx_chunk.memory != NULL);
+ rx_chunk.size = 0;
+
+ /***************************************************************************/
+ /* Create the memory chunk to be sent as the body of the post. */
+ /***************************************************************************/
+ tx_chunk.memory = msg;
+ tx_chunk.size = size;
+ EVEL_DEBUG("Sending chunk of size %d", tx_chunk.size);
+
+ /***************************************************************************/
+ /* Point to the data to be received. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &rx_chunk);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libCURL to upload. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+ EVEL_DEBUG("Initialized data to receive");
+
+ /***************************************************************************/
+ /* Pointer to pass to our read function */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READDATA, &tx_chunk);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to set upload data for libCURL to upload. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+ EVEL_DEBUG("Initialized data to send");
+
+ /***************************************************************************/
+ /* Size of the data to transmit. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle,
+ CURLOPT_POSTFIELDSIZE,
+ tx_chunk.size);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to set length of upload data for libCURL to "
+ "upload. Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+ EVEL_DEBUG("Initialized length of data to send");
+
+ /***************************************************************************/
+ /* Now run off and do what you've been told! */
+ /***************************************************************************/
+ curl_rc = curl_easy_perform(curl_handle);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to transfer an event to Vendor Event Listener! "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ EVEL_ERROR("Dropped event: %s", msg);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* See what response we got - any 2XX response is good. */
+ /***************************************************************************/
+ curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code);
+ EVEL_DEBUG("HTTP response code: %d", http_response_code);
+ if ((http_response_code / 100) == 2)
+ {
+ /*************************************************************************/
+ /* If the server responded with data it may be interesting but not a */
+ /* problem. */
+ /*************************************************************************/
+ if ((rx_chunk.size > 0) && (rx_chunk.memory != NULL))
+ {
+ EVEL_DEBUG("Server returned data = %d (%s)",
+ rx_chunk.size,
+ rx_chunk.memory);
+
+ /***********************************************************************/
+ /* If this is a response to priority post, then we're not interested. */
+ /***********************************************************************/
+ if (priority_post.memory != NULL)
+ {
+ EVEL_ERROR("Ignoring priority post response");
+ }
+ else
+ {
+ evel_handle_event_response(&rx_chunk, &priority_post);
+ }
+ }
+ }
+ else
+ {
+ EVEL_ERROR("Unexpected HTTP response code: %d with data size %d (%s)",
+ http_response_code,
+ rx_chunk.size,
+ rx_chunk.size > 0 ? rx_chunk.memory : "NONE");
+ EVEL_ERROR("Potentially dropped event: %s", msg);
+ }
+
+exit_label:
+ free(rx_chunk.memory);
+ EVEL_EXIT();
+ return(rc);
+}
+
+/**************************************************************************//**
+ * Callback function to provide data to send.
+ *
+ * Copy data into the supplied buffer, read_callback::ptr, checking size
+ * limits.
+ *
+ * @returns Number of bytes placed into read_callback::ptr. 0 for EOF.
+ *****************************************************************************/
+static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
+{
+ size_t rtn = 0;
+ size_t bytes_to_write = 0;
+ MEMORY_CHUNK *tx_chunk = (MEMORY_CHUNK *)userp;
+
+ EVEL_ENTER();
+
+ bytes_to_write = min(size*nmemb, tx_chunk->size);
+
+ if (bytes_to_write > 0)
+ {
+ EVEL_DEBUG("Going to try to write %d bytes", bytes_to_write);
+ strncpy((char *)ptr, tx_chunk->memory, bytes_to_write);
+ tx_chunk->memory += bytes_to_write;
+ tx_chunk->size -= bytes_to_write;
+ rtn = bytes_to_write;
+ }
+ else
+ {
+ EVEL_DEBUG("Reached EOF");
+ }
+
+ EVEL_EXIT();
+ return rtn;
+}
+
+/**************************************************************************//**
+ * Callback function to provide returned data.
+ *
+ * Copy data into the supplied buffer, write_callback::ptr, checking size
+ * limits.
+ *
+ * @returns Number of bytes placed into write_callback::ptr. 0 for EOF.
+ *****************************************************************************/
+size_t evel_write_callback(void *contents,
+ size_t size,
+ size_t nmemb,
+ void *userp)
+{
+ size_t realsize = size * nmemb;
+ MEMORY_CHUNK * rx_chunk = (MEMORY_CHUNK *)userp;
+
+ EVEL_ENTER();
+
+ EVEL_DEBUG("Called with %d chunks of %d size = %d", nmemb, size, realsize);
+ EVEL_DEBUG("rx chunk size is %d", rx_chunk->size);
+
+ rx_chunk->memory = realloc(rx_chunk->memory, rx_chunk->size + realsize + 1);
+ if(rx_chunk->memory == NULL) {
+ /* out of memory! */
+ printf("not enough memory (realloc returned NULL)\n");
+ return 0;
+ }
+
+ memcpy(&(rx_chunk->memory[rx_chunk->size]), contents, realsize);
+ rx_chunk->size += realsize;
+ rx_chunk->memory[rx_chunk->size] = 0;
+
+ EVEL_DEBUG("Rx data: %s", rx_chunk->memory);
+ EVEL_DEBUG("Returning: %d", realsize);
+
+ EVEL_EXIT();
+ return realsize;
+}
+
+/**************************************************************************//**
+ * Event Handler.
+ *
+ * Watch for messages coming on the internal queue and send them to the
+ * listener.
+ *
+ * param[in] arg Argument - unused.
+ *****************************************************************************/
+static void * event_handler(void * arg __attribute__ ((unused)))
+{
+ int old_type = 0;
+ EVENT_HEADER * msg = NULL;
+ EVENT_INTERNAL * internal_msg = NULL;
+ int json_size = 0;
+ char json_body[EVEL_MAX_JSON_BODY];
+ int rc = EVEL_SUCCESS;
+ CURLcode curl_rc;
+
+ EVEL_INFO("Event handler thread started");
+
+ /***************************************************************************/
+ /* Set this thread to be cancellable immediately. */
+ /***************************************************************************/
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type);
+
+ /***************************************************************************/
+ /* Set the handler as active, defending against weird situations like */
+ /* immediately shutting down after initializing the library so the */
+ /* handler never gets started up properly. */
+ /***************************************************************************/
+ if (evt_handler_state == EVT_HANDLER_INACTIVE)
+ {
+ evt_handler_state = EVT_HANDLER_ACTIVE;
+ }
+ else
+ {
+ EVEL_ERROR("Event Handler State was not INACTIVE at start-up - "
+ "Handler will exit immediately!");
+ }
+
+ while (evt_handler_state == EVT_HANDLER_ACTIVE)
+ {
+ /*************************************************************************/
+ /* Wait for a message to be received. */
+ /*************************************************************************/
+ EVEL_DEBUG("Event handler getting any messages");
+ msg = ring_buffer_read(&event_buffer);
+
+ /*************************************************************************/
+ /* Internal events get special treatment while regular events get posted */
+ /* to the far side. */
+ /*************************************************************************/
+ if (msg->event_domain != EVEL_DOMAIN_INTERNAL)
+ {
+ EVEL_DEBUG("External event received");
+
+ /***********************************************************************/
+ /* Encode the event in JSON. */
+ /***********************************************************************/
+ json_size = evel_json_encode_event(json_body, EVEL_MAX_JSON_BODY, msg);
+
+ /***********************************************************************/
+ /* Send the JSON across the API. */
+ /***********************************************************************/
+ EVEL_DEBUG("Sending JSON of size %d is: %s", json_size, json_body);
+ rc = evel_post_api(json_body, json_size);
+ if (rc != EVEL_SUCCESS)
+ {
+ EVEL_ERROR("Failed to transfer the data. Error code=%d", rc);
+ }
+ }
+ else
+ {
+ EVEL_DEBUG("Internal event received");
+ internal_msg = (EVENT_INTERNAL *) msg;
+ assert(internal_msg->command == EVT_CMD_TERMINATE);
+ evt_handler_state = EVT_HANDLER_TERMINATING;
+ }
+
+ /*************************************************************************/
+ /* We are responsible for freeing the memory. */
+ /*************************************************************************/
+ evel_free_event(msg);
+ msg = NULL;
+
+ /*************************************************************************/
+ /* There may be a single priority post to be sent. */
+ /*************************************************************************/
+ if (priority_post.memory != NULL)
+ {
+ EVEL_DEBUG("Priority Post");
+
+ /***********************************************************************/
+ /* Set the URL for the throttling API. */
+ /***********************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_throt_api_url);
+ if (curl_rc != CURLE_OK)
+ {
+ /*********************************************************************/
+ /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which */
+ /* case we carry on regardless. */
+ /*********************************************************************/
+ EVEL_ERROR("Failed to set throttling URL. Error code=%d", rc);
+ }
+ else
+ {
+ rc = evel_post_api(priority_post.memory, priority_post.size);
+ if (rc != EVEL_SUCCESS)
+ {
+ EVEL_ERROR("Failed to transfer priority post. Error code=%d", rc);
+ }
+ }
+
+ /***********************************************************************/
+ /* Reinstate the URL for the event API. */
+ /***********************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_event_api_url);
+ if (curl_rc != CURLE_OK)
+ {
+ /*********************************************************************/
+ /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which */
+ /* case we carry on regardless. */
+ /*********************************************************************/
+ EVEL_ERROR("Failed to reinstate events URL. Error code=%d", rc);
+ }
+
+ /***********************************************************************/
+ /* We are responsible for freeing the memory. */
+ /***********************************************************************/
+ free(priority_post.memory);
+ priority_post.memory = NULL;
+ }
+ }
+
+ /***************************************************************************/
+ /* The event handler is now exiting. The ring-buffer could contain events */
+ /* which have not been processed, so deplete those. Because we've been */
+ /* asked to exit we can be confident that the foreground will have stopped */
+ /* sending events in so we know that this process will conclude! */
+ /***************************************************************************/
+ evt_handler_state = EVT_HANDLER_TERMINATING;
+ while (!ring_buffer_is_empty(&event_buffer))
+ {
+ EVEL_DEBUG("Reading event from buffer");
+ msg = ring_buffer_read(&event_buffer);
+ evel_free_event(msg);
+ }
+ evt_handler_state = EVT_HANDLER_TERMINATED;
+ EVEL_INFO("Event handler thread stopped");
+
+ return (NULL);
+}
+
+/**************************************************************************//**
+ * Handle a JSON response from the listener, contained in a ::MEMORY_CHUNK.
+ *
+ * Tokenize the response, and decode any tokens found.
+ *
+ * @param chunk The memory chunk containing the response.
+ * @param post The memory chunk in which to place any resulting POST.
+ *****************************************************************************/
+void evel_handle_event_response(const MEMORY_CHUNK * const chunk,
+ MEMORY_CHUNK * const post)
+{
+ jsmn_parser json_parser;
+ jsmntok_t json_tokens[EVEL_MAX_RESPONSE_TOKENS];
+ int num_tokens = 0;
+
+ EVEL_ENTER();
+
+ /***************************************************************************/
+ /* Check preconditions. */
+ /***************************************************************************/
+ assert(chunk != NULL);
+ assert(priority_post.memory == NULL);
+
+ EVEL_DEBUG("Response size = %d", chunk->size);
+ EVEL_DEBUG("Response = %s", chunk->memory);
+
+ /***************************************************************************/
+ /* Initialize the parser and tokenize the response. */
+ /***************************************************************************/
+ jsmn_init(&json_parser);
+ num_tokens = jsmn_parse(&json_parser,
+ chunk->memory,
+ chunk->size,
+ json_tokens,
+ EVEL_MAX_RESPONSE_TOKENS);
+
+ if (num_tokens < 0)
+ {
+ EVEL_ERROR("Failed to parse JSON response. "
+ "Error code=%d", num_tokens);
+ }
+ else if (num_tokens == 0)
+ {
+ EVEL_DEBUG("No tokens found in JSON response");
+ }
+ else
+ {
+ EVEL_DEBUG("Decode JSON response tokens");
+ if (!evel_handle_response_tokens(chunk, json_tokens, num_tokens, post))
+ {
+ EVEL_ERROR("Failed to handle JSON response.");
+ }
+ }
+
+ EVEL_EXIT();
+}
+
+/**************************************************************************//**
+ * Handle a JSON response from the listener, as a list of tokens from JSMN.
+ *
+ * @param chunk Memory chunk containing the JSON buffer.
+ * @param json_tokens Array of tokens to handle.
+ * @param num_tokens The number of tokens to handle.
+ * @param post The memory chunk in which to place any resulting POST.
+ * @return true if we handled the response, false otherwise.
+ *****************************************************************************/
+bool evel_handle_response_tokens(const MEMORY_CHUNK * const chunk,
+ const jsmntok_t * const json_tokens,
+ const int num_tokens,
+ MEMORY_CHUNK * const post)
+{
+ bool json_ok = false;
+
+ EVEL_ENTER();
+
+ /***************************************************************************/
+ /* Check preconditions. */
+ /***************************************************************************/
+ assert(chunk != NULL);
+ assert(json_tokens != NULL);
+ assert(num_tokens < EVEL_MAX_RESPONSE_TOKENS);
+
+ /***************************************************************************/
+ /* Peek at the tokens to decide what the response it, then call the */
+ /* appropriate handler to handle it. There is only one handler at this */
+ /* point. */
+ /***************************************************************************/
+ if (evel_tokens_match_command_list(chunk, json_tokens, num_tokens))
+ {
+ json_ok = evel_handle_command_list(chunk, json_tokens, num_tokens, post);
+ }
+
+ EVEL_EXIT();
+
+ return json_ok;
+}
+
+/**************************************************************************//**
+ * Determine whether a list of tokens looks like a "commandList" response.
+ *
+ * @param chunk Memory chunk containing the JSON buffer.
+ * @param json_tokens Token to check.
+ * @param num_tokens The number of tokens to handle.
+ * @return true if the tokens look like a "commandList" match, or false.
+ *****************************************************************************/
+bool evel_tokens_match_command_list(const MEMORY_CHUNK * const chunk,
+ const jsmntok_t * const json_tokens,
+ const int num_tokens)
+{
+ bool result = false;
+
+ EVEL_ENTER();
+
+ /***************************************************************************/
+ /* Make some checks on the basic layout of the commandList. */
+ /***************************************************************************/
+ if ((num_tokens > 3) &&
+ (json_tokens[0].type == JSMN_OBJECT) &&
+ (json_tokens[1].type == JSMN_STRING) &&
+ (json_tokens[2].type == JSMN_ARRAY) &&
+ (evel_token_equals_string(chunk, &json_tokens[1], "commandList")))
+ {
+ result = true;
+ }
+
+ EVEL_EXIT();
+
+ return result;
+}
+
+/**************************************************************************//**
+ * Check that a string token matches a given input string.
+ *
+ * @param chunk Memory chunk containing the JSON buffer.
+ * @param json_token Token to check.
+ * @param check_string String to check it against.
+ * @return true if the strings match, or false.
+ *****************************************************************************/
+bool evel_token_equals_string(const MEMORY_CHUNK * const chunk,
+ const jsmntok_t * json_token,
+ const char * check_string)
+{
+ bool result = false;
+
+ EVEL_ENTER();
+
+ const int token_length = json_token->end - json_token->start;
+ const char * const token_string = chunk->memory + json_token->start;
+
+ if (token_length == (int)strlen(check_string))
+ {
+ result = (strncmp(token_string, check_string, token_length) == 0);
+ }
+
+ EVEL_EXIT();
+
+ return result;
+}