1 /*************************************************************************//**
3 * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
5 * Unless otherwise specified, all software contained herein is
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 * ECOMP is a trademark and service mark of AT&T Intellectual Property.
17 ****************************************************************************/
19 /**************************************************************************//**
23 * Simple event manager that is responsible for taking events (Heartbeats,
24 * Faults and Measurements) from the ring-buffer and posting them to the API.
26 ****************************************************************************/
33 #include <curl/curl.h>
36 #include "evel_internal.h"
37 #include "ring_buffer.h"
38 #include "evel_throttle.h"
40 /**************************************************************************//**
41 * How long we're prepared to wait for the API service to respond in
43 *****************************************************************************/
44 static const int EVEL_API_TIMEOUT = 5;
46 /*****************************************************************************/
47 /* Prototypes of locally scoped functions. */
48 /*****************************************************************************/
49 static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp);
50 static void * event_handler(void *arg);
51 static bool evel_handle_response_tokens(const MEMORY_CHUNK * const chunk,
52 const jsmntok_t * const json_tokens,
54 MEMORY_CHUNK * const post);
55 static bool evel_tokens_match_command_list(const MEMORY_CHUNK * const chunk,
56 const jsmntok_t * const json_token,
57 const int num_tokens);
58 static bool evel_token_equals_string(const MEMORY_CHUNK * const chunk,
59 const jsmntok_t * const json_token,
60 const char * check_string);
62 /**************************************************************************//**
63 * Buffers for error strings from libcurl.
64 *****************************************************************************/
65 static char curl_err_string[CURL_ERROR_SIZE] = "<NULL>";
67 /**************************************************************************//**
68 * Handle for the API into libcurl.
69 *****************************************************************************/
70 static CURL * curl_handle = NULL;
72 /**************************************************************************//**
73 * Special headers that we send.
74 *****************************************************************************/
75 static struct curl_slist * hdr_chunk = NULL;
77 /**************************************************************************//**
78 * Message queue for sending events to the API.
79 *****************************************************************************/
80 static ring_buffer event_buffer;
82 /**************************************************************************//**
83 * Single pending priority post, which can be generated as a result of a
84 * response to an event. Currently only used to respond to a commandList.
85 *****************************************************************************/
86 static MEMORY_CHUNK priority_post;
88 /**************************************************************************//**
89 * The thread which is responsible for handling events off of the ring-buffer
90 * and posting them to the Event Handler API.
91 *****************************************************************************/
92 static pthread_t evt_handler_thread;
94 /**************************************************************************//**
95 * Variable to convey to the event handler thread what the foreground wants it
97 *****************************************************************************/
98 static EVT_HANDLER_STATE evt_handler_state = EVT_HANDLER_UNINITIALIZED;
100 /**************************************************************************//**
101 * The configured API URL for event and throttling.
102 *****************************************************************************/
103 static char * evel_event_api_url;
104 static char * evel_throt_api_url;
106 /**************************************************************************//**
107 * Initialize the event handler.
109 * Primarily responsible for getting CURL ready for use.
111 * @param[in] event_api_url
112 * The URL where the Vendor Event Listener API is expected
114 * @param[in] throt_api_url
115 * The URL where the Throttling API is expected to be.
116 * @param[in] username The username for the Basic Authentication of requests.
117 * @param[in] password The password for the Basic Authentication of requests.
118 * @param verbosity 0 for normal operation, positive values for chattier
120 *****************************************************************************/
121 EVEL_ERR_CODES event_handler_initialize(const char * const event_api_url,
122 const char * const throt_api_url,
123 const char * const username,
124 const char * const password,
127 int rc = EVEL_SUCCESS;
128 CURLcode curl_rc = CURLE_OK;
132 /***************************************************************************/
133 /* Check assumptions. */
134 /***************************************************************************/
135 assert(event_api_url != NULL);
136 assert(throt_api_url != NULL);
137 assert(username != NULL);
138 assert(password != NULL);
140 /***************************************************************************/
141 /* Store the API URLs. */
142 /***************************************************************************/
143 evel_event_api_url = strdup(event_api_url);
144 assert(evel_event_api_url != NULL);
145 evel_throt_api_url = strdup(throt_api_url);
146 assert(evel_throt_api_url != NULL);
149 curl_version_info_data *d = curl_version_info(CURLVERSION_NOW);
150 /* compare with the 24 bit hex number in 8 bit fields */
151 if(d->version_num >= 0x072100) {
152 /* this is libcurl 7.33.0 or later */
153 EVEL_INFO("7.33 or later Curl version %x.",d->version_num);
156 EVEL_INFO("Old Curl version.");
158 /***************************************************************************/
159 /* Start the CURL library. Note that this initialization is not threadsafe */
160 /* which imposes a constraint that the EVEL library is initialized before */
161 /* any threads are started. */
162 /***************************************************************************/
163 curl_rc = curl_global_init(CURL_GLOBAL_SSL);
164 if (curl_rc != CURLE_OK)
166 rc = EVEL_CURL_LIBRARY_FAIL;
167 log_error_state("Failed to initialize libCURL. Error code=%d", curl_rc);
171 /***************************************************************************/
172 /* Get a curl handle which we'll use for all of our output. */
173 /***************************************************************************/
174 curl_handle = curl_easy_init();
175 if (curl_handle == NULL)
177 rc = EVEL_CURL_LIBRARY_FAIL;
178 log_error_state("Failed to get libCURL handle");
182 /***************************************************************************/
183 /* Prime the library to give friendly error codes. */
184 /***************************************************************************/
185 curl_rc = curl_easy_setopt(curl_handle,
188 if (curl_rc != CURLE_OK)
190 rc = EVEL_CURL_LIBRARY_FAIL;
191 log_error_state("Failed to initialize libCURL to provide friendly errors. "
192 "Error code=%d", curl_rc);
196 /***************************************************************************/
197 /* If running in verbose mode generate more output. */
198 /***************************************************************************/
201 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
202 if (curl_rc != CURLE_OK)
204 rc = EVEL_CURL_LIBRARY_FAIL;
205 log_error_state("Failed to initialize libCURL to be verbose. "
206 "Error code=%d", curl_rc);
211 /***************************************************************************/
212 /* Set the URL for the API. */
213 /***************************************************************************/
214 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, event_api_url);
215 if (curl_rc != CURLE_OK)
217 rc = EVEL_CURL_LIBRARY_FAIL;
218 log_error_state("Failed to initialize libCURL with the API URL. "
219 "Error code=%d (%s)", curl_rc, curl_err_string);
222 EVEL_INFO("Initializing CURL to send events to: %s", event_api_url);
224 /***************************************************************************/
225 /* send all data to this function. */
226 /***************************************************************************/
227 curl_rc = curl_easy_setopt(curl_handle,
228 CURLOPT_WRITEFUNCTION,
229 evel_write_callback);
230 if (curl_rc != CURLE_OK)
232 rc = EVEL_CURL_LIBRARY_FAIL;
233 log_error_state("Failed to initialize libCURL with the write callback. "
234 "Error code=%d (%s)", curl_rc, curl_err_string);
238 /***************************************************************************/
239 /* some servers don't like requests that are made without a user-agent */
240 /* field, so we provide one. */
241 /***************************************************************************/
242 curl_rc = curl_easy_setopt(curl_handle,
244 "libcurl-agent/1.0");
245 if (curl_rc != CURLE_OK)
247 rc = EVEL_CURL_LIBRARY_FAIL;
248 log_error_state("Failed to initialize libCURL to upload. "
249 "Error code=%d (%s)", curl_rc, curl_err_string);
253 /***************************************************************************/
254 /* Specify that we are going to POST data. */
255 /***************************************************************************/
256 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
257 if (curl_rc != CURLE_OK)
259 rc = EVEL_CURL_LIBRARY_FAIL;
260 log_error_state("Failed to initialize libCURL to upload. "
261 "Error code=%d (%s)", curl_rc, curl_err_string);
265 /***************************************************************************/
266 /* we want to use our own read function. */
267 /***************************************************************************/
268 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, read_callback);
269 if (curl_rc != CURLE_OK)
271 rc = EVEL_CURL_LIBRARY_FAIL;
272 log_error_state("Failed to initialize libCURL to upload using read "
273 "function. Error code=%d (%s)", curl_rc, curl_err_string);
277 /***************************************************************************/
278 /* All of our events are JSON encoded. We also suppress the */
279 /* Expect: 100-continue header that we would otherwise get since it */
280 /* confuses some servers. */
282 /* @TODO: do AT&T want this behavior? */
283 /***************************************************************************/
284 hdr_chunk = curl_slist_append(hdr_chunk, "Content-type: application/json");
285 hdr_chunk = curl_slist_append(hdr_chunk, "Expect:");
287 /***************************************************************************/
288 /* set our custom set of headers. */
289 /***************************************************************************/
290 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, hdr_chunk);
291 if (curl_rc != CURLE_OK)
293 rc = EVEL_CURL_LIBRARY_FAIL;
294 log_error_state("Failed to initialize libCURL to use custom headers. "
295 "Error code=%d (%s)", curl_rc, curl_err_string);
299 /***************************************************************************/
300 /* Set the timeout for the operation. */
301 /***************************************************************************/
302 curl_rc = curl_easy_setopt(curl_handle,
305 if (curl_rc != CURLE_OK)
307 rc = EVEL_CURL_LIBRARY_FAIL;
308 log_error_state("Failed to initialize libCURL for API timeout. "
309 "Error code=%d (%s)", curl_rc, curl_err_string);
313 /***************************************************************************/
314 /* Set that we want Basic authentication with username:password Base-64 */
315 /* encoded for the operation. */
316 /***************************************************************************/
317 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
318 if (curl_rc != CURLE_OK)
320 rc = EVEL_CURL_LIBRARY_FAIL;
321 log_error_state("Failed to initialize libCURL for Basic Authentication. "
322 "Error code=%d (%s)", curl_rc, curl_err_string);
325 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_USERNAME, username);
326 if (curl_rc != CURLE_OK)
328 rc = EVEL_CURL_LIBRARY_FAIL;
329 log_error_state("Failed to initialize libCURL with username. "
330 "Error code=%d (%s)", curl_rc, curl_err_string);
333 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_PASSWORD, password);
334 if (curl_rc != CURLE_OK)
336 rc = EVEL_CURL_LIBRARY_FAIL;
337 log_error_state("Failed to initialize libCURL with password. "
338 "Error code=%d (%s)", curl_rc, curl_err_string);
342 /***************************************************************************/
343 /* Initialize a message ring-buffer to be used between the foreground and */
344 /* the thread which sends the messages. This can't fail. */
345 /***************************************************************************/
346 ring_buffer_initialize(&event_buffer, EVEL_EVENT_BUFFER_DEPTH);
348 /***************************************************************************/
349 /* Initialize the priority post buffer to empty. */
350 /***************************************************************************/
351 priority_post.memory = NULL;
359 /**************************************************************************//**
360 * Run the event handler.
362 * Spawns the thread responsible for handling events and sending them to the
365 * @return Status code.
366 * @retval ::EVEL_SUCCESS if everything OK.
367 * @retval One of ::EVEL_ERR_CODES if there was a problem.
368 *****************************************************************************/
369 EVEL_ERR_CODES event_handler_run()
371 EVEL_ERR_CODES rc = EVEL_SUCCESS;
376 /***************************************************************************/
377 /* Start the event handler thread. */
378 /***************************************************************************/
379 evt_handler_state = EVT_HANDLER_INACTIVE;
380 pthread_rc = pthread_create(&evt_handler_thread, NULL, event_handler, NULL);
383 rc = EVEL_PTHREAD_LIBRARY_FAIL;
384 log_error_state("Failed to start event handler thread. "
385 "Error code=%d", pthread_rc);
392 /**************************************************************************//**
393 * Terminate the event handler.
395 * Shuts down the event handler thread in as clean a way as possible. Sets the
396 * global exit flag and then signals the thread to interrupt it since it's
397 * most likely waiting on the ring-buffer.
399 * Having achieved an orderly shutdown of the event handler thread, clean up
400 * the cURL library's resources cleanly.
402 * @return Status code.
403 * @retval ::EVEL_SUCCESS if everything OK.
404 * @retval One of ::EVEL_ERR_CODES if there was a problem.
405 *****************************************************************************/
406 EVEL_ERR_CODES event_handler_terminate()
408 EVEL_ERR_CODES rc = EVEL_SUCCESS;
411 EVENT_INTERNAL *event = NULL;
413 /***************************************************************************/
414 /* Make sure that we were initialized before trying to terminate the */
415 /* event handler thread. */
416 /***************************************************************************/
417 if (evt_handler_state != EVT_HANDLER_UNINITIALIZED)
419 /*************************************************************************/
420 /* Make sure that the event handler knows it's time to die. */
421 /*************************************************************************/
422 event = evel_new_internal_event(EVT_CMD_TERMINATE,"EVELinternal","EVELid");
425 /***********************************************************************/
426 /* We failed to get an event, but we don't bail out - we will just */
427 /* clean up what we can and continue on our way, since we're exiting */
429 /***********************************************************************/
430 EVEL_ERROR("Failed to get internal event - perform dirty exit instead!");
434 /***********************************************************************/
435 /* Post the event then wait for the Event Handler to exit. Set the */
436 /* global command, too, in case the ring-buffer is full. */
437 /***********************************************************************/
438 EVEL_DEBUG("Sending event to Event Hander to request it to exit.");
439 evt_handler_state = EVT_HANDLER_REQUEST_TERMINATE;
440 evel_post_event((EVENT_HEADER *) event);
441 pthread_join(evt_handler_thread, NULL);
442 EVEL_DEBUG("Event Handler thread has exited.");
447 EVEL_DEBUG("Event handler was not initialized, so no need to kill it");
450 /***************************************************************************/
451 /* Clean-up the cURL library. */
452 /***************************************************************************/
453 if (curl_handle != NULL)
455 curl_easy_cleanup(curl_handle);
458 if (hdr_chunk != NULL)
460 curl_slist_free_all(hdr_chunk);
464 /***************************************************************************/
465 /* Free off the stored API URL strings. */
466 /***************************************************************************/
467 if (evel_event_api_url != NULL)
469 free(evel_event_api_url);
470 evel_event_api_url = NULL;
472 if (evel_throt_api_url != NULL)
474 free(evel_throt_api_url);
475 evel_throt_api_url = NULL;
482 /**************************************************************************//**
485 * @note So far as the caller is concerned, successfully posting the event
486 * relinquishes all responsibility for the event - the library will take care
487 * of freeing the event in due course.
489 * @param event The event to be posted.
491 * @returns Status code
492 * @retval EVEL_SUCCESS On success
493 * @retval "One of ::EVEL_ERR_CODES" On failure.
494 *****************************************************************************/
495 EVEL_ERR_CODES evel_post_event(EVENT_HEADER * event)
497 int rc = EVEL_SUCCESS;
501 /***************************************************************************/
502 /* Check preconditions. */
503 /***************************************************************************/
504 assert(event != NULL);
506 /***************************************************************************/
507 /* We need to make sure that we are either initializing or running */
508 /* normally before writing the event into the buffer so that we can */
509 /* guarantee that the ring-buffer empties properly on exit. */
510 /***************************************************************************/
511 if ((evt_handler_state == EVT_HANDLER_ACTIVE) ||
512 (evt_handler_state == EVT_HANDLER_INACTIVE) ||
513 (evt_handler_state == EVT_HANDLER_REQUEST_TERMINATE))
515 if (ring_buffer_write(&event_buffer, event) == 0)
517 log_error_state("Failed to write event to buffer - event dropped!");
518 rc = EVEL_EVENT_BUFFER_FULL;
519 evel_free_event(event);
524 /*************************************************************************/
525 /* System is not in active operation, so reject the event. */
526 /*************************************************************************/
527 log_error_state("Event Handler system not active - event dropped!");
528 rc = EVEL_EVENT_HANDLER_INACTIVE;
529 evel_free_event(event);
536 /**************************************************************************//**
537 * Post an event to the Vendor Event Listener API.
539 * @returns Status code
540 * @retval EVEL_SUCCESS On success
541 * @retval "One of ::EVEL_ERR_CODES" On failure.
542 *****************************************************************************/
543 static EVEL_ERR_CODES evel_post_api(char * msg, size_t size)
545 int rc = EVEL_SUCCESS;
546 CURLcode curl_rc = CURLE_OK;
547 MEMORY_CHUNK rx_chunk;
548 MEMORY_CHUNK tx_chunk;
549 int http_response_code = 0;
553 /***************************************************************************/
554 /* Create the memory chunk to be used for the response to the post. The */
555 /* will be realloced. */
556 /***************************************************************************/
557 rx_chunk.memory = malloc(1);
558 assert(rx_chunk.memory != NULL);
561 /***************************************************************************/
562 /* Create the memory chunk to be sent as the body of the post. */
563 /***************************************************************************/
564 tx_chunk.memory = msg;
565 tx_chunk.size = size;
566 EVEL_DEBUG("Sending chunk of size %d", tx_chunk.size);
568 /***************************************************************************/
569 /* Point to the data to be received. */
570 /***************************************************************************/
571 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &rx_chunk);
572 if (curl_rc != CURLE_OK)
574 rc = EVEL_CURL_LIBRARY_FAIL;
575 log_error_state("Failed to initialize libCURL to upload. "
576 "Error code=%d (%s)", curl_rc, curl_err_string);
579 EVEL_DEBUG("Initialized data to receive");
581 /***************************************************************************/
582 /* Pointer to pass to our read function */
583 /***************************************************************************/
584 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READDATA, &tx_chunk);
585 if (curl_rc != CURLE_OK)
587 rc = EVEL_CURL_LIBRARY_FAIL;
588 log_error_state("Failed to set upload data for libCURL to upload. "
589 "Error code=%d (%s)", curl_rc, curl_err_string);
592 EVEL_DEBUG("Initialized data to send");
594 /***************************************************************************/
595 /* Size of the data to transmit. */
596 /***************************************************************************/
597 curl_rc = curl_easy_setopt(curl_handle,
598 CURLOPT_POSTFIELDSIZE,
600 if (curl_rc != CURLE_OK)
602 rc = EVEL_CURL_LIBRARY_FAIL;
603 log_error_state("Failed to set length of upload data for libCURL to "
604 "upload. Error code=%d (%s)", curl_rc, curl_err_string);
607 EVEL_DEBUG("Initialized length of data to send");
609 /***************************************************************************/
610 /* Now run off and do what you've been told! */
611 /***************************************************************************/
612 curl_rc = curl_easy_perform(curl_handle);
613 if (curl_rc != CURLE_OK)
615 rc = EVEL_CURL_LIBRARY_FAIL;
616 log_error_state("Failed to transfer an event to Vendor Event Listener! "
617 "Error code=%d (%s)", curl_rc, curl_err_string);
618 EVEL_ERROR("Dropped event: %s", msg);
622 /***************************************************************************/
623 /* See what response we got - any 2XX response is good. */
624 /***************************************************************************/
625 curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code);
626 EVEL_DEBUG("HTTP response code: %d", http_response_code);
627 if ((http_response_code / 100) == 2)
629 /*************************************************************************/
630 /* If the server responded with data it may be interesting but not a */
632 /*************************************************************************/
633 if ((rx_chunk.size > 0) && (rx_chunk.memory != NULL))
635 EVEL_DEBUG("Server returned data = %d (%s)",
639 /***********************************************************************/
640 /* If this is a response to priority post, then we're not interested. */
641 /***********************************************************************/
642 if (priority_post.memory != NULL)
644 EVEL_ERROR("Ignoring priority post response");
648 evel_handle_event_response(&rx_chunk, &priority_post);
654 EVEL_ERROR("Unexpected HTTP response code: %d with data size %d (%s)",
657 rx_chunk.size > 0 ? rx_chunk.memory : "NONE");
658 EVEL_ERROR("Potentially dropped event: %s", msg);
662 free(rx_chunk.memory);
667 /**************************************************************************//**
668 * Callback function to provide data to send.
670 * Copy data into the supplied buffer, read_callback::ptr, checking size
673 * @returns Number of bytes placed into read_callback::ptr. 0 for EOF.
674 *****************************************************************************/
675 static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
678 size_t bytes_to_write = 0;
679 MEMORY_CHUNK *tx_chunk = (MEMORY_CHUNK *)userp;
683 bytes_to_write = min(size*nmemb, tx_chunk->size);
685 if (bytes_to_write > 0)
687 EVEL_DEBUG("Going to try to write %d bytes", bytes_to_write);
688 strncpy((char *)ptr, tx_chunk->memory, bytes_to_write);
689 tx_chunk->memory += bytes_to_write;
690 tx_chunk->size -= bytes_to_write;
691 rtn = bytes_to_write;
695 EVEL_DEBUG("Reached EOF");
702 /**************************************************************************//**
703 * Callback function to provide returned data.
705 * Copy data into the supplied buffer, write_callback::ptr, checking size
708 * @returns Number of bytes placed into write_callback::ptr. 0 for EOF.
709 *****************************************************************************/
710 size_t evel_write_callback(void *contents,
715 size_t realsize = size * nmemb;
716 MEMORY_CHUNK * rx_chunk = (MEMORY_CHUNK *)userp;
720 EVEL_DEBUG("Called with %d chunks of %d size = %d", nmemb, size, realsize);
721 EVEL_DEBUG("rx chunk size is %d", rx_chunk->size);
723 rx_chunk->memory = realloc(rx_chunk->memory, rx_chunk->size + realsize + 1);
724 if(rx_chunk->memory == NULL) {
726 printf("not enough memory (realloc returned NULL)\n");
730 memcpy(&(rx_chunk->memory[rx_chunk->size]), contents, realsize);
731 rx_chunk->size += realsize;
732 rx_chunk->memory[rx_chunk->size] = 0;
734 EVEL_DEBUG("Rx data: %s", rx_chunk->memory);
735 EVEL_DEBUG("Returning: %d", realsize);
741 /**************************************************************************//**
744 * Watch for messages coming on the internal queue and send them to the
747 * param[in] arg Argument - unused.
748 *****************************************************************************/
749 static void * event_handler(void * arg __attribute__ ((unused)))
752 EVENT_HEADER * msg = NULL;
753 EVENT_INTERNAL * internal_msg = NULL;
755 char json_body[EVEL_MAX_JSON_BODY];
756 int rc = EVEL_SUCCESS;
759 EVEL_INFO("Event handler thread started");
761 /***************************************************************************/
762 /* Set this thread to be cancellable immediately. */
763 /***************************************************************************/
764 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type);
766 /***************************************************************************/
767 /* Set the handler as active, defending against weird situations like */
768 /* immediately shutting down after initializing the library so the */
769 /* handler never gets started up properly. */
770 /***************************************************************************/
771 if (evt_handler_state == EVT_HANDLER_INACTIVE)
773 evt_handler_state = EVT_HANDLER_ACTIVE;
777 EVEL_ERROR("Event Handler State was not INACTIVE at start-up - "
778 "Handler will exit immediately!");
781 while (evt_handler_state == EVT_HANDLER_ACTIVE)
783 /*************************************************************************/
784 /* Wait for a message to be received. */
785 /*************************************************************************/
786 EVEL_DEBUG("Event handler getting any messages");
787 msg = ring_buffer_read(&event_buffer);
789 /*************************************************************************/
790 /* Internal events get special treatment while regular events get posted */
791 /* to the far side. */
792 /*************************************************************************/
793 if (msg->event_domain != EVEL_DOMAIN_INTERNAL)
795 EVEL_DEBUG("External event received");
797 /***********************************************************************/
798 /* Encode the event in JSON. */
799 /***********************************************************************/
800 json_size = evel_json_encode_event(json_body, EVEL_MAX_JSON_BODY, msg);
802 /***********************************************************************/
803 /* Send the JSON across the API. */
804 /***********************************************************************/
805 EVEL_DEBUG("Sending JSON of size %d is: %s", json_size, json_body);
806 rc = evel_post_api(json_body, json_size);
807 if (rc != EVEL_SUCCESS)
809 EVEL_ERROR("Failed to transfer the data. Error code=%d", rc);
814 EVEL_DEBUG("Internal event received");
815 internal_msg = (EVENT_INTERNAL *) msg;
816 assert(internal_msg->command == EVT_CMD_TERMINATE);
817 evt_handler_state = EVT_HANDLER_TERMINATING;
820 /*************************************************************************/
821 /* We are responsible for freeing the memory. */
822 /*************************************************************************/
823 evel_free_event(msg);
826 /*************************************************************************/
827 /* There may be a single priority post to be sent. */
828 /*************************************************************************/
829 if (priority_post.memory != NULL)
831 EVEL_DEBUG("Priority Post");
833 /***********************************************************************/
834 /* Set the URL for the throttling API. */
835 /***********************************************************************/
836 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_throt_api_url);
837 if (curl_rc != CURLE_OK)
839 /*********************************************************************/
840 /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which */
841 /* case we carry on regardless. */
842 /*********************************************************************/
843 EVEL_ERROR("Failed to set throttling URL. Error code=%d", rc);
847 rc = evel_post_api(priority_post.memory, priority_post.size);
848 if (rc != EVEL_SUCCESS)
850 EVEL_ERROR("Failed to transfer priority post. Error code=%d", rc);
854 /***********************************************************************/
855 /* Reinstate the URL for the event API. */
856 /***********************************************************************/
857 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_event_api_url);
858 if (curl_rc != CURLE_OK)
860 /*********************************************************************/
861 /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which */
862 /* case we carry on regardless. */
863 /*********************************************************************/
864 EVEL_ERROR("Failed to reinstate events URL. Error code=%d", rc);
867 /***********************************************************************/
868 /* We are responsible for freeing the memory. */
869 /***********************************************************************/
870 free(priority_post.memory);
871 priority_post.memory = NULL;
875 /***************************************************************************/
876 /* The event handler is now exiting. The ring-buffer could contain events */
877 /* which have not been processed, so deplete those. Because we've been */
878 /* asked to exit we can be confident that the foreground will have stopped */
879 /* sending events in so we know that this process will conclude! */
880 /***************************************************************************/
881 evt_handler_state = EVT_HANDLER_TERMINATING;
882 while (!ring_buffer_is_empty(&event_buffer))
884 EVEL_DEBUG("Reading event from buffer");
885 msg = ring_buffer_read(&event_buffer);
886 evel_free_event(msg);
888 evt_handler_state = EVT_HANDLER_TERMINATED;
889 EVEL_INFO("Event handler thread stopped");
894 /**************************************************************************//**
895 * Handle a JSON response from the listener, contained in a ::MEMORY_CHUNK.
897 * Tokenize the response, and decode any tokens found.
899 * @param chunk The memory chunk containing the response.
900 * @param post The memory chunk in which to place any resulting POST.
901 *****************************************************************************/
902 void evel_handle_event_response(const MEMORY_CHUNK * const chunk,
903 MEMORY_CHUNK * const post)
905 jsmn_parser json_parser;
906 jsmntok_t json_tokens[EVEL_MAX_RESPONSE_TOKENS];
911 /***************************************************************************/
912 /* Check preconditions. */
913 /***************************************************************************/
914 assert(chunk != NULL);
915 assert(priority_post.memory == NULL);
917 EVEL_DEBUG("Response size = %d", chunk->size);
918 EVEL_DEBUG("Response = %s", chunk->memory);
920 /***************************************************************************/
921 /* Initialize the parser and tokenize the response. */
922 /***************************************************************************/
923 jsmn_init(&json_parser);
924 num_tokens = jsmn_parse(&json_parser,
928 EVEL_MAX_RESPONSE_TOKENS);
932 EVEL_ERROR("Failed to parse JSON response. "
933 "Error code=%d", num_tokens);
935 else if (num_tokens == 0)
937 EVEL_DEBUG("No tokens found in JSON response");
941 EVEL_DEBUG("Decode JSON response tokens");
942 if (!evel_handle_response_tokens(chunk, json_tokens, num_tokens, post))
944 EVEL_ERROR("Failed to handle JSON response.");
951 /**************************************************************************//**
952 * Handle a JSON response from the listener, as a list of tokens from JSMN.
954 * @param chunk Memory chunk containing the JSON buffer.
955 * @param json_tokens Array of tokens to handle.
956 * @param num_tokens The number of tokens to handle.
957 * @param post The memory chunk in which to place any resulting POST.
958 * @return true if we handled the response, false otherwise.
959 *****************************************************************************/
960 bool evel_handle_response_tokens(const MEMORY_CHUNK * const chunk,
961 const jsmntok_t * const json_tokens,
962 const int num_tokens,
963 MEMORY_CHUNK * const post)
965 bool json_ok = false;
969 /***************************************************************************/
970 /* Check preconditions. */
971 /***************************************************************************/
972 assert(chunk != NULL);
973 assert(json_tokens != NULL);
974 assert(num_tokens < EVEL_MAX_RESPONSE_TOKENS);
976 /***************************************************************************/
977 /* Peek at the tokens to decide what the response it, then call the */
978 /* appropriate handler to handle it. There is only one handler at this */
980 /***************************************************************************/
981 if (evel_tokens_match_command_list(chunk, json_tokens, num_tokens))
983 json_ok = evel_handle_command_list(chunk, json_tokens, num_tokens, post);
991 /**************************************************************************//**
992 * Determine whether a list of tokens looks like a "commandList" response.
994 * @param chunk Memory chunk containing the JSON buffer.
995 * @param json_tokens Token to check.
996 * @param num_tokens The number of tokens to handle.
997 * @return true if the tokens look like a "commandList" match, or false.
998 *****************************************************************************/
999 bool evel_tokens_match_command_list(const MEMORY_CHUNK * const chunk,
1000 const jsmntok_t * const json_tokens,
1001 const int num_tokens)
1003 bool result = false;
1007 /***************************************************************************/
1008 /* Make some checks on the basic layout of the commandList. */
1009 /***************************************************************************/
1010 if ((num_tokens > 3) &&
1011 (json_tokens[0].type == JSMN_OBJECT) &&
1012 (json_tokens[1].type == JSMN_STRING) &&
1013 (json_tokens[2].type == JSMN_ARRAY) &&
1014 (evel_token_equals_string(chunk, &json_tokens[1], "commandList")))
1024 /**************************************************************************//**
1025 * Check that a string token matches a given input string.
1027 * @param chunk Memory chunk containing the JSON buffer.
1028 * @param json_token Token to check.
1029 * @param check_string String to check it against.
1030 * @return true if the strings match, or false.
1031 *****************************************************************************/
1032 bool evel_token_equals_string(const MEMORY_CHUNK * const chunk,
1033 const jsmntok_t * json_token,
1034 const char * check_string)
1036 bool result = false;
1040 const int token_length = json_token->end - json_token->start;
1041 const char * const token_string = chunk->memory + json_token->start;
1043 if (token_length == (int)strlen(check_string))
1045 result = (strncmp(token_string, check_string, token_length) == 0);