1 /*************************************************************************//**
3 * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 ****************************************************************************/
18 /**************************************************************************//**
22 * Simple event manager that is responsible for taking events (Heartbeats,
23 * Faults and Measurements) from the ring-buffer and posting them to the API.
25 ****************************************************************************/
32 #include <curl/curl.h>
35 #include "evel_internal.h"
36 #include "ring_buffer.h"
37 #include "evel_throttle.h"
39 /**************************************************************************//**
40 * How long we're prepared to wait for the API service to respond in
42 *****************************************************************************/
43 static const int EVEL_API_TIMEOUT = 5;
45 /*****************************************************************************/
46 /* Prototypes of locally scoped functions. */
47 /*****************************************************************************/
48 static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp);
49 static void * event_handler(void *arg);
50 static bool evel_handle_response_tokens(const MEMORY_CHUNK * const chunk,
51 const jsmntok_t * const json_tokens,
53 MEMORY_CHUNK * const post);
54 static bool evel_tokens_match_command_list(const MEMORY_CHUNK * const chunk,
55 const jsmntok_t * const json_token,
56 const int num_tokens);
57 static bool evel_token_equals_string(const MEMORY_CHUNK * const chunk,
58 const jsmntok_t * const json_token,
59 const char * check_string);
61 /**************************************************************************//**
62 * Buffers for error strings from libcurl.
63 *****************************************************************************/
64 static char curl_err_string[CURL_ERROR_SIZE] = "<NULL>";
66 /**************************************************************************//**
67 * Handle for the API into libcurl.
68 *****************************************************************************/
69 static CURL * curl_handle = NULL;
71 /**************************************************************************//**
72 * Special headers that we send.
73 *****************************************************************************/
74 static struct curl_slist * hdr_chunk = NULL;
76 /**************************************************************************//**
77 * Message queue for sending events to the API.
78 *****************************************************************************/
79 static ring_buffer event_buffer;
81 /**************************************************************************//**
82 * Single pending priority post, which can be generated as a result of a
83 * response to an event. Currently only used to respond to a commandList.
84 *****************************************************************************/
85 static MEMORY_CHUNK priority_post;
87 /**************************************************************************//**
88 * The thread which is responsible for handling events off of the ring-buffer
89 * and posting them to the Event Handler API.
90 *****************************************************************************/
91 static pthread_t evt_handler_thread;
93 /**************************************************************************//**
94 * Variable to convey to the event handler thread what the foreground wants it
96 *****************************************************************************/
97 static EVT_HANDLER_STATE evt_handler_state = EVT_HANDLER_UNINITIALIZED;
99 /**************************************************************************//**
100 * The configured API URL for event and throttling.
101 *****************************************************************************/
102 static char * evel_event_api_url;
103 static char * evel_throt_api_url;
105 /**************************************************************************//**
106 * Initialize the event handler.
108 * Primarily responsible for getting CURL ready for use.
110 * @param[in] event_api_url
111 * The URL where the Vendor Event Listener API is expected
113 * @param[in] throt_api_url
114 * The URL where the Throttling API is expected to be.
115 * @param[in] username The username for the Basic Authentication of requests.
116 * @param[in] password The password for the Basic Authentication of requests.
117 * @param verbosity 0 for normal operation, positive values for chattier
119 *****************************************************************************/
120 EVEL_ERR_CODES event_handler_initialize(const char * const event_api_url,
121 const char * const throt_api_url,
122 const char * const username,
123 const char * const password,
126 int rc = EVEL_SUCCESS;
127 CURLcode curl_rc = CURLE_OK;
131 /***************************************************************************/
132 /* Check assumptions. */
133 /***************************************************************************/
134 assert(event_api_url != NULL);
135 assert(throt_api_url != NULL);
136 assert(username != NULL);
137 assert(password != NULL);
139 /***************************************************************************/
140 /* Store the API URLs. */
141 /***************************************************************************/
142 evel_event_api_url = strdup(event_api_url);
143 assert(evel_event_api_url != NULL);
144 evel_throt_api_url = strdup(throt_api_url);
145 assert(evel_throt_api_url != NULL);
148 curl_version_info_data *d = curl_version_info(CURLVERSION_NOW);
149 /* compare with the 24 bit hex number in 8 bit fields */
150 if(d->version_num >= 0x072100) {
151 /* this is libcurl 7.33.0 or later */
152 EVEL_INFO("7.33 or later Curl version %x.",d->version_num);
155 EVEL_INFO("Old Curl version.");
157 /***************************************************************************/
158 /* Start the CURL library. Note that this initialization is not threadsafe */
159 /* which imposes a constraint that the EVEL library is initialized before */
160 /* any threads are started. */
161 /***************************************************************************/
162 curl_rc = curl_global_init(CURL_GLOBAL_SSL);
163 if (curl_rc != CURLE_OK)
165 rc = EVEL_CURL_LIBRARY_FAIL;
166 log_error_state("Failed to initialize libCURL. Error code=%d", curl_rc);
170 /***************************************************************************/
171 /* Get a curl handle which we'll use for all of our output. */
172 /***************************************************************************/
173 curl_handle = curl_easy_init();
174 if (curl_handle == NULL)
176 rc = EVEL_CURL_LIBRARY_FAIL;
177 log_error_state("Failed to get libCURL handle");
181 /***************************************************************************/
182 /* Prime the library to give friendly error codes. */
183 /***************************************************************************/
184 curl_rc = curl_easy_setopt(curl_handle,
187 if (curl_rc != CURLE_OK)
189 rc = EVEL_CURL_LIBRARY_FAIL;
190 log_error_state("Failed to initialize libCURL to provide friendly errors. "
191 "Error code=%d", curl_rc);
195 /***************************************************************************/
196 /* If running in verbose mode generate more output. */
197 /***************************************************************************/
200 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
201 if (curl_rc != CURLE_OK)
203 rc = EVEL_CURL_LIBRARY_FAIL;
204 log_error_state("Failed to initialize libCURL to be verbose. "
205 "Error code=%d", curl_rc);
210 /***************************************************************************/
211 /* Set the URL for the API. */
212 /***************************************************************************/
213 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, event_api_url);
214 if (curl_rc != CURLE_OK)
216 rc = EVEL_CURL_LIBRARY_FAIL;
217 log_error_state("Failed to initialize libCURL with the API URL. "
218 "Error code=%d (%s)", curl_rc, curl_err_string);
221 EVEL_INFO("Initializing CURL to send events to: %s", event_api_url);
223 /***************************************************************************/
224 /* send all data to this function. */
225 /***************************************************************************/
226 curl_rc = curl_easy_setopt(curl_handle,
227 CURLOPT_WRITEFUNCTION,
228 evel_write_callback);
229 if (curl_rc != CURLE_OK)
231 rc = EVEL_CURL_LIBRARY_FAIL;
232 log_error_state("Failed to initialize libCURL with the write callback. "
233 "Error code=%d (%s)", curl_rc, curl_err_string);
237 /***************************************************************************/
238 /* some servers don't like requests that are made without a user-agent */
239 /* field, so we provide one. */
240 /***************************************************************************/
241 curl_rc = curl_easy_setopt(curl_handle,
243 "libcurl-agent/1.0");
244 if (curl_rc != CURLE_OK)
246 rc = EVEL_CURL_LIBRARY_FAIL;
247 log_error_state("Failed to initialize libCURL to upload. "
248 "Error code=%d (%s)", curl_rc, curl_err_string);
252 /***************************************************************************/
253 /* Specify that we are going to POST data. */
254 /***************************************************************************/
255 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
256 if (curl_rc != CURLE_OK)
258 rc = EVEL_CURL_LIBRARY_FAIL;
259 log_error_state("Failed to initialize libCURL to upload. "
260 "Error code=%d (%s)", curl_rc, curl_err_string);
264 /***************************************************************************/
265 /* we want to use our own read function. */
266 /***************************************************************************/
267 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, read_callback);
268 if (curl_rc != CURLE_OK)
270 rc = EVEL_CURL_LIBRARY_FAIL;
271 log_error_state("Failed to initialize libCURL to upload using read "
272 "function. Error code=%d (%s)", curl_rc, curl_err_string);
276 /***************************************************************************/
277 /* All of our events are JSON encoded. We also suppress the */
278 /* Expect: 100-continue header that we would otherwise get since it */
279 /* confuses some servers. */
281 /* @TODO: do AT&T want this behavior? */
282 /***************************************************************************/
283 hdr_chunk = curl_slist_append(hdr_chunk, "Content-type: application/json");
284 hdr_chunk = curl_slist_append(hdr_chunk, "Expect:");
286 /***************************************************************************/
287 /* set our custom set of headers. */
288 /***************************************************************************/
289 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, hdr_chunk);
290 if (curl_rc != CURLE_OK)
292 rc = EVEL_CURL_LIBRARY_FAIL;
293 log_error_state("Failed to initialize libCURL to use custom headers. "
294 "Error code=%d (%s)", curl_rc, curl_err_string);
298 /***************************************************************************/
299 /* Set the timeout for the operation. */
300 /***************************************************************************/
301 curl_rc = curl_easy_setopt(curl_handle,
304 if (curl_rc != CURLE_OK)
306 rc = EVEL_CURL_LIBRARY_FAIL;
307 log_error_state("Failed to initialize libCURL for API timeout. "
308 "Error code=%d (%s)", curl_rc, curl_err_string);
312 /***************************************************************************/
313 /* Set that we want Basic authentication with username:password Base-64 */
314 /* encoded for the operation. */
315 /***************************************************************************/
316 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
317 if (curl_rc != CURLE_OK)
319 rc = EVEL_CURL_LIBRARY_FAIL;
320 log_error_state("Failed to initialize libCURL for Basic Authentication. "
321 "Error code=%d (%s)", curl_rc, curl_err_string);
324 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_USERNAME, username);
325 if (curl_rc != CURLE_OK)
327 rc = EVEL_CURL_LIBRARY_FAIL;
328 log_error_state("Failed to initialize libCURL with username. "
329 "Error code=%d (%s)", curl_rc, curl_err_string);
332 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_PASSWORD, password);
333 if (curl_rc != CURLE_OK)
335 rc = EVEL_CURL_LIBRARY_FAIL;
336 log_error_state("Failed to initialize libCURL with password. "
337 "Error code=%d (%s)", curl_rc, curl_err_string);
341 /***************************************************************************/
342 /* Initialize a message ring-buffer to be used between the foreground and */
343 /* the thread which sends the messages. This can't fail. */
344 /***************************************************************************/
345 ring_buffer_initialize(&event_buffer, EVEL_EVENT_BUFFER_DEPTH);
347 /***************************************************************************/
348 /* Initialize the priority post buffer to empty. */
349 /***************************************************************************/
350 priority_post.memory = NULL;
358 /**************************************************************************//**
359 * Run the event handler.
361 * Spawns the thread responsible for handling events and sending them to the
364 * @return Status code.
365 * @retval ::EVEL_SUCCESS if everything OK.
366 * @retval One of ::EVEL_ERR_CODES if there was a problem.
367 *****************************************************************************/
368 EVEL_ERR_CODES event_handler_run()
370 EVEL_ERR_CODES rc = EVEL_SUCCESS;
375 /***************************************************************************/
376 /* Start the event handler thread. */
377 /***************************************************************************/
378 evt_handler_state = EVT_HANDLER_INACTIVE;
379 pthread_rc = pthread_create(&evt_handler_thread, NULL, event_handler, NULL);
382 rc = EVEL_PTHREAD_LIBRARY_FAIL;
383 log_error_state("Failed to start event handler thread. "
384 "Error code=%d", pthread_rc);
391 /**************************************************************************//**
392 * Terminate the event handler.
394 * Shuts down the event handler thread in as clean a way as possible. Sets the
395 * global exit flag and then signals the thread to interrupt it since it's
396 * most likely waiting on the ring-buffer.
398 * Having achieved an orderly shutdown of the event handler thread, clean up
399 * the cURL library's resources cleanly.
401 * @return Status code.
402 * @retval ::EVEL_SUCCESS if everything OK.
403 * @retval One of ::EVEL_ERR_CODES if there was a problem.
404 *****************************************************************************/
405 EVEL_ERR_CODES event_handler_terminate()
407 EVEL_ERR_CODES rc = EVEL_SUCCESS;
410 EVENT_INTERNAL *event = NULL;
412 /***************************************************************************/
413 /* Make sure that we were initialized before trying to terminate the */
414 /* event handler thread. */
415 /***************************************************************************/
416 if (evt_handler_state != EVT_HANDLER_UNINITIALIZED)
418 /*************************************************************************/
419 /* Make sure that the event handler knows it's time to die. */
420 /*************************************************************************/
421 event = evel_new_internal_event(EVT_CMD_TERMINATE,"EVELinternal","EVELid");
424 /***********************************************************************/
425 /* We failed to get an event, but we don't bail out - we will just */
426 /* clean up what we can and continue on our way, since we're exiting */
428 /***********************************************************************/
429 EVEL_ERROR("Failed to get internal event - perform dirty exit instead!");
433 /***********************************************************************/
434 /* Post the event then wait for the Event Handler to exit. Set the */
435 /* global command, too, in case the ring-buffer is full. */
436 /***********************************************************************/
437 EVEL_DEBUG("Sending event to Event Hander to request it to exit.");
438 evt_handler_state = EVT_HANDLER_REQUEST_TERMINATE;
439 evel_post_event((EVENT_HEADER *) event);
440 pthread_join(evt_handler_thread, NULL);
441 EVEL_DEBUG("Event Handler thread has exited.");
446 EVEL_DEBUG("Event handler was not initialized, so no need to kill it");
449 /***************************************************************************/
450 /* Clean-up the cURL library. */
451 /***************************************************************************/
452 if (curl_handle != NULL)
454 curl_easy_cleanup(curl_handle);
457 if (hdr_chunk != NULL)
459 curl_slist_free_all(hdr_chunk);
463 /***************************************************************************/
464 /* Free off the stored API URL strings. */
465 /***************************************************************************/
466 if (evel_event_api_url != NULL)
468 free(evel_event_api_url);
469 evel_event_api_url = NULL;
471 if (evel_throt_api_url != NULL)
473 free(evel_throt_api_url);
474 evel_throt_api_url = NULL;
481 /**************************************************************************//**
484 * @note So far as the caller is concerned, successfully posting the event
485 * relinquishes all responsibility for the event - the library will take care
486 * of freeing the event in due course.
488 * @param event The event to be posted.
490 * @returns Status code
491 * @retval EVEL_SUCCESS On success
492 * @retval "One of ::EVEL_ERR_CODES" On failure.
493 *****************************************************************************/
494 EVEL_ERR_CODES evel_post_event(EVENT_HEADER * event)
496 int rc = EVEL_SUCCESS;
500 /***************************************************************************/
501 /* Check preconditions. */
502 /***************************************************************************/
503 assert(event != NULL);
505 /***************************************************************************/
506 /* We need to make sure that we are either initializing or running */
507 /* normally before writing the event into the buffer so that we can */
508 /* guarantee that the ring-buffer empties properly on exit. */
509 /***************************************************************************/
510 if ((evt_handler_state == EVT_HANDLER_ACTIVE) ||
511 (evt_handler_state == EVT_HANDLER_INACTIVE) ||
512 (evt_handler_state == EVT_HANDLER_REQUEST_TERMINATE))
514 if (ring_buffer_write(&event_buffer, event) == 0)
516 log_error_state("Failed to write event to buffer - event dropped!");
517 rc = EVEL_EVENT_BUFFER_FULL;
518 evel_free_event(event);
523 /*************************************************************************/
524 /* System is not in active operation, so reject the event. */
525 /*************************************************************************/
526 log_error_state("Event Handler system not active - event dropped!");
527 rc = EVEL_EVENT_HANDLER_INACTIVE;
528 evel_free_event(event);
535 /**************************************************************************//**
536 * Post an event to the Vendor Event Listener API.
538 * @returns Status code
539 * @retval EVEL_SUCCESS On success
540 * @retval "One of ::EVEL_ERR_CODES" On failure.
541 *****************************************************************************/
542 static EVEL_ERR_CODES evel_post_api(char * msg, size_t size)
544 int rc = EVEL_SUCCESS;
545 CURLcode curl_rc = CURLE_OK;
546 MEMORY_CHUNK rx_chunk;
547 MEMORY_CHUNK tx_chunk;
548 int http_response_code = 0;
552 /***************************************************************************/
553 /* Create the memory chunk to be used for the response to the post. The */
554 /* will be realloced. */
555 /***************************************************************************/
556 rx_chunk.memory = malloc(1);
557 assert(rx_chunk.memory != NULL);
560 /***************************************************************************/
561 /* Create the memory chunk to be sent as the body of the post. */
562 /***************************************************************************/
563 tx_chunk.memory = msg;
564 tx_chunk.size = size;
565 EVEL_DEBUG("Sending chunk of size %d", tx_chunk.size);
567 /***************************************************************************/
568 /* Point to the data to be received. */
569 /***************************************************************************/
570 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &rx_chunk);
571 if (curl_rc != CURLE_OK)
573 rc = EVEL_CURL_LIBRARY_FAIL;
574 log_error_state("Failed to initialize libCURL to upload. "
575 "Error code=%d (%s)", curl_rc, curl_err_string);
578 EVEL_DEBUG("Initialized data to receive");
580 /***************************************************************************/
581 /* Pointer to pass to our read function */
582 /***************************************************************************/
583 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READDATA, &tx_chunk);
584 if (curl_rc != CURLE_OK)
586 rc = EVEL_CURL_LIBRARY_FAIL;
587 log_error_state("Failed to set upload data for libCURL to upload. "
588 "Error code=%d (%s)", curl_rc, curl_err_string);
591 EVEL_DEBUG("Initialized data to send");
593 /***************************************************************************/
594 /* Size of the data to transmit. */
595 /***************************************************************************/
596 curl_rc = curl_easy_setopt(curl_handle,
597 CURLOPT_POSTFIELDSIZE,
599 if (curl_rc != CURLE_OK)
601 rc = EVEL_CURL_LIBRARY_FAIL;
602 log_error_state("Failed to set length of upload data for libCURL to "
603 "upload. Error code=%d (%s)", curl_rc, curl_err_string);
606 EVEL_DEBUG("Initialized length of data to send");
608 /***************************************************************************/
609 /* Now run off and do what you've been told! */
610 /***************************************************************************/
611 curl_rc = curl_easy_perform(curl_handle);
612 if (curl_rc != CURLE_OK)
614 rc = EVEL_CURL_LIBRARY_FAIL;
615 log_error_state("Failed to transfer an event to Vendor Event Listener! "
616 "Error code=%d (%s)", curl_rc, curl_err_string);
617 EVEL_ERROR("Dropped event: %s", msg);
621 /***************************************************************************/
622 /* See what response we got - any 2XX response is good. */
623 /***************************************************************************/
624 curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code);
625 EVEL_DEBUG("HTTP response code: %d", http_response_code);
626 if ((http_response_code / 100) == 2)
628 /*************************************************************************/
629 /* If the server responded with data it may be interesting but not a */
631 /*************************************************************************/
632 if ((rx_chunk.size > 0) && (rx_chunk.memory != NULL))
634 EVEL_DEBUG("Server returned data = %d (%s)",
638 /***********************************************************************/
639 /* If this is a response to priority post, then we're not interested. */
640 /***********************************************************************/
641 if (priority_post.memory != NULL)
643 EVEL_ERROR("Ignoring priority post response");
647 evel_handle_event_response(&rx_chunk, &priority_post);
653 EVEL_ERROR("Unexpected HTTP response code: %d with data size %d (%s)",
656 rx_chunk.size > 0 ? rx_chunk.memory : "NONE");
657 EVEL_ERROR("Potentially dropped event: %s", msg);
661 free(rx_chunk.memory);
666 /**************************************************************************//**
667 * Callback function to provide data to send.
669 * Copy data into the supplied buffer, read_callback::ptr, checking size
672 * @returns Number of bytes placed into read_callback::ptr. 0 for EOF.
673 *****************************************************************************/
674 static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
677 size_t bytes_to_write = 0;
678 MEMORY_CHUNK *tx_chunk = (MEMORY_CHUNK *)userp;
682 bytes_to_write = min(size*nmemb, tx_chunk->size);
684 if (bytes_to_write > 0)
686 EVEL_DEBUG("Going to try to write %d bytes", bytes_to_write);
687 strncpy((char *)ptr, tx_chunk->memory, bytes_to_write);
688 tx_chunk->memory += bytes_to_write;
689 tx_chunk->size -= bytes_to_write;
690 rtn = bytes_to_write;
694 EVEL_DEBUG("Reached EOF");
701 /**************************************************************************//**
702 * Callback function to provide returned data.
704 * Copy data into the supplied buffer, write_callback::ptr, checking size
707 * @returns Number of bytes placed into write_callback::ptr. 0 for EOF.
708 *****************************************************************************/
709 size_t evel_write_callback(void *contents,
714 size_t realsize = size * nmemb;
715 MEMORY_CHUNK * rx_chunk = (MEMORY_CHUNK *)userp;
719 EVEL_DEBUG("Called with %d chunks of %d size = %d", nmemb, size, realsize);
720 EVEL_DEBUG("rx chunk size is %d", rx_chunk->size);
722 rx_chunk->memory = realloc(rx_chunk->memory, rx_chunk->size + realsize + 1);
723 if(rx_chunk->memory == NULL) {
725 printf("not enough memory (realloc returned NULL)\n");
729 memcpy(&(rx_chunk->memory[rx_chunk->size]), contents, realsize);
730 rx_chunk->size += realsize;
731 rx_chunk->memory[rx_chunk->size] = 0;
733 EVEL_DEBUG("Rx data: %s", rx_chunk->memory);
734 EVEL_DEBUG("Returning: %d", realsize);
740 /**************************************************************************//**
743 * Watch for messages coming on the internal queue and send them to the
746 * param[in] arg Argument - unused.
747 *****************************************************************************/
748 static void * event_handler(void * arg __attribute__ ((unused)))
751 EVENT_HEADER * msg = NULL;
752 EVENT_INTERNAL * internal_msg = NULL;
754 char json_body[EVEL_MAX_JSON_BODY];
755 int rc = EVEL_SUCCESS;
758 EVEL_INFO("Event handler thread started");
760 /***************************************************************************/
761 /* Set this thread to be cancellable immediately. */
762 /***************************************************************************/
763 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type);
765 /***************************************************************************/
766 /* Set the handler as active, defending against weird situations like */
767 /* immediately shutting down after initializing the library so the */
768 /* handler never gets started up properly. */
769 /***************************************************************************/
770 if (evt_handler_state == EVT_HANDLER_INACTIVE)
772 evt_handler_state = EVT_HANDLER_ACTIVE;
776 EVEL_ERROR("Event Handler State was not INACTIVE at start-up - "
777 "Handler will exit immediately!");
780 while (evt_handler_state == EVT_HANDLER_ACTIVE)
782 /*************************************************************************/
783 /* Wait for a message to be received. */
784 /*************************************************************************/
785 EVEL_DEBUG("Event handler getting any messages");
786 msg = ring_buffer_read(&event_buffer);
788 /*************************************************************************/
789 /* Internal events get special treatment while regular events get posted */
790 /* to the far side. */
791 /*************************************************************************/
792 if (msg->event_domain != EVEL_DOMAIN_INTERNAL)
794 EVEL_DEBUG("External event received");
796 /***********************************************************************/
797 /* Encode the event in JSON. */
798 /***********************************************************************/
799 json_size = evel_json_encode_event(json_body, EVEL_MAX_JSON_BODY, msg);
801 /***********************************************************************/
802 /* Send the JSON across the API. */
803 /***********************************************************************/
804 EVEL_DEBUG("Sending JSON of size %d is: %s", json_size, json_body);
805 rc = evel_post_api(json_body, json_size);
806 if (rc != EVEL_SUCCESS)
808 EVEL_ERROR("Failed to transfer the data. Error code=%d", rc);
813 EVEL_DEBUG("Internal event received");
814 internal_msg = (EVENT_INTERNAL *) msg;
815 assert(internal_msg->command == EVT_CMD_TERMINATE);
816 evt_handler_state = EVT_HANDLER_TERMINATING;
819 /*************************************************************************/
820 /* We are responsible for freeing the memory. */
821 /*************************************************************************/
822 evel_free_event(msg);
825 /*************************************************************************/
826 /* There may be a single priority post to be sent. */
827 /*************************************************************************/
828 if (priority_post.memory != NULL)
830 EVEL_DEBUG("Priority Post");
832 /***********************************************************************/
833 /* Set the URL for the throttling API. */
834 /***********************************************************************/
835 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_throt_api_url);
836 if (curl_rc != CURLE_OK)
838 /*********************************************************************/
839 /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which */
840 /* case we carry on regardless. */
841 /*********************************************************************/
842 EVEL_ERROR("Failed to set throttling URL. Error code=%d", rc);
846 rc = evel_post_api(priority_post.memory, priority_post.size);
847 if (rc != EVEL_SUCCESS)
849 EVEL_ERROR("Failed to transfer priority post. Error code=%d", rc);
853 /***********************************************************************/
854 /* Reinstate the URL for the event API. */
855 /***********************************************************************/
856 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_event_api_url);
857 if (curl_rc != CURLE_OK)
859 /*********************************************************************/
860 /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which */
861 /* case we carry on regardless. */
862 /*********************************************************************/
863 EVEL_ERROR("Failed to reinstate events URL. Error code=%d", rc);
866 /***********************************************************************/
867 /* We are responsible for freeing the memory. */
868 /***********************************************************************/
869 free(priority_post.memory);
870 priority_post.memory = NULL;
874 /***************************************************************************/
875 /* The event handler is now exiting. The ring-buffer could contain events */
876 /* which have not been processed, so deplete those. Because we've been */
877 /* asked to exit we can be confident that the foreground will have stopped */
878 /* sending events in so we know that this process will conclude! */
879 /***************************************************************************/
880 evt_handler_state = EVT_HANDLER_TERMINATING;
881 while (!ring_buffer_is_empty(&event_buffer))
883 EVEL_DEBUG("Reading event from buffer");
884 msg = ring_buffer_read(&event_buffer);
885 evel_free_event(msg);
887 evt_handler_state = EVT_HANDLER_TERMINATED;
888 EVEL_INFO("Event handler thread stopped");
893 /**************************************************************************//**
894 * Handle a JSON response from the listener, contained in a ::MEMORY_CHUNK.
896 * Tokenize the response, and decode any tokens found.
898 * @param chunk The memory chunk containing the response.
899 * @param post The memory chunk in which to place any resulting POST.
900 *****************************************************************************/
901 void evel_handle_event_response(const MEMORY_CHUNK * const chunk,
902 MEMORY_CHUNK * const post)
904 jsmn_parser json_parser;
905 jsmntok_t json_tokens[EVEL_MAX_RESPONSE_TOKENS];
910 /***************************************************************************/
911 /* Check preconditions. */
912 /***************************************************************************/
913 assert(chunk != NULL);
914 assert(priority_post.memory == NULL);
916 EVEL_DEBUG("Response size = %d", chunk->size);
917 EVEL_DEBUG("Response = %s", chunk->memory);
919 /***************************************************************************/
920 /* Initialize the parser and tokenize the response. */
921 /***************************************************************************/
922 jsmn_init(&json_parser);
923 num_tokens = jsmn_parse(&json_parser,
927 EVEL_MAX_RESPONSE_TOKENS);
931 EVEL_ERROR("Failed to parse JSON response. "
932 "Error code=%d", num_tokens);
934 else if (num_tokens == 0)
936 EVEL_DEBUG("No tokens found in JSON response");
940 EVEL_DEBUG("Decode JSON response tokens");
941 if (!evel_handle_response_tokens(chunk, json_tokens, num_tokens, post))
943 EVEL_ERROR("Failed to handle JSON response.");
950 /**************************************************************************//**
951 * Handle a JSON response from the listener, as a list of tokens from JSMN.
953 * @param chunk Memory chunk containing the JSON buffer.
954 * @param json_tokens Array of tokens to handle.
955 * @param num_tokens The number of tokens to handle.
956 * @param post The memory chunk in which to place any resulting POST.
957 * @return true if we handled the response, false otherwise.
958 *****************************************************************************/
959 bool evel_handle_response_tokens(const MEMORY_CHUNK * const chunk,
960 const jsmntok_t * const json_tokens,
961 const int num_tokens,
962 MEMORY_CHUNK * const post)
964 bool json_ok = false;
968 /***************************************************************************/
969 /* Check preconditions. */
970 /***************************************************************************/
971 assert(chunk != NULL);
972 assert(json_tokens != NULL);
973 assert(num_tokens < EVEL_MAX_RESPONSE_TOKENS);
975 /***************************************************************************/
976 /* Peek at the tokens to decide what the response it, then call the */
977 /* appropriate handler to handle it. There is only one handler at this */
979 /***************************************************************************/
980 if (evel_tokens_match_command_list(chunk, json_tokens, num_tokens))
982 json_ok = evel_handle_command_list(chunk, json_tokens, num_tokens, post);
990 /**************************************************************************//**
991 * Determine whether a list of tokens looks like a "commandList" response.
993 * @param chunk Memory chunk containing the JSON buffer.
994 * @param json_tokens Token to check.
995 * @param num_tokens The number of tokens to handle.
996 * @return true if the tokens look like a "commandList" match, or false.
997 *****************************************************************************/
998 bool evel_tokens_match_command_list(const MEMORY_CHUNK * const chunk,
999 const jsmntok_t * const json_tokens,
1000 const int num_tokens)
1002 bool result = false;
1006 /***************************************************************************/
1007 /* Make some checks on the basic layout of the commandList. */
1008 /***************************************************************************/
1009 if ((num_tokens > 3) &&
1010 (json_tokens[0].type == JSMN_OBJECT) &&
1011 (json_tokens[1].type == JSMN_STRING) &&
1012 (json_tokens[2].type == JSMN_ARRAY) &&
1013 (evel_token_equals_string(chunk, &json_tokens[1], "commandList")))
1023 /**************************************************************************//**
1024 * Check that a string token matches a given input string.
1026 * @param chunk Memory chunk containing the JSON buffer.
1027 * @param json_token Token to check.
1028 * @param check_string String to check it against.
1029 * @return true if the strings match, or false.
1030 *****************************************************************************/
1031 bool evel_token_equals_string(const MEMORY_CHUNK * const chunk,
1032 const jsmntok_t * json_token,
1033 const char * check_string)
1035 bool result = false;
1039 const int token_length = json_token->end - json_token->start;
1040 const char * const token_string = chunk->memory + json_token->start;
1042 if (token_length == (int)strlen(check_string))
1044 result = (strncmp(token_string, check_string, token_length) == 0);