/**************************************************************************//** * @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 © 2017 AT&T Intellectual Property. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *****************************************************************************/ #include #include #include #include #include #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] = ""; /**************************************************************************//** * 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; }