1 /**************************************************************************//**
5 * Simple event manager that is responsible for taking events (Heartbeats,
6 * Faults and Measurements) from the ring-buffer and posting them to the API.
11 * Copyright(c) <2016>, AT&T Intellectual Property. All other rights reserved.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions are met:
16 * 1. Redistributions of source code must retain the above copyright notice,
17 * this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright notice,
19 * this list of conditions and the following disclaimer in the documentation
20 * and/or other materials provided with the distribution.
21 * 3. All advertising materials mentioning features or use of this software
22 * must display the following acknowledgement: This product includes
23 * software developed by the AT&T.
24 * 4. Neither the name of AT&T nor the names of its contributors may be used to
25 * endorse or promote products derived from this software without specific
26 * prior written permission.
28 * THIS SOFTWARE IS PROVIDED BY AT&T INTELLECTUAL PROPERTY ''AS IS'' AND ANY
29 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
30 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
31 * DISCLAIMED. IN NO EVENT SHALL AT&T INTELLECTUAL PROPERTY BE LIABLE FOR ANY
32 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
33 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
34 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
35 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
37 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 *****************************************************************************/
45 #include <curl/curl.h>
48 #include "evel_internal.h"
49 #include "ring_buffer.h"
50 #include "evel_throttle.h"
52 /**************************************************************************//**
53 * How long we're prepared to wait for the API service to respond in
55 *****************************************************************************/
56 static const int EVEL_API_TIMEOUT = 5;
58 /*****************************************************************************/
59 /* Prototypes of locally scoped functions. */
60 /*****************************************************************************/
61 static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp);
62 static void * event_handler(void *arg);
63 static bool evel_handle_response_tokens(const MEMORY_CHUNK * const chunk,
64 const jsmntok_t * const json_tokens,
66 MEMORY_CHUNK * const post);
67 static bool evel_tokens_match_command_list(const MEMORY_CHUNK * const chunk,
68 const jsmntok_t * const json_token,
69 const int num_tokens);
70 static bool evel_token_equals_string(const MEMORY_CHUNK * const chunk,
71 const jsmntok_t * const json_token,
72 const char * check_string);
74 /**************************************************************************//**
75 * Buffers for error strings from libcurl.
76 *****************************************************************************/
77 static char curl_err_string[CURL_ERROR_SIZE] = "<NULL>";
79 /**************************************************************************//**
80 * Handle for the API into libcurl.
81 *****************************************************************************/
82 static CURL * curl_handle = NULL;
84 /**************************************************************************//**
85 * Special headers that we send.
86 *****************************************************************************/
87 static struct curl_slist * hdr_chunk = NULL;
89 /**************************************************************************//**
90 * Message queue for sending events to the API.
91 *****************************************************************************/
92 static ring_buffer event_buffer;
94 /**************************************************************************//**
95 * Single pending priority post, which can be generated as a result of a
96 * response to an event. Currently only used to respond to a commandList.
97 *****************************************************************************/
98 static MEMORY_CHUNK priority_post;
100 /**************************************************************************//**
101 * The thread which is responsible for handling events off of the ring-buffer
102 * and posting them to the Event Handler API.
103 *****************************************************************************/
104 static pthread_t evt_handler_thread;
106 /**************************************************************************//**
107 * Variable to convey to the event handler thread what the foreground wants it
109 *****************************************************************************/
110 static EVT_HANDLER_STATE evt_handler_state = EVT_HANDLER_UNINITIALIZED;
112 /**************************************************************************//**
113 * The configured API URL for event and throttling.
114 *****************************************************************************/
115 static char * evel_event_api_url;
116 static char * evel_throt_api_url;
118 /**************************************************************************//**
119 * Initialize the event handler.
121 * Primarily responsible for getting CURL ready for use.
123 * @param[in] event_api_url
124 * The URL where the Vendor Event Listener API is expected
126 * @param[in] throt_api_url
127 * The URL where the Throttling API is expected to be.
128 * @param[in] username The username for the Basic Authentication of requests.
129 * @param[in] password The password for the Basic Authentication of requests.
130 * @param verbosity 0 for normal operation, positive values for chattier
132 *****************************************************************************/
133 EVEL_ERR_CODES event_handler_initialize(const char * const event_api_url,
134 const char * const throt_api_url,
135 const char * const username,
136 const char * const password,
139 int rc = EVEL_SUCCESS;
140 CURLcode curl_rc = CURLE_OK;
144 /***************************************************************************/
145 /* Check assumptions. */
146 /***************************************************************************/
147 assert(event_api_url != NULL);
148 assert(throt_api_url != NULL);
149 assert(username != NULL);
150 assert(password != NULL);
152 /***************************************************************************/
153 /* Store the API URLs. */
154 /***************************************************************************/
155 evel_event_api_url = strdup(event_api_url);
156 assert(evel_event_api_url != NULL);
157 evel_throt_api_url = strdup(throt_api_url);
158 assert(evel_throt_api_url != NULL);
160 /***************************************************************************/
161 /* Start the CURL library. Note that this initialization is not threadsafe */
162 /* which imposes a constraint that the EVEL library is initialized before */
163 /* any threads are started. */
164 /***************************************************************************/
165 curl_rc = curl_global_init(CURL_GLOBAL_SSL);
166 if (curl_rc != CURLE_OK)
168 rc = EVEL_CURL_LIBRARY_FAIL;
169 log_error_state("Failed to initialize libCURL. Error code=%d", curl_rc);
173 /***************************************************************************/
174 /* Get a curl handle which we'll use for all of our output. */
175 /***************************************************************************/
176 curl_handle = curl_easy_init();
177 if (curl_handle == NULL)
179 rc = EVEL_CURL_LIBRARY_FAIL;
180 log_error_state("Failed to get libCURL handle");
184 /***************************************************************************/
185 /* Prime the library to give friendly error codes. */
186 /***************************************************************************/
187 curl_rc = curl_easy_setopt(curl_handle,
190 if (curl_rc != CURLE_OK)
192 rc = EVEL_CURL_LIBRARY_FAIL;
193 log_error_state("Failed to initialize libCURL to provide friendly errors. "
194 "Error code=%d", curl_rc);
198 /***************************************************************************/
199 /* If running in verbose mode generate more output. */
200 /***************************************************************************/
203 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
204 if (curl_rc != CURLE_OK)
206 rc = EVEL_CURL_LIBRARY_FAIL;
207 log_error_state("Failed to initialize libCURL to be verbose. "
208 "Error code=%d", curl_rc);
213 /***************************************************************************/
214 /* Set the URL for the API. */
215 /***************************************************************************/
216 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, event_api_url);
217 if (curl_rc != CURLE_OK)
219 rc = EVEL_CURL_LIBRARY_FAIL;
220 log_error_state("Failed to initialize libCURL with the API URL. "
221 "Error code=%d (%s)", curl_rc, curl_err_string);
224 EVEL_INFO("Initializing CURL to send events to: %s", event_api_url);
226 /***************************************************************************/
227 /* send all data to this function. */
228 /***************************************************************************/
229 curl_rc = curl_easy_setopt(curl_handle,
230 CURLOPT_WRITEFUNCTION,
231 evel_write_callback);
232 if (curl_rc != CURLE_OK)
234 rc = EVEL_CURL_LIBRARY_FAIL;
235 log_error_state("Failed to initialize libCURL with the write callback. "
236 "Error code=%d (%s)", curl_rc, curl_err_string);
240 /***************************************************************************/
241 /* some servers don't like requests that are made without a user-agent */
242 /* field, so we provide one. */
243 /***************************************************************************/
244 curl_rc = curl_easy_setopt(curl_handle,
246 "libcurl-agent/1.0");
247 if (curl_rc != CURLE_OK)
249 rc = EVEL_CURL_LIBRARY_FAIL;
250 log_error_state("Failed to initialize libCURL to upload. "
251 "Error code=%d (%s)", curl_rc, curl_err_string);
255 /***************************************************************************/
256 /* Specify that we are going to POST data. */
257 /***************************************************************************/
258 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
259 if (curl_rc != CURLE_OK)
261 rc = EVEL_CURL_LIBRARY_FAIL;
262 log_error_state("Failed to initialize libCURL to upload. "
263 "Error code=%d (%s)", curl_rc, curl_err_string);
267 /***************************************************************************/
268 /* we want to use our own read function. */
269 /***************************************************************************/
270 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, read_callback);
271 if (curl_rc != CURLE_OK)
273 rc = EVEL_CURL_LIBRARY_FAIL;
274 log_error_state("Failed to initialize libCURL to upload using read "
275 "function. Error code=%d (%s)", curl_rc, curl_err_string);
279 /***************************************************************************/
280 /* All of our events are JSON encoded. We also suppress the */
281 /* Expect: 100-continue header that we would otherwise get since it */
282 /* confuses some servers. */
284 /* @TODO: do AT&T want this behavior? */
285 /***************************************************************************/
286 hdr_chunk = curl_slist_append(hdr_chunk, "Content-type: application/json");
287 hdr_chunk = curl_slist_append(hdr_chunk, "Expect:");
289 /***************************************************************************/
290 /* set our custom set of headers. */
291 /***************************************************************************/
292 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, hdr_chunk);
293 if (curl_rc != CURLE_OK)
295 rc = EVEL_CURL_LIBRARY_FAIL;
296 log_error_state("Failed to initialize libCURL to use custom headers. "
297 "Error code=%d (%s)", curl_rc, curl_err_string);
301 /***************************************************************************/
302 /* Set the timeout for the operation. */
303 /***************************************************************************/
304 curl_rc = curl_easy_setopt(curl_handle,
307 if (curl_rc != CURLE_OK)
309 rc = EVEL_CURL_LIBRARY_FAIL;
310 log_error_state("Failed to initialize libCURL for API timeout. "
311 "Error code=%d (%s)", curl_rc, curl_err_string);
315 /***************************************************************************/
316 /* Set that we want Basic authentication with username:password Base-64 */
317 /* encoded for the operation. */
318 /***************************************************************************/
319 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
320 if (curl_rc != CURLE_OK)
322 rc = EVEL_CURL_LIBRARY_FAIL;
323 log_error_state("Failed to initialize libCURL for Basic Authentication. "
324 "Error code=%d (%s)", curl_rc, curl_err_string);
327 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_USERNAME, username);
328 if (curl_rc != CURLE_OK)
330 rc = EVEL_CURL_LIBRARY_FAIL;
331 log_error_state("Failed to initialize libCURL with username. "
332 "Error code=%d (%s)", curl_rc, curl_err_string);
335 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_PASSWORD, password);
336 if (curl_rc != CURLE_OK)
338 rc = EVEL_CURL_LIBRARY_FAIL;
339 log_error_state("Failed to initialize libCURL with password. "
340 "Error code=%d (%s)", curl_rc, curl_err_string);
344 /***************************************************************************/
345 /* Initialize a message ring-buffer to be used between the foreground and */
346 /* the thread which sends the messages. This can't fail. */
347 /***************************************************************************/
348 ring_buffer_initialize(&event_buffer, EVEL_EVENT_BUFFER_DEPTH);
350 /***************************************************************************/
351 /* Initialize the priority post buffer to empty. */
352 /***************************************************************************/
353 priority_post.memory = NULL;
361 /**************************************************************************//**
362 * Run the event handler.
364 * Spawns the thread responsible for handling events and sending them to the
367 * @return Status code.
368 * @retval ::EVEL_SUCCESS if everything OK.
369 * @retval One of ::EVEL_ERR_CODES if there was a problem.
370 *****************************************************************************/
371 EVEL_ERR_CODES event_handler_run()
373 EVEL_ERR_CODES rc = EVEL_SUCCESS;
378 /***************************************************************************/
379 /* Start the event handler thread. */
380 /***************************************************************************/
381 evt_handler_state = EVT_HANDLER_INACTIVE;
382 pthread_rc = pthread_create(&evt_handler_thread, NULL, event_handler, NULL);
385 rc = EVEL_PTHREAD_LIBRARY_FAIL;
386 log_error_state("Failed to start event handler thread. "
387 "Error code=%d", pthread_rc);
394 /**************************************************************************//**
395 * Terminate the event handler.
397 * Shuts down the event handler thread in as clean a way as possible. Sets the
398 * global exit flag and then signals the thread to interrupt it since it's
399 * most likely waiting on the ring-buffer.
401 * Having achieved an orderly shutdown of the event handler thread, clean up
402 * the cURL library's resources cleanly.
404 * @return Status code.
405 * @retval ::EVEL_SUCCESS if everything OK.
406 * @retval One of ::EVEL_ERR_CODES if there was a problem.
407 *****************************************************************************/
408 EVEL_ERR_CODES event_handler_terminate()
410 EVEL_ERR_CODES rc = EVEL_SUCCESS;
413 EVENT_INTERNAL *event = NULL;
415 /***************************************************************************/
416 /* Make sure that we were initialized before trying to terminate the */
417 /* event handler thread. */
418 /***************************************************************************/
419 if (evt_handler_state != EVT_HANDLER_UNINITIALIZED)
421 /*************************************************************************/
422 /* Make sure that the event handler knows it's time to die. */
423 /*************************************************************************/
424 event = evel_new_internal_event(EVT_CMD_TERMINATE);
427 /***********************************************************************/
428 /* We failed to get an event, but we don't bail out - we will just */
429 /* clean up what we can and continue on our way, since we're exiting */
431 /***********************************************************************/
432 EVEL_ERROR("Failed to get internal event - perform dirty exit instead!");
436 /***********************************************************************/
437 /* Post the event then wait for the Event Handler to exit. Set the */
438 /* global command, too, in case the ring-buffer is full. */
439 /***********************************************************************/
440 EVEL_DEBUG("Sending event to Event Hander to request it to exit.");
441 evt_handler_state = EVT_HANDLER_REQUEST_TERMINATE;
442 evel_post_event((EVENT_HEADER *) event);
443 pthread_join(evt_handler_thread, NULL);
444 EVEL_DEBUG("Event Handler thread has exited.");
449 EVEL_DEBUG("Event handler was not initialized, so no need to kill it");
452 /***************************************************************************/
453 /* Clean-up the cURL library. */
454 /***************************************************************************/
455 if (curl_handle != NULL)
457 curl_easy_cleanup(curl_handle);
460 if (hdr_chunk != NULL)
462 curl_slist_free_all(hdr_chunk);
466 /***************************************************************************/
467 /* Free off the stored API URL strings. */
468 /***************************************************************************/
469 if (evel_event_api_url != NULL)
471 free(evel_event_api_url);
472 evel_event_api_url = NULL;
474 if (evel_throt_api_url != NULL)
476 free(evel_throt_api_url);
477 evel_throt_api_url = NULL;
484 /**************************************************************************//**
487 * @note So far as the caller is concerned, successfully posting the event
488 * relinquishes all responsibility for the event - the library will take care
489 * of freeing the event in due course.
491 * @param event The event to be posted.
493 * @returns Status code
494 * @retval EVEL_SUCCESS On success
495 * @retval "One of ::EVEL_ERR_CODES" On failure.
496 *****************************************************************************/
497 EVEL_ERR_CODES evel_post_event(EVENT_HEADER * event)
499 int rc = EVEL_SUCCESS;
503 /***************************************************************************/
504 /* Check preconditions. */
505 /***************************************************************************/
506 assert(event != NULL);
508 /***************************************************************************/
509 /* We need to make sure that we are either initializing or running */
510 /* normally before writing the event into the buffer so that we can */
511 /* guarantee that the ring-buffer empties properly on exit. */
512 /***************************************************************************/
513 if ((evt_handler_state == EVT_HANDLER_ACTIVE) ||
514 (evt_handler_state == EVT_HANDLER_INACTIVE) ||
515 (evt_handler_state == EVT_HANDLER_REQUEST_TERMINATE))
517 if (ring_buffer_write(&event_buffer, event) == 0)
519 log_error_state("Failed to write event to buffer - event dropped!");
520 rc = EVEL_EVENT_BUFFER_FULL;
521 evel_free_event(event);
526 /*************************************************************************/
527 /* System is not in active operation, so reject the event. */
528 /*************************************************************************/
529 log_error_state("Event Handler system not active - event dropped!");
530 rc = EVEL_EVENT_HANDLER_INACTIVE;
531 evel_free_event(event);
538 /**************************************************************************//**
539 * Post an event to the Vendor Event Listener API.
541 * @returns Status code
542 * @retval EVEL_SUCCESS On success
543 * @retval "One of ::EVEL_ERR_CODES" On failure.
544 *****************************************************************************/
545 static EVEL_ERR_CODES evel_post_api(char * msg, size_t size)
547 int rc = EVEL_SUCCESS;
548 CURLcode curl_rc = CURLE_OK;
549 MEMORY_CHUNK rx_chunk;
550 MEMORY_CHUNK tx_chunk;
551 int http_response_code = 0;
555 /***************************************************************************/
556 /* Create the memory chunk to be used for the response to the post. The */
557 /* will be realloced. */
558 /***************************************************************************/
559 rx_chunk.memory = malloc(1);
560 assert(rx_chunk.memory != NULL);
563 /***************************************************************************/
564 /* Create the memory chunk to be sent as the body of the post. */
565 /***************************************************************************/
566 tx_chunk.memory = msg;
567 tx_chunk.size = size;
568 EVEL_DEBUG("Sending chunk of size %d", tx_chunk.size);
570 /***************************************************************************/
571 /* Point to the data to be received. */
572 /***************************************************************************/
573 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &rx_chunk);
574 if (curl_rc != CURLE_OK)
576 rc = EVEL_CURL_LIBRARY_FAIL;
577 log_error_state("Failed to initialize libCURL to upload. "
578 "Error code=%d (%s)", curl_rc, curl_err_string);
581 EVEL_DEBUG("Initialized data to receive");
583 /***************************************************************************/
584 /* Pointer to pass to our read function */
585 /***************************************************************************/
586 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READDATA, &tx_chunk);
587 if (curl_rc != CURLE_OK)
589 rc = EVEL_CURL_LIBRARY_FAIL;
590 log_error_state("Failed to set upload data for libCURL to upload. "
591 "Error code=%d (%s)", curl_rc, curl_err_string);
594 EVEL_DEBUG("Initialized data to send");
596 /***************************************************************************/
597 /* Size of the data to transmit. */
598 /***************************************************************************/
599 curl_rc = curl_easy_setopt(curl_handle,
600 CURLOPT_POSTFIELDSIZE,
602 if (curl_rc != CURLE_OK)
604 rc = EVEL_CURL_LIBRARY_FAIL;
605 log_error_state("Failed to set length of upload data for libCURL to "
606 "upload. Error code=%d (%s)", curl_rc, curl_err_string);
609 EVEL_DEBUG("Initialized length of data to send");
611 /***************************************************************************/
612 /* Now run off and do what you've been told! */
613 /***************************************************************************/
614 curl_rc = curl_easy_perform(curl_handle);
615 if (curl_rc != CURLE_OK)
617 rc = EVEL_CURL_LIBRARY_FAIL;
618 log_error_state("Failed to transfer an event to Vendor Event Listener! "
619 "Error code=%d (%s)", curl_rc, curl_err_string);
620 EVEL_ERROR("Dropped event: %s", msg);
624 /***************************************************************************/
625 /* See what response we got - any 2XX response is good. */
626 /***************************************************************************/
627 curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code);
628 EVEL_DEBUG("HTTP response code: %d", http_response_code);
629 if ((http_response_code / 100) == 2)
631 /*************************************************************************/
632 /* If the server responded with data it may be interesting but not a */
634 /*************************************************************************/
635 if ((rx_chunk.size > 0) && (rx_chunk.memory != NULL))
637 EVEL_DEBUG("Server returned data = %d (%s)",
641 /***********************************************************************/
642 /* If this is a response to priority post, then we're not interested. */
643 /***********************************************************************/
644 if (priority_post.memory != NULL)
646 EVEL_ERROR("Ignoring priority post response");
650 evel_handle_event_response(&rx_chunk, &priority_post);
656 EVEL_ERROR("Unexpected HTTP response code: %d with data size %d (%s)",
659 rx_chunk.size > 0 ? rx_chunk.memory : "NONE");
660 EVEL_ERROR("Potentially dropped event: %s", msg);
664 free(rx_chunk.memory);
669 /**************************************************************************//**
670 * Callback function to provide data to send.
672 * Copy data into the supplied buffer, read_callback::ptr, checking size
675 * @returns Number of bytes placed into read_callback::ptr. 0 for EOF.
676 *****************************************************************************/
677 static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
680 size_t bytes_to_write = 0;
681 MEMORY_CHUNK *tx_chunk = (MEMORY_CHUNK *)userp;
685 bytes_to_write = min(size*nmemb, tx_chunk->size);
687 if (bytes_to_write > 0)
689 EVEL_DEBUG("Going to try to write %d bytes", bytes_to_write);
690 strncpy((char *)ptr, tx_chunk->memory, bytes_to_write);
691 tx_chunk->memory += bytes_to_write;
692 tx_chunk->size -= bytes_to_write;
693 rtn = bytes_to_write;
697 EVEL_DEBUG("Reached EOF");
704 /**************************************************************************//**
705 * Callback function to provide returned data.
707 * Copy data into the supplied buffer, write_callback::ptr, checking size
710 * @returns Number of bytes placed into write_callback::ptr. 0 for EOF.
711 *****************************************************************************/
712 size_t evel_write_callback(void *contents,
717 size_t realsize = size * nmemb;
718 MEMORY_CHUNK * rx_chunk = (MEMORY_CHUNK *)userp;
722 EVEL_DEBUG("Called with %d chunks of %d size = %d", nmemb, size, realsize);
723 EVEL_DEBUG("rx chunk size is %d", rx_chunk->size);
725 rx_chunk->memory = realloc(rx_chunk->memory, rx_chunk->size + realsize + 1);
726 if(rx_chunk->memory == NULL) {
728 printf("not enough memory (realloc returned NULL)\n");
732 memcpy(&(rx_chunk->memory[rx_chunk->size]), contents, realsize);
733 rx_chunk->size += realsize;
734 rx_chunk->memory[rx_chunk->size] = 0;
736 EVEL_DEBUG("Rx data: %s", rx_chunk->memory);
737 EVEL_DEBUG("Returning: %d", realsize);
743 /**************************************************************************//**
746 * Watch for messages coming on the internal queue and send them to the
749 * param[in] arg Argument - unused.
750 *****************************************************************************/
751 static void * event_handler(void * arg __attribute__ ((unused)))
754 EVENT_HEADER * msg = NULL;
755 EVENT_INTERNAL * internal_msg = NULL;
757 char json_body[EVEL_MAX_JSON_BODY];
758 int rc = EVEL_SUCCESS;
761 EVEL_INFO("Event handler thread started");
763 /***************************************************************************/
764 /* Set this thread to be cancellable immediately. */
765 /***************************************************************************/
766 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type);
768 /***************************************************************************/
769 /* Set the handler as active, defending against weird situations like */
770 /* immediately shutting down after initializing the library so the */
771 /* handler never gets started up properly. */
772 /***************************************************************************/
773 if (evt_handler_state == EVT_HANDLER_INACTIVE)
775 evt_handler_state = EVT_HANDLER_ACTIVE;
779 EVEL_ERROR("Event Handler State was not INACTIVE at start-up - "
780 "Handler will exit immediately!");
783 while (evt_handler_state == EVT_HANDLER_ACTIVE)
785 /*************************************************************************/
786 /* Wait for a message to be received. */
787 /*************************************************************************/
788 EVEL_DEBUG("Event handler getting any messages");
789 msg = ring_buffer_read(&event_buffer);
791 /*************************************************************************/
792 /* Internal events get special treatment while regular events get posted */
793 /* to the far side. */
794 /*************************************************************************/
795 if (msg->event_domain != EVEL_DOMAIN_INTERNAL)
797 EVEL_DEBUG("External event received");
799 /***********************************************************************/
800 /* Encode the event in JSON. */
801 /***********************************************************************/
802 json_size = evel_json_encode_event(json_body, EVEL_MAX_JSON_BODY, msg);
804 /***********************************************************************/
805 /* Send the JSON across the API. */
806 /***********************************************************************/
807 EVEL_DEBUG("Sending JSON of size %d is: %s", json_size, json_body);
808 rc = evel_post_api(json_body, json_size);
809 if (rc != EVEL_SUCCESS)
811 EVEL_ERROR("Failed to transfer the data. Error code=%d", rc);
816 EVEL_DEBUG("Internal event received");
817 internal_msg = (EVENT_INTERNAL *) msg;
818 assert(internal_msg->command == EVT_CMD_TERMINATE);
819 evt_handler_state = EVT_HANDLER_TERMINATING;
822 /*************************************************************************/
823 /* We are responsible for freeing the memory. */
824 /*************************************************************************/
825 evel_free_event(msg);
828 /*************************************************************************/
829 /* There may be a single priority post to be sent. */
830 /*************************************************************************/
831 if (priority_post.memory != NULL)
833 EVEL_DEBUG("Priority Post");
835 /***********************************************************************/
836 /* Set the URL for the throttling API. */
837 /***********************************************************************/
838 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_throt_api_url);
839 if (curl_rc != CURLE_OK)
841 /*********************************************************************/
842 /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which */
843 /* case we carry on regardless. */
844 /*********************************************************************/
845 EVEL_ERROR("Failed to set throttling URL. Error code=%d", rc);
849 rc = evel_post_api(priority_post.memory, priority_post.size);
850 if (rc != EVEL_SUCCESS)
852 EVEL_ERROR("Failed to transfer priority post. Error code=%d", rc);
856 /***********************************************************************/
857 /* Reinstate the URL for the event API. */
858 /***********************************************************************/
859 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_event_api_url);
860 if (curl_rc != CURLE_OK)
862 /*********************************************************************/
863 /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which */
864 /* case we carry on regardless. */
865 /*********************************************************************/
866 EVEL_ERROR("Failed to reinstate events URL. Error code=%d", rc);
869 /***********************************************************************/
870 /* We are responsible for freeing the memory. */
871 /***********************************************************************/
872 free(priority_post.memory);
873 priority_post.memory = NULL;
877 /***************************************************************************/
878 /* The event handler is now exiting. The ring-buffer could contain events */
879 /* which have not been processed, so deplete those. Because we've been */
880 /* asked to exit we can be confident that the foreground will have stopped */
881 /* sending events in so we know that this process will conclude! */
882 /***************************************************************************/
883 evt_handler_state = EVT_HANDLER_TERMINATING;
884 while (!ring_buffer_is_empty(&event_buffer))
886 EVEL_DEBUG("Reading event from buffer");
887 msg = ring_buffer_read(&event_buffer);
888 evel_free_event(msg);
890 evt_handler_state = EVT_HANDLER_TERMINATED;
891 EVEL_INFO("Event handler thread stopped");
896 /**************************************************************************//**
897 * Handle a JSON response from the listener, contained in a ::MEMORY_CHUNK.
899 * Tokenize the response, and decode any tokens found.
901 * @param chunk The memory chunk containing the response.
902 * @param post The memory chunk in which to place any resulting POST.
903 *****************************************************************************/
904 void evel_handle_event_response(const MEMORY_CHUNK * const chunk,
905 MEMORY_CHUNK * const post)
907 jsmn_parser json_parser;
908 jsmntok_t json_tokens[EVEL_MAX_RESPONSE_TOKENS];
913 /***************************************************************************/
914 /* Check preconditions. */
915 /***************************************************************************/
916 assert(chunk != NULL);
917 assert(priority_post.memory == NULL);
919 EVEL_DEBUG("Response size = %d", chunk->size);
920 EVEL_DEBUG("Response = %s", chunk->memory);
922 /***************************************************************************/
923 /* Initialize the parser and tokenize the response. */
924 /***************************************************************************/
925 jsmn_init(&json_parser);
926 num_tokens = jsmn_parse(&json_parser,
930 EVEL_MAX_RESPONSE_TOKENS);
934 EVEL_ERROR("Failed to parse JSON response. "
935 "Error code=%d", num_tokens);
937 else if (num_tokens == 0)
939 EVEL_DEBUG("No tokens found in JSON response");
943 EVEL_DEBUG("Decode JSON response tokens");
944 if (!evel_handle_response_tokens(chunk, json_tokens, num_tokens, post))
946 EVEL_ERROR("Failed to handle JSON response.");
953 /**************************************************************************//**
954 * Handle a JSON response from the listener, as a list of tokens from JSMN.
956 * @param chunk Memory chunk containing the JSON buffer.
957 * @param json_tokens Array of tokens to handle.
958 * @param num_tokens The number of tokens to handle.
959 * @param post The memory chunk in which to place any resulting POST.
960 * @return true if we handled the response, false otherwise.
961 *****************************************************************************/
962 bool evel_handle_response_tokens(const MEMORY_CHUNK * const chunk,
963 const jsmntok_t * const json_tokens,
964 const int num_tokens,
965 MEMORY_CHUNK * const post)
967 bool json_ok = false;
971 /***************************************************************************/
972 /* Check preconditions. */
973 /***************************************************************************/
974 assert(chunk != NULL);
975 assert(json_tokens != NULL);
976 assert(num_tokens < EVEL_MAX_RESPONSE_TOKENS);
978 /***************************************************************************/
979 /* Peek at the tokens to decide what the response it, then call the */
980 /* appropriate handler to handle it. There is only one handler at this */
982 /***************************************************************************/
983 if (evel_tokens_match_command_list(chunk, json_tokens, num_tokens))
985 json_ok = evel_handle_command_list(chunk, json_tokens, num_tokens, post);
993 /**************************************************************************//**
994 * Determine whether a list of tokens looks like a "commandList" response.
996 * @param chunk Memory chunk containing the JSON buffer.
997 * @param json_tokens Token to check.
998 * @param num_tokens The number of tokens to handle.
999 * @return true if the tokens look like a "commandList" match, or false.
1000 *****************************************************************************/
1001 bool evel_tokens_match_command_list(const MEMORY_CHUNK * const chunk,
1002 const jsmntok_t * const json_tokens,
1003 const int num_tokens)
1005 bool result = false;
1009 /***************************************************************************/
1010 /* Make some checks on the basic layout of the commandList. */
1011 /***************************************************************************/
1012 if ((num_tokens > 3) &&
1013 (json_tokens[0].type == JSMN_OBJECT) &&
1014 (json_tokens[1].type == JSMN_STRING) &&
1015 (json_tokens[2].type == JSMN_ARRAY) &&
1016 (evel_token_equals_string(chunk, &json_tokens[1], "commandList")))
1026 /**************************************************************************//**
1027 * Check that a string token matches a given input string.
1029 * @param chunk Memory chunk containing the JSON buffer.
1030 * @param json_token Token to check.
1031 * @param check_string String to check it against.
1032 * @return true if the strings match, or false.
1033 *****************************************************************************/
1034 bool evel_token_equals_string(const MEMORY_CHUNK * const chunk,
1035 const jsmntok_t * json_token,
1036 const char * check_string)
1038 bool result = false;
1042 const int token_length = json_token->end - json_token->start;
1043 const char * const token_string = chunk->memory + json_token->start;
1045 if (token_length == (int)strlen(check_string))
1047 result = (strncmp(token_string, check_string, token_length) == 0);