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;
105 static char * evel_batch_api_url;
107 /**************************************************************************//**
108 * Initialize the event handler.
110 * Primarily responsible for getting CURL ready for use.
112 * @param[in] event_api_url
113 * The URL where the Vendor Event Listener API is expected
115 * @param[in] throt_api_url
116 * The URL where the Throttling API is expected to be.
117 * @param[in] username The username for the Basic Authentication of requests.
118 * @param[in] password The password for the Basic Authentication of requests.
119 * @param verbosity 0 for normal operation, positive values for chattier
121 *****************************************************************************/
122 EVEL_ERR_CODES event_handler_initialize(const char * const event_api_url,
123 const char * const throt_api_url,
124 const char * const username,
125 const char * const password,
128 int rc = EVEL_SUCCESS;
129 CURLcode curl_rc = CURLE_OK;
130 char batch_api_url[EVEL_MAX_URL_LEN + 1] = {0};
134 /***************************************************************************/
135 /* Check assumptions. */
136 /***************************************************************************/
137 assert(event_api_url != NULL);
138 assert(throt_api_url != NULL);
139 assert(username != NULL);
140 assert(password != NULL);
142 /***************************************************************************/
143 /* Store the API URLs. */
144 /***************************************************************************/
145 evel_event_api_url = strdup(event_api_url);
146 assert(evel_event_api_url != NULL);
147 sprintf(batch_api_url,"%s/eventBatch",event_api_url);
148 evel_batch_api_url = strdup(batch_api_url);
149 assert(evel_batch_api_url != NULL);
150 evel_throt_api_url = strdup(throt_api_url);
151 assert(evel_throt_api_url != NULL);
154 curl_version_info_data *d = curl_version_info(CURLVERSION_NOW);
155 /* compare with the 24 bit hex number in 8 bit fields */
156 if(d->version_num >= 0x072100) {
157 /* this is libcurl 7.33.0 or later */
158 EVEL_INFO("7.33 or later Curl version %x.",d->version_num);
161 EVEL_INFO("Old Curl version.");
163 /***************************************************************************/
164 /* Start the CURL library. Note that this initialization is not threadsafe */
165 /* which imposes a constraint that the EVEL library is initialized before */
166 /* any threads are started. */
167 /***************************************************************************/
168 curl_rc = curl_global_init(CURL_GLOBAL_SSL);
169 if (curl_rc != CURLE_OK)
171 rc = EVEL_CURL_LIBRARY_FAIL;
172 log_error_state("Failed to initialize libCURL. Error code=%d", curl_rc);
176 /***************************************************************************/
177 /* Get a curl handle which we'll use for all of our output. */
178 /***************************************************************************/
179 curl_handle = curl_easy_init();
180 if (curl_handle == NULL)
182 rc = EVEL_CURL_LIBRARY_FAIL;
183 log_error_state("Failed to get libCURL handle");
187 /***************************************************************************/
188 /* Prime the library to give friendly error codes. */
189 /***************************************************************************/
190 curl_rc = curl_easy_setopt(curl_handle,
193 if (curl_rc != CURLE_OK)
195 rc = EVEL_CURL_LIBRARY_FAIL;
196 log_error_state("Failed to initialize libCURL to provide friendly errors. "
197 "Error code=%d", curl_rc);
201 /***************************************************************************/
202 /* If running in verbose mode generate more output. */
203 /***************************************************************************/
206 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
207 if (curl_rc != CURLE_OK)
209 rc = EVEL_CURL_LIBRARY_FAIL;
210 log_error_state("Failed to initialize libCURL to be verbose. "
211 "Error code=%d", curl_rc);
216 /***************************************************************************/
217 /* Set the URL for the API. */
218 /***************************************************************************/
219 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, event_api_url);
220 if (curl_rc != CURLE_OK)
222 rc = EVEL_CURL_LIBRARY_FAIL;
223 log_error_state("Failed to initialize libCURL with the API URL. "
224 "Error code=%d (%s)", curl_rc, curl_err_string);
227 EVEL_INFO("Initializing CURL to send events to: %s", event_api_url);
229 /***************************************************************************/
230 /* send all data to this function. */
231 /***************************************************************************/
232 curl_rc = curl_easy_setopt(curl_handle,
233 CURLOPT_WRITEFUNCTION,
234 evel_write_callback);
235 if (curl_rc != CURLE_OK)
237 rc = EVEL_CURL_LIBRARY_FAIL;
238 log_error_state("Failed to initialize libCURL with the write callback. "
239 "Error code=%d (%s)", curl_rc, curl_err_string);
243 /***************************************************************************/
244 /* some servers don't like requests that are made without a user-agent */
245 /* field, so we provide one. */
246 /***************************************************************************/
247 curl_rc = curl_easy_setopt(curl_handle,
249 "libcurl-agent/1.0");
250 if (curl_rc != CURLE_OK)
252 rc = EVEL_CURL_LIBRARY_FAIL;
253 log_error_state("Failed to initialize libCURL to upload. "
254 "Error code=%d (%s)", curl_rc, curl_err_string);
258 /***************************************************************************/
259 /* Specify that we are going to POST data. */
260 /***************************************************************************/
261 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
262 if (curl_rc != CURLE_OK)
264 rc = EVEL_CURL_LIBRARY_FAIL;
265 log_error_state("Failed to initialize libCURL to upload. "
266 "Error code=%d (%s)", curl_rc, curl_err_string);
270 /***************************************************************************/
271 /* we want to use our own read function. */
272 /***************************************************************************/
273 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, read_callback);
274 if (curl_rc != CURLE_OK)
276 rc = EVEL_CURL_LIBRARY_FAIL;
277 log_error_state("Failed to initialize libCURL to upload using read "
278 "function. Error code=%d (%s)", curl_rc, curl_err_string);
282 /***************************************************************************/
283 /* All of our events are JSON encoded. We also suppress the */
284 /* Expect: 100-continue header that we would otherwise get since it */
285 /* confuses some servers. */
287 /* @TODO: do AT&T want this behavior? */
288 /***************************************************************************/
289 hdr_chunk = curl_slist_append(hdr_chunk, "Content-type: application/json");
290 hdr_chunk = curl_slist_append(hdr_chunk, "Expect:");
292 /***************************************************************************/
293 /* set our custom set of headers. */
294 /***************************************************************************/
295 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, hdr_chunk);
296 if (curl_rc != CURLE_OK)
298 rc = EVEL_CURL_LIBRARY_FAIL;
299 log_error_state("Failed to initialize libCURL to use custom headers. "
300 "Error code=%d (%s)", curl_rc, curl_err_string);
304 /***************************************************************************/
305 /* Set the timeout for the operation. */
306 /***************************************************************************/
307 curl_rc = curl_easy_setopt(curl_handle,
310 if (curl_rc != CURLE_OK)
312 rc = EVEL_CURL_LIBRARY_FAIL;
313 log_error_state("Failed to initialize libCURL for API timeout. "
314 "Error code=%d (%s)", curl_rc, curl_err_string);
318 /***************************************************************************/
319 /* Set that we want Basic authentication with username:password Base-64 */
320 /* encoded for the operation. */
321 /***************************************************************************/
322 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
323 if (curl_rc != CURLE_OK)
325 rc = EVEL_CURL_LIBRARY_FAIL;
326 log_error_state("Failed to initialize libCURL for Basic Authentication. "
327 "Error code=%d (%s)", curl_rc, curl_err_string);
330 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_USERNAME, username);
331 if (curl_rc != CURLE_OK)
333 rc = EVEL_CURL_LIBRARY_FAIL;
334 log_error_state("Failed to initialize libCURL with username. "
335 "Error code=%d (%s)", curl_rc, curl_err_string);
338 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_PASSWORD, password);
339 if (curl_rc != CURLE_OK)
341 rc = EVEL_CURL_LIBRARY_FAIL;
342 log_error_state("Failed to initialize libCURL with password. "
343 "Error code=%d (%s)", curl_rc, curl_err_string);
347 /***************************************************************************/
348 /* Initialize a message ring-buffer to be used between the foreground and */
349 /* the thread which sends the messages. This can't fail. */
350 /***************************************************************************/
351 ring_buffer_initialize(&event_buffer, EVEL_EVENT_BUFFER_DEPTH);
353 /***************************************************************************/
354 /* Initialize the priority post buffer to empty. */
355 /***************************************************************************/
356 priority_post.memory = NULL;
364 /**************************************************************************//**
365 * Run the event handler.
367 * Spawns the thread responsible for handling events and sending them to the
370 * @return Status code.
371 * @retval ::EVEL_SUCCESS if everything OK.
372 * @retval One of ::EVEL_ERR_CODES if there was a problem.
373 *****************************************************************************/
374 EVEL_ERR_CODES event_handler_run()
376 EVEL_ERR_CODES rc = EVEL_SUCCESS;
381 /***************************************************************************/
382 /* Start the event handler thread. */
383 /***************************************************************************/
384 evt_handler_state = EVT_HANDLER_INACTIVE;
385 pthread_rc = pthread_create(&evt_handler_thread, NULL, event_handler, NULL);
388 rc = EVEL_PTHREAD_LIBRARY_FAIL;
389 log_error_state("Failed to start event handler thread. "
390 "Error code=%d", pthread_rc);
397 /**************************************************************************//**
398 * Terminate the event handler.
400 * Shuts down the event handler thread in as clean a way as possible. Sets the
401 * global exit flag and then signals the thread to interrupt it since it's
402 * most likely waiting on the ring-buffer.
404 * Having achieved an orderly shutdown of the event handler thread, clean up
405 * the cURL library's resources cleanly.
407 * @return Status code.
408 * @retval ::EVEL_SUCCESS if everything OK.
409 * @retval One of ::EVEL_ERR_CODES if there was a problem.
410 *****************************************************************************/
411 EVEL_ERR_CODES event_handler_terminate()
413 EVEL_ERR_CODES rc = EVEL_SUCCESS;
416 EVENT_INTERNAL *event = NULL;
418 /***************************************************************************/
419 /* Make sure that we were initialized before trying to terminate the */
420 /* event handler thread. */
421 /***************************************************************************/
422 if (evt_handler_state != EVT_HANDLER_UNINITIALIZED)
424 /*************************************************************************/
425 /* Make sure that the event handler knows it's time to die. */
426 /*************************************************************************/
427 event = evel_new_internal_event(EVT_CMD_TERMINATE,"EVELinternal","EVELid");
430 /***********************************************************************/
431 /* We failed to get an event, but we don't bail out - we will just */
432 /* clean up what we can and continue on our way, since we're exiting */
434 /***********************************************************************/
435 EVEL_ERROR("Failed to get internal event - perform dirty exit instead!");
439 /***********************************************************************/
440 /* Post the event then wait for the Event Handler to exit. Set the */
441 /* global command, too, in case the ring-buffer is full. */
442 /***********************************************************************/
443 EVEL_DEBUG("Sending event to Event Hander to request it to exit.");
444 evt_handler_state = EVT_HANDLER_REQUEST_TERMINATE;
445 evel_post_event((EVENT_HEADER *) event);
446 pthread_join(evt_handler_thread, NULL);
447 EVEL_DEBUG("Event Handler thread has exited.");
452 EVEL_DEBUG("Event handler was not initialized, so no need to kill it");
455 /***************************************************************************/
456 /* Clean-up the cURL library. */
457 /***************************************************************************/
458 if (curl_handle != NULL)
460 curl_easy_cleanup(curl_handle);
463 if (hdr_chunk != NULL)
465 curl_slist_free_all(hdr_chunk);
469 /***************************************************************************/
470 /* Free off the stored API URL strings. */
471 /***************************************************************************/
472 if (evel_event_api_url != NULL)
474 free(evel_event_api_url);
475 evel_event_api_url = NULL;
477 if (evel_batch_api_url != NULL)
479 free(evel_batch_api_url);
480 evel_batch_api_url = NULL;
482 if (evel_throt_api_url != NULL)
484 free(evel_throt_api_url);
485 evel_throt_api_url = NULL;
492 /**************************************************************************//**
495 * @note So far as the caller is concerned, successfully posting the event
496 * relinquishes all responsibility for the event - the library will take care
497 * of freeing the event in due course.
499 * @param event The event to be posted.
501 * @returns Status code
502 * @retval EVEL_SUCCESS On success
503 * @retval "One of ::EVEL_ERR_CODES" On failure.
504 *****************************************************************************/
505 EVEL_ERR_CODES evel_post_event(EVENT_HEADER * event)
507 int rc = EVEL_SUCCESS;
511 /***************************************************************************/
512 /* Check preconditions. */
513 /***************************************************************************/
514 assert(event != NULL);
516 /***************************************************************************/
517 /* We need to make sure that we are either initializing or running */
518 /* normally before writing the event into the buffer so that we can */
519 /* guarantee that the ring-buffer empties properly on exit. */
520 /***************************************************************************/
521 if ((evt_handler_state == EVT_HANDLER_ACTIVE) ||
522 (evt_handler_state == EVT_HANDLER_INACTIVE) ||
523 (evt_handler_state == EVT_HANDLER_REQUEST_TERMINATE))
525 if (ring_buffer_write(&event_buffer, event) == 0)
527 log_error_state("Failed to write event to buffer - event dropped!");
528 rc = EVEL_EVENT_BUFFER_FULL;
529 evel_free_event(event);
534 /*************************************************************************/
535 /* System is not in active operation, so reject the event. */
536 /*************************************************************************/
537 log_error_state("Event Handler system not active - event dropped!");
538 rc = EVEL_EVENT_HANDLER_INACTIVE;
539 evel_free_event(event);
546 /**************************************************************************//**
547 * Post an event to the Vendor Event Listener API.
549 * @returns Status code
550 * @retval EVEL_SUCCESS On success
551 * @retval "One of ::EVEL_ERR_CODES" On failure.
552 *****************************************************************************/
553 static EVEL_ERR_CODES evel_post_api(char * msg, size_t size)
555 int rc = EVEL_SUCCESS;
556 CURLcode curl_rc = CURLE_OK;
557 MEMORY_CHUNK rx_chunk;
558 MEMORY_CHUNK tx_chunk;
559 int http_response_code = 0;
563 /***************************************************************************/
564 /* Create the memory chunk to be used for the response to the post. The */
565 /* will be realloced. */
566 /***************************************************************************/
567 rx_chunk.memory = malloc(1);
568 assert(rx_chunk.memory != NULL);
571 /***************************************************************************/
572 /* Create the memory chunk to be sent as the body of the post. */
573 /***************************************************************************/
574 tx_chunk.memory = msg;
575 tx_chunk.size = size;
576 EVEL_DEBUG("Sending chunk of size %d", tx_chunk.size);
578 /***************************************************************************/
579 /* Point to the data to be received. */
580 /***************************************************************************/
581 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &rx_chunk);
582 if (curl_rc != CURLE_OK)
584 rc = EVEL_CURL_LIBRARY_FAIL;
585 log_error_state("Failed to initialize libCURL to upload. "
586 "Error code=%d (%s)", curl_rc, curl_err_string);
589 EVEL_DEBUG("Initialized data to receive");
591 /***************************************************************************/
592 /* Pointer to pass to our read function */
593 /***************************************************************************/
594 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READDATA, &tx_chunk);
595 if (curl_rc != CURLE_OK)
597 rc = EVEL_CURL_LIBRARY_FAIL;
598 log_error_state("Failed to set upload data for libCURL to upload. "
599 "Error code=%d (%s)", curl_rc, curl_err_string);
602 EVEL_DEBUG("Initialized data to send");
604 /***************************************************************************/
605 /* Size of the data to transmit. */
606 /***************************************************************************/
607 curl_rc = curl_easy_setopt(curl_handle,
608 CURLOPT_POSTFIELDSIZE,
610 if (curl_rc != CURLE_OK)
612 rc = EVEL_CURL_LIBRARY_FAIL;
613 log_error_state("Failed to set length of upload data for libCURL to "
614 "upload. Error code=%d (%s)", curl_rc, curl_err_string);
617 EVEL_DEBUG("Initialized length of data to send");
619 /***************************************************************************/
620 /* Now run off and do what you've been told! */
621 /***************************************************************************/
622 curl_rc = curl_easy_perform(curl_handle);
623 if (curl_rc != CURLE_OK)
625 rc = EVEL_CURL_LIBRARY_FAIL;
626 log_error_state("Failed to transfer an event to Vendor Event Listener! "
627 "Error code=%d (%s)", curl_rc, curl_err_string);
628 EVEL_ERROR("Dropped event: %s", msg);
632 /***************************************************************************/
633 /* See what response we got - any 2XX response is good. */
634 /***************************************************************************/
635 curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code);
636 EVEL_DEBUG("HTTP response code: %d", http_response_code);
637 if ((http_response_code / 100) == 2)
639 /*************************************************************************/
640 /* If the server responded with data it may be interesting but not a */
642 /*************************************************************************/
643 if ((rx_chunk.size > 0) && (rx_chunk.memory != NULL))
645 EVEL_DEBUG("Server returned data = %d (%s)",
649 /***********************************************************************/
650 /* If this is a response to priority post, then we're not interested. */
651 /***********************************************************************/
652 if (priority_post.memory != NULL)
654 EVEL_ERROR("Ignoring priority post response");
658 evel_handle_event_response(&rx_chunk, &priority_post);
664 EVEL_ERROR("Unexpected HTTP response code: %d with data size %d (%s)",
667 rx_chunk.size > 0 ? rx_chunk.memory : "NONE");
668 EVEL_ERROR("Potentially dropped event: %s", msg);
672 free(rx_chunk.memory);
677 /**************************************************************************//**
678 * Callback function to provide data to send.
680 * Copy data into the supplied buffer, read_callback::ptr, checking size
683 * @returns Number of bytes placed into read_callback::ptr. 0 for EOF.
684 *****************************************************************************/
685 static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
688 size_t bytes_to_write = 0;
689 MEMORY_CHUNK *tx_chunk = (MEMORY_CHUNK *)userp;
693 bytes_to_write = min(size*nmemb, tx_chunk->size);
695 if (bytes_to_write > 0)
697 EVEL_DEBUG("Going to try to write %d bytes", bytes_to_write);
698 strncpy((char *)ptr, tx_chunk->memory, bytes_to_write);
699 tx_chunk->memory += bytes_to_write;
700 tx_chunk->size -= bytes_to_write;
701 rtn = bytes_to_write;
705 EVEL_DEBUG("Reached EOF");
712 /**************************************************************************//**
713 * Callback function to provide returned data.
715 * Copy data into the supplied buffer, write_callback::ptr, checking size
718 * @returns Number of bytes placed into write_callback::ptr. 0 for EOF.
719 *****************************************************************************/
720 size_t evel_write_callback(void *contents,
725 size_t realsize = size * nmemb;
726 MEMORY_CHUNK * rx_chunk = (MEMORY_CHUNK *)userp;
730 EVEL_DEBUG("Called with %d chunks of %d size = %d", nmemb, size, realsize);
731 EVEL_DEBUG("rx chunk size is %d", rx_chunk->size);
733 rx_chunk->memory = realloc(rx_chunk->memory, rx_chunk->size + realsize + 1);
734 if(rx_chunk->memory == NULL) {
736 printf("not enough memory (realloc returned NULL)\n");
740 memcpy(&(rx_chunk->memory[rx_chunk->size]), contents, realsize);
741 rx_chunk->size += realsize;
742 rx_chunk->memory[rx_chunk->size] = 0;
744 EVEL_DEBUG("Rx data: %s", rx_chunk->memory);
745 EVEL_DEBUG("Returning: %d", realsize);
751 /**************************************************************************//**
754 * Watch for messages coming on the internal queue and send them to the
757 * param[in] arg Argument - unused.
758 *****************************************************************************/
759 static void * event_handler(void * arg __attribute__ ((unused)))
762 EVENT_HEADER * msg = NULL;
763 EVENT_INTERNAL * internal_msg = NULL;
765 char json_body[EVEL_MAX_JSON_BODY];
766 int rc = EVEL_SUCCESS;
769 EVEL_INFO("Event handler thread started");
771 /***************************************************************************/
772 /* Set this thread to be cancellable immediately. */
773 /***************************************************************************/
774 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type);
776 /***************************************************************************/
777 /* Set the handler as active, defending against weird situations like */
778 /* immediately shutting down after initializing the library so the */
779 /* handler never gets started up properly. */
780 /***************************************************************************/
781 if (evt_handler_state == EVT_HANDLER_INACTIVE)
783 evt_handler_state = EVT_HANDLER_ACTIVE;
787 EVEL_ERROR("Event Handler State was not INACTIVE at start-up - "
788 "Handler will exit immediately!");
791 while (evt_handler_state == EVT_HANDLER_ACTIVE)
793 /*************************************************************************/
794 /* Wait for a message to be received. */
795 /*************************************************************************/
796 EVEL_DEBUG("Event handler getting any messages");
797 msg = ring_buffer_read(&event_buffer);
799 /*************************************************************************/
800 /* Internal events get special treatment while regular events get posted */
801 /* to the far side. */
802 /*************************************************************************/
803 if (msg->event_domain == EVEL_DOMAIN_BATCH )
805 EVEL_DEBUG("Batch event received");
807 /***********************************************************************/
808 /* Encode the event in JSON. */
809 /***********************************************************************/
810 json_size = evel_json_encode_batch_event(json_body, EVEL_MAX_JSON_BODY, msg);
812 /***************************************************************************/
813 /* Set the URL for the API. */
814 /***************************************************************************/
815 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_batch_api_url);
816 if (curl_rc != CURLE_OK)
818 rc = EVEL_CURL_LIBRARY_FAIL;
819 log_error_state("Failed to initialize libCURL with the Batch API URL. "
820 "Error code=%d (%s)", curl_rc, curl_err_string);
823 /***********************************************************************/
824 /* Send the JSON across the API. */
825 /***********************************************************************/
826 EVEL_DEBUG("Sending Batch JSON of size %d is: %s", json_size, json_body);
827 rc = evel_post_api(json_body, json_size);
828 if (rc != EVEL_SUCCESS)
830 EVEL_ERROR("Failed to transfer the data. Error code=%d", rc);
833 else if (msg->event_domain != EVEL_DOMAIN_INTERNAL )
835 EVEL_DEBUG("External event received");
837 /***********************************************************************/
838 /* Encode the event in JSON. */
839 /***********************************************************************/
840 json_size = evel_json_encode_event(json_body, EVEL_MAX_JSON_BODY, msg);
842 /***************************************************************************/
843 /* Set the URL for the API. */
844 /***************************************************************************/
845 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_event_api_url);
846 if (curl_rc != CURLE_OK)
848 rc = EVEL_CURL_LIBRARY_FAIL;
849 log_error_state("Failed to initialize libCURL with the API URL. "
850 "Error code=%d (%s)", curl_rc, curl_err_string);
853 /***********************************************************************/
854 /* Send the JSON across the API. */
855 /***********************************************************************/
856 EVEL_DEBUG("Sending JSON of size %d is: %s", json_size, json_body);
857 rc = evel_post_api(json_body, json_size);
858 if (rc != EVEL_SUCCESS)
860 EVEL_ERROR("Failed to transfer the data. Error code=%d", rc);
865 EVEL_DEBUG("Internal event received");
866 internal_msg = (EVENT_INTERNAL *) msg;
867 assert(internal_msg->command == EVT_CMD_TERMINATE);
868 evt_handler_state = EVT_HANDLER_TERMINATING;
871 /*************************************************************************/
872 /* We are responsible for freeing the memory. */
873 /*************************************************************************/
874 evel_free_event(msg);
877 /*************************************************************************/
878 /* There may be a single priority post to be sent. */
879 /*************************************************************************/
880 if (priority_post.memory != NULL)
882 EVEL_DEBUG("Priority Post");
884 /***********************************************************************/
885 /* Set the URL for the throttling API. */
886 /***********************************************************************/
887 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_throt_api_url);
888 if (curl_rc != CURLE_OK)
890 /*********************************************************************/
891 /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which */
892 /* case we carry on regardless. */
893 /*********************************************************************/
894 EVEL_ERROR("Failed to set throttling URL. Error code=%d", rc);
898 rc = evel_post_api(priority_post.memory, priority_post.size);
899 if (rc != EVEL_SUCCESS)
901 EVEL_ERROR("Failed to transfer priority post. Error code=%d", rc);
905 /***********************************************************************/
906 /* Reinstate the URL for the event API. */
907 /***********************************************************************/
908 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_event_api_url);
909 if (curl_rc != CURLE_OK)
911 /*********************************************************************/
912 /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which */
913 /* case we carry on regardless. */
914 /*********************************************************************/
915 EVEL_ERROR("Failed to reinstate events URL. Error code=%d", rc);
918 /***********************************************************************/
919 /* We are responsible for freeing the memory. */
920 /***********************************************************************/
921 free(priority_post.memory);
922 priority_post.memory = NULL;
926 /***************************************************************************/
927 /* The event handler is now exiting. The ring-buffer could contain events */
928 /* which have not been processed, so deplete those. Because we've been */
929 /* asked to exit we can be confident that the foreground will have stopped */
930 /* sending events in so we know that this process will conclude! */
931 /***************************************************************************/
932 evt_handler_state = EVT_HANDLER_TERMINATING;
933 while (!ring_buffer_is_empty(&event_buffer))
935 EVEL_DEBUG("Reading event from buffer");
936 msg = ring_buffer_read(&event_buffer);
937 evel_free_event(msg);
939 evt_handler_state = EVT_HANDLER_TERMINATED;
940 EVEL_INFO("Event handler thread stopped");
945 /**************************************************************************//**
946 * Handle a JSON response from the listener, contained in a ::MEMORY_CHUNK.
948 * Tokenize the response, and decode any tokens found.
950 * @param chunk The memory chunk containing the response.
951 * @param post The memory chunk in which to place any resulting POST.
952 *****************************************************************************/
953 void evel_handle_event_response(const MEMORY_CHUNK * const chunk,
954 MEMORY_CHUNK * const post)
956 jsmn_parser json_parser;
957 jsmntok_t json_tokens[EVEL_MAX_RESPONSE_TOKENS];
962 /***************************************************************************/
963 /* Check preconditions. */
964 /***************************************************************************/
965 assert(chunk != NULL);
966 assert(priority_post.memory == NULL);
968 EVEL_DEBUG("Response size = %d", chunk->size);
969 EVEL_DEBUG("Response = %s", chunk->memory);
971 /***************************************************************************/
972 /* Initialize the parser and tokenize the response. */
973 /***************************************************************************/
974 jsmn_init(&json_parser);
975 num_tokens = jsmn_parse(&json_parser,
979 EVEL_MAX_RESPONSE_TOKENS);
983 EVEL_ERROR("Failed to parse JSON response. "
984 "Error code=%d", num_tokens);
986 else if (num_tokens == 0)
988 EVEL_DEBUG("No tokens found in JSON response");
992 EVEL_DEBUG("Decode JSON response tokens");
993 if (!evel_handle_response_tokens(chunk, json_tokens, num_tokens, post))
995 EVEL_ERROR("Failed to handle JSON response.");
1002 /**************************************************************************//**
1003 * Handle a JSON response from the listener, as a list of tokens from JSMN.
1005 * @param chunk Memory chunk containing the JSON buffer.
1006 * @param json_tokens Array of tokens to handle.
1007 * @param num_tokens The number of tokens to handle.
1008 * @param post The memory chunk in which to place any resulting POST.
1009 * @return true if we handled the response, false otherwise.
1010 *****************************************************************************/
1011 bool evel_handle_response_tokens(const MEMORY_CHUNK * const chunk,
1012 const jsmntok_t * const json_tokens,
1013 const int num_tokens,
1014 MEMORY_CHUNK * const post)
1016 bool json_ok = false;
1020 /***************************************************************************/
1021 /* Check preconditions. */
1022 /***************************************************************************/
1023 assert(chunk != NULL);
1024 assert(json_tokens != NULL);
1025 assert(num_tokens < EVEL_MAX_RESPONSE_TOKENS);
1027 /***************************************************************************/
1028 /* Peek at the tokens to decide what the response it, then call the */
1029 /* appropriate handler to handle it. There is only one handler at this */
1031 /***************************************************************************/
1032 if (evel_tokens_match_command_list(chunk, json_tokens, num_tokens))
1034 json_ok = evel_handle_command_list(chunk, json_tokens, num_tokens, post);
1042 /**************************************************************************//**
1043 * Determine whether a list of tokens looks like a "commandList" response.
1045 * @param chunk Memory chunk containing the JSON buffer.
1046 * @param json_tokens Token to check.
1047 * @param num_tokens The number of tokens to handle.
1048 * @return true if the tokens look like a "commandList" match, or false.
1049 *****************************************************************************/
1050 bool evel_tokens_match_command_list(const MEMORY_CHUNK * const chunk,
1051 const jsmntok_t * const json_tokens,
1052 const int num_tokens)
1054 bool result = false;
1058 /***************************************************************************/
1059 /* Make some checks on the basic layout of the commandList. */
1060 /***************************************************************************/
1061 if ((num_tokens > 3) &&
1062 (json_tokens[0].type == JSMN_OBJECT) &&
1063 (json_tokens[1].type == JSMN_STRING) &&
1064 (json_tokens[2].type == JSMN_ARRAY) &&
1065 (evel_token_equals_string(chunk, &json_tokens[1], "commandList")))
1075 /**************************************************************************//**
1076 * Check that a string token matches a given input string.
1078 * @param chunk Memory chunk containing the JSON buffer.
1079 * @param json_token Token to check.
1080 * @param check_string String to check it against.
1081 * @return true if the strings match, or false.
1082 *****************************************************************************/
1083 bool evel_token_equals_string(const MEMORY_CHUNK * const chunk,
1084 const jsmntok_t * json_token,
1085 const char * check_string)
1087 bool result = false;
1091 const int token_length = json_token->end - json_token->start;
1092 const char * const token_string = chunk->memory + json_token->start;
1094 if (token_length == (int)strlen(check_string))
1096 result = (strncmp(token_string, check_string, token_length) == 0);