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] source_ip Source IP of VES Agent
118 * @param[in] secure Whether Using http or https
119 * @param[in] cert_file_path Path to Client Certificate file
120 * @param[in] key_file_path Path to Client key file
121 * @param[in] ca_info Path to CA info file
122 * @param[in] ca_file_path Path to CA file
123 * @param[in] verify_peer Using peer verification or not 0 or 1
124 * @param[in] verify_host Using host verification or not 0 or 1
125 * @param[in] username The username for the Basic Authentication of requests.
126 * @param[in] password The password for the Basic Authentication of requests.
127 * @param verbosity 0 for normal operation, positive values for chattier
129 *****************************************************************************/
130 EVEL_ERR_CODES event_handler_initialize(const char * const event_api_url,
131 const char * const throt_api_url,
132 const char * const source_ip,
134 const char * const cert_file_path,
135 const char * const key_file_path,
136 const char * const ca_info,
137 const char * const ca_file_path,
140 const char * const username,
141 const char * const password,
144 int rc = EVEL_SUCCESS;
145 CURLcode curl_rc = CURLE_OK;
146 char batch_api_url[EVEL_MAX_URL_LEN + 1] = {0};
147 char local_address[64];
151 /***************************************************************************/
152 /* Check assumptions. */
153 /***************************************************************************/
154 assert(event_api_url != NULL);
155 assert(throt_api_url != NULL);
156 assert(username != NULL);
157 assert(password != NULL);
159 /***************************************************************************/
160 /* Store the API URLs. */
161 /***************************************************************************/
162 evel_event_api_url = strdup(event_api_url);
163 assert(evel_event_api_url != NULL);
164 sprintf(batch_api_url,"%s/eventBatch",event_api_url);
165 evel_batch_api_url = strdup(batch_api_url);
166 assert(evel_batch_api_url != NULL);
167 evel_throt_api_url = strdup(throt_api_url);
168 assert(evel_throt_api_url != NULL);
171 curl_version_info_data *d = curl_version_info(CURLVERSION_NOW);
172 /* compare with the 24 bit hex number in 8 bit fields */
173 if(d->version_num >= 0x072100) {
174 /* this is libcurl 7.33.0 or later */
175 EVEL_INFO("7.33 or later Curl version %x.",d->version_num);
178 EVEL_INFO("Old Curl version.");
180 /***************************************************************************/
181 /* Start the CURL library. Note that this initialization is not threadsafe */
182 /* which imposes a constraint that the EVEL library is initialized before */
183 /* any threads are started. */
184 /***************************************************************************/
185 curl_rc = curl_global_init(CURL_GLOBAL_SSL);
186 if (curl_rc != CURLE_OK)
188 rc = EVEL_CURL_LIBRARY_FAIL;
189 log_error_state("Failed to initialize libCURL. Error code=%d", curl_rc);
193 /***************************************************************************/
194 /* Get a curl handle which we'll use for all of our output. */
195 /***************************************************************************/
196 curl_handle = curl_easy_init();
197 if (curl_handle == NULL)
199 rc = EVEL_CURL_LIBRARY_FAIL;
200 log_error_state("Failed to get libCURL handle");
204 /***************************************************************************/
205 /* Prime the library to give friendly error codes. */
206 /***************************************************************************/
207 curl_rc = curl_easy_setopt(curl_handle,
210 if (curl_rc != CURLE_OK)
212 rc = EVEL_CURL_LIBRARY_FAIL;
213 log_error_state("Failed to initialize libCURL to provide friendly errors. "
214 "Error code=%d", curl_rc);
218 /***************************************************************************/
219 /* If running in verbose mode generate more output. */
220 /***************************************************************************/
223 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
224 if (curl_rc != CURLE_OK)
226 rc = EVEL_CURL_LIBRARY_FAIL;
227 log_error_state("Failed to initialize libCURL to be verbose. "
228 "Error code=%d", curl_rc);
233 /***************************************************************************/
234 /* Set the URL for the API. */
235 /***************************************************************************/
236 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, event_api_url);
237 if (curl_rc != CURLE_OK)
239 rc = EVEL_CURL_LIBRARY_FAIL;
240 log_error_state("Failed to initialize libCURL with the API URL. "
241 "Error code=%d (%s)", curl_rc, curl_err_string);
244 EVEL_INFO("Initializing CURL to send events to: %s", event_api_url);
246 /***************************************************************************/
247 /* send all data to this function. */
248 /***************************************************************************/
249 curl_rc = curl_easy_setopt(curl_handle,
250 CURLOPT_WRITEFUNCTION,
251 evel_write_callback);
252 if (curl_rc != CURLE_OK)
254 rc = EVEL_CURL_LIBRARY_FAIL;
255 log_error_state("Failed to initialize libCURL with the write callback. "
256 "Error code=%d (%s)", curl_rc, curl_err_string);
260 /***************************************************************************/
261 /* configure local ip address if provided */
262 /* Default ip if NULL */
263 /***************************************************************************/
264 if( source_ip != NULL )
266 snprintf(local_address,sizeof(local_address),source_ip);
267 if( local_address[0] != '\0' )
269 curl_rc = curl_easy_setopt(curl_handle,
272 if (curl_rc != CURLE_OK)
274 rc = EVEL_CURL_LIBRARY_FAIL;
275 log_error_state("Failed to initialize libCURL with the local address. "
276 "Error code=%d (%s)", curl_rc, curl_err_string);
282 /***************************************************************************/
283 /* configure SSL options for HTTPS transfers */
284 /***************************************************************************/
287 if( cert_file_path != NULL )
289 curl_rc = curl_easy_setopt(curl_handle,
292 if (curl_rc != CURLE_OK)
294 rc = EVEL_CURL_LIBRARY_FAIL;
295 log_error_state("Failed to initialize libCURL with the client cert. "
296 "Error code=%d (%s)", curl_rc, curl_err_string);
301 if( key_file_path != NULL )
303 curl_rc = curl_easy_setopt(curl_handle,
306 if (curl_rc != CURLE_OK)
308 rc = EVEL_CURL_LIBRARY_FAIL;
309 log_error_state("Failed to initialize libCURL with the client key. "
310 "Error code=%d (%s)", curl_rc, curl_err_string);
315 if( ca_info != NULL )
317 curl_rc = curl_easy_setopt(curl_handle,
320 if (curl_rc != CURLE_OK)
322 rc = EVEL_CURL_LIBRARY_FAIL;
323 log_error_state("Failed to initialize libCURL with the CA cert file. "
324 "Error code=%d (%s)", curl_rc, curl_err_string);
329 if( ca_file_path != NULL )
331 curl_rc = curl_easy_setopt(curl_handle,
334 if (curl_rc != CURLE_OK)
336 rc = EVEL_CURL_LIBRARY_FAIL;
337 log_error_state("Failed to initialize libCURL with the CA cert path. "
338 "Error code=%d (%s)", curl_rc, curl_err_string);
343 curl_rc = curl_easy_setopt(curl_handle,
344 CURLOPT_SSL_VERIFYPEER,
346 if (curl_rc != CURLE_OK)
348 rc = EVEL_CURL_LIBRARY_FAIL;
349 log_error_state("Failed to initialize libCURL with SSL Server verification. "
350 "Error code=%d (%s)", curl_rc, curl_err_string);
353 curl_rc = curl_easy_setopt(curl_handle,
354 CURLOPT_SSL_VERIFYHOST,
356 if (curl_rc != CURLE_OK)
358 rc = EVEL_CURL_LIBRARY_FAIL;
359 log_error_state("Failed to initialize libCURL with Client host verification. "
360 "Error code=%d (%s)", curl_rc, curl_err_string);
368 /***************************************************************************/
369 /* some servers don't like requests that are made without a user-agent */
370 /* field, so we provide one. */
371 /***************************************************************************/
372 curl_rc = curl_easy_setopt(curl_handle,
374 "libcurl-agent/1.0");
375 if (curl_rc != CURLE_OK)
377 rc = EVEL_CURL_LIBRARY_FAIL;
378 log_error_state("Failed to initialize libCURL to upload. "
379 "Error code=%d (%s)", curl_rc, curl_err_string);
383 /***************************************************************************/
384 /* Specify that we are going to POST data. */
385 /***************************************************************************/
386 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
387 if (curl_rc != CURLE_OK)
389 rc = EVEL_CURL_LIBRARY_FAIL;
390 log_error_state("Failed to initialize libCURL to upload. "
391 "Error code=%d (%s)", curl_rc, curl_err_string);
395 /***************************************************************************/
396 /* we want to use our own read function. */
397 /***************************************************************************/
398 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, read_callback);
399 if (curl_rc != CURLE_OK)
401 rc = EVEL_CURL_LIBRARY_FAIL;
402 log_error_state("Failed to initialize libCURL to upload using read "
403 "function. Error code=%d (%s)", curl_rc, curl_err_string);
407 /***************************************************************************/
408 /* All of our events are JSON encoded. We also suppress the */
409 /* Expect: 100-continue header that we would otherwise get since it */
410 /* confuses some servers. */
412 /* @TODO: do AT&T want this behavior? */
413 /***************************************************************************/
414 hdr_chunk = curl_slist_append(hdr_chunk, "Content-type: application/json");
415 hdr_chunk = curl_slist_append(hdr_chunk, "Expect:");
417 /***************************************************************************/
418 /* set our custom set of headers. */
419 /***************************************************************************/
420 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, hdr_chunk);
421 if (curl_rc != CURLE_OK)
423 rc = EVEL_CURL_LIBRARY_FAIL;
424 log_error_state("Failed to initialize libCURL to use custom headers. "
425 "Error code=%d (%s)", curl_rc, curl_err_string);
429 /***************************************************************************/
430 /* Set the timeout for the operation. */
431 /***************************************************************************/
432 curl_rc = curl_easy_setopt(curl_handle,
435 if (curl_rc != CURLE_OK)
437 rc = EVEL_CURL_LIBRARY_FAIL;
438 log_error_state("Failed to initialize libCURL for API timeout. "
439 "Error code=%d (%s)", curl_rc, curl_err_string);
443 /***************************************************************************/
444 /* Set that we want Basic authentication with username:password Base-64 */
445 /* encoded for the operation. */
446 /***************************************************************************/
447 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
448 if (curl_rc != CURLE_OK)
450 rc = EVEL_CURL_LIBRARY_FAIL;
451 log_error_state("Failed to initialize libCURL for Basic Authentication. "
452 "Error code=%d (%s)", curl_rc, curl_err_string);
455 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_USERNAME, username);
456 if (curl_rc != CURLE_OK)
458 rc = EVEL_CURL_LIBRARY_FAIL;
459 log_error_state("Failed to initialize libCURL with username. "
460 "Error code=%d (%s)", curl_rc, curl_err_string);
463 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_PASSWORD, password);
464 if (curl_rc != CURLE_OK)
466 rc = EVEL_CURL_LIBRARY_FAIL;
467 log_error_state("Failed to initialize libCURL with password. "
468 "Error code=%d (%s)", curl_rc, curl_err_string);
472 /***************************************************************************/
473 /* Initialize a message ring-buffer to be used between the foreground and */
474 /* the thread which sends the messages. This can't fail. */
475 /***************************************************************************/
476 ring_buffer_initialize(&event_buffer, EVEL_EVENT_BUFFER_DEPTH);
478 /***************************************************************************/
479 /* Initialize the priority post buffer to empty. */
480 /***************************************************************************/
481 priority_post.memory = NULL;
489 /**************************************************************************//**
490 * Run the event handler.
492 * Spawns the thread responsible for handling events and sending them to the
495 * @return Status code.
496 * @retval ::EVEL_SUCCESS if everything OK.
497 * @retval One of ::EVEL_ERR_CODES if there was a problem.
498 *****************************************************************************/
499 EVEL_ERR_CODES event_handler_run()
501 EVEL_ERR_CODES rc = EVEL_SUCCESS;
506 /***************************************************************************/
507 /* Start the event handler thread. */
508 /***************************************************************************/
509 evt_handler_state = EVT_HANDLER_INACTIVE;
510 pthread_rc = pthread_create(&evt_handler_thread, NULL, event_handler, NULL);
513 rc = EVEL_PTHREAD_LIBRARY_FAIL;
514 log_error_state("Failed to start event handler thread. "
515 "Error code=%d", pthread_rc);
522 /**************************************************************************//**
523 * Terminate the event handler.
525 * Shuts down the event handler thread in as clean a way as possible. Sets the
526 * global exit flag and then signals the thread to interrupt it since it's
527 * most likely waiting on the ring-buffer.
529 * Having achieved an orderly shutdown of the event handler thread, clean up
530 * the cURL library's resources cleanly.
532 * @return Status code.
533 * @retval ::EVEL_SUCCESS if everything OK.
534 * @retval One of ::EVEL_ERR_CODES if there was a problem.
535 *****************************************************************************/
536 EVEL_ERR_CODES event_handler_terminate()
538 EVEL_ERR_CODES rc = EVEL_SUCCESS;
541 EVENT_INTERNAL *event = NULL;
543 /***************************************************************************/
544 /* Make sure that we were initialized before trying to terminate the */
545 /* event handler thread. */
546 /***************************************************************************/
547 if (evt_handler_state != EVT_HANDLER_UNINITIALIZED)
549 /*************************************************************************/
550 /* Make sure that the event handler knows it's time to die. */
551 /*************************************************************************/
552 event = evel_new_internal_event(EVT_CMD_TERMINATE,"EVELinternal","EVELid");
555 /***********************************************************************/
556 /* We failed to get an event, but we don't bail out - we will just */
557 /* clean up what we can and continue on our way, since we're exiting */
559 /***********************************************************************/
560 EVEL_ERROR("Failed to get internal event - perform dirty exit instead!");
564 /***********************************************************************/
565 /* Post the event then wait for the Event Handler to exit. Set the */
566 /* global command, too, in case the ring-buffer is full. */
567 /***********************************************************************/
568 EVEL_DEBUG("Sending event to Event Hander to request it to exit.");
569 evt_handler_state = EVT_HANDLER_REQUEST_TERMINATE;
570 evel_post_event((EVENT_HEADER *) event);
571 pthread_join(evt_handler_thread, NULL);
572 EVEL_DEBUG("Event Handler thread has exited.");
577 EVEL_DEBUG("Event handler was not initialized, so no need to kill it");
580 /***************************************************************************/
581 /* Clean-up the cURL library. */
582 /***************************************************************************/
583 if (curl_handle != NULL)
585 curl_easy_cleanup(curl_handle);
588 if (hdr_chunk != NULL)
590 curl_slist_free_all(hdr_chunk);
594 /***************************************************************************/
595 /* Free off the stored API URL strings. */
596 /***************************************************************************/
597 if (evel_event_api_url != NULL)
599 free(evel_event_api_url);
600 evel_event_api_url = NULL;
602 if (evel_batch_api_url != NULL)
604 free(evel_batch_api_url);
605 evel_batch_api_url = NULL;
607 if (evel_throt_api_url != NULL)
609 free(evel_throt_api_url);
610 evel_throt_api_url = NULL;
617 /**************************************************************************//**
620 * @note So far as the caller is concerned, successfully posting the event
621 * relinquishes all responsibility for the event - the library will take care
622 * of freeing the event in due course.
624 * @param event The event to be posted.
626 * @returns Status code
627 * @retval EVEL_SUCCESS On success
628 * @retval "One of ::EVEL_ERR_CODES" On failure.
629 *****************************************************************************/
630 EVEL_ERR_CODES evel_post_event(EVENT_HEADER * event)
632 int rc = EVEL_SUCCESS;
636 /***************************************************************************/
637 /* Check preconditions. */
638 /***************************************************************************/
639 assert(event != NULL);
641 /***************************************************************************/
642 /* We need to make sure that we are either initializing or running */
643 /* normally before writing the event into the buffer so that we can */
644 /* guarantee that the ring-buffer empties properly on exit. */
645 /***************************************************************************/
646 if ((evt_handler_state == EVT_HANDLER_ACTIVE) ||
647 (evt_handler_state == EVT_HANDLER_INACTIVE) ||
648 (evt_handler_state == EVT_HANDLER_REQUEST_TERMINATE))
650 if (ring_buffer_write(&event_buffer, event) == 0)
652 log_error_state("Failed to write event to buffer - event dropped!");
653 rc = EVEL_EVENT_BUFFER_FULL;
654 evel_free_event(event);
659 /*************************************************************************/
660 /* System is not in active operation, so reject the event. */
661 /*************************************************************************/
662 log_error_state("Event Handler system not active - event dropped!");
663 rc = EVEL_EVENT_HANDLER_INACTIVE;
664 evel_free_event(event);
671 /**************************************************************************//**
672 * Post an event to the Vendor Event Listener API.
674 * @returns Status code
675 * @retval EVEL_SUCCESS On success
676 * @retval "One of ::EVEL_ERR_CODES" On failure.
677 *****************************************************************************/
678 static EVEL_ERR_CODES evel_post_api(char * msg, size_t size)
680 int rc = EVEL_SUCCESS;
681 CURLcode curl_rc = CURLE_OK;
682 MEMORY_CHUNK rx_chunk;
683 MEMORY_CHUNK tx_chunk;
684 int http_response_code = 0;
688 /***************************************************************************/
689 /* Create the memory chunk to be used for the response to the post. The */
690 /* will be realloced. */
691 /***************************************************************************/
692 rx_chunk.memory = malloc(1);
693 assert(rx_chunk.memory != NULL);
696 /***************************************************************************/
697 /* Create the memory chunk to be sent as the body of the post. */
698 /***************************************************************************/
699 tx_chunk.memory = msg;
700 tx_chunk.size = size;
701 EVEL_DEBUG("Sending chunk of size %d", tx_chunk.size);
703 /***************************************************************************/
704 /* Point to the data to be received. */
705 /***************************************************************************/
706 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &rx_chunk);
707 if (curl_rc != CURLE_OK)
709 rc = EVEL_CURL_LIBRARY_FAIL;
710 log_error_state("Failed to initialize libCURL to upload. "
711 "Error code=%d (%s)", curl_rc, curl_err_string);
714 EVEL_DEBUG("Initialized data to receive");
716 /***************************************************************************/
717 /* Pointer to pass to our read function */
718 /***************************************************************************/
719 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READDATA, &tx_chunk);
720 if (curl_rc != CURLE_OK)
722 rc = EVEL_CURL_LIBRARY_FAIL;
723 log_error_state("Failed to set upload data for libCURL to upload. "
724 "Error code=%d (%s)", curl_rc, curl_err_string);
727 EVEL_DEBUG("Initialized data to send");
729 /***************************************************************************/
730 /* Size of the data to transmit. */
731 /***************************************************************************/
732 curl_rc = curl_easy_setopt(curl_handle,
733 CURLOPT_POSTFIELDSIZE,
735 if (curl_rc != CURLE_OK)
737 rc = EVEL_CURL_LIBRARY_FAIL;
738 log_error_state("Failed to set length of upload data for libCURL to "
739 "upload. Error code=%d (%s)", curl_rc, curl_err_string);
742 EVEL_DEBUG("Initialized length of data to send");
744 /***************************************************************************/
745 /* Now run off and do what you've been told! */
746 /***************************************************************************/
747 curl_rc = curl_easy_perform(curl_handle);
748 if (curl_rc != CURLE_OK)
750 rc = EVEL_CURL_LIBRARY_FAIL;
751 log_error_state("Failed to transfer an event to Vendor Event Listener! "
752 "Error code=%d (%s)", curl_rc, curl_err_string);
753 EVEL_ERROR("Dropped event: %s", msg);
757 /***************************************************************************/
758 /* See what response we got - any 2XX response is good. */
759 /***************************************************************************/
760 curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code);
761 EVEL_DEBUG("HTTP response code: %d", http_response_code);
762 if ((http_response_code / 100) == 2)
764 /*************************************************************************/
765 /* If the server responded with data it may be interesting but not a */
767 /*************************************************************************/
768 if ((rx_chunk.size > 0) && (rx_chunk.memory != NULL))
770 EVEL_DEBUG("Server returned data = %d (%s)",
774 /***********************************************************************/
775 /* If this is a response to priority post, then we're not interested. */
776 /***********************************************************************/
777 if (priority_post.memory != NULL)
779 EVEL_ERROR("Ignoring priority post response");
783 evel_handle_event_response(&rx_chunk, &priority_post);
789 EVEL_ERROR("Unexpected HTTP response code: %d with data size %d (%s)",
792 rx_chunk.size > 0 ? rx_chunk.memory : "NONE");
793 EVEL_ERROR("Potentially dropped event: %s", msg);
797 free(rx_chunk.memory);
802 /**************************************************************************//**
803 * Callback function to provide data to send.
805 * Copy data into the supplied buffer, read_callback::ptr, checking size
808 * @returns Number of bytes placed into read_callback::ptr. 0 for EOF.
809 *****************************************************************************/
810 static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
813 size_t bytes_to_write = 0;
814 MEMORY_CHUNK *tx_chunk = (MEMORY_CHUNK *)userp;
818 bytes_to_write = min(size*nmemb, tx_chunk->size);
820 if (bytes_to_write > 0)
822 EVEL_DEBUG("Going to try to write %d bytes", bytes_to_write);
823 strncpy((char *)ptr, tx_chunk->memory, bytes_to_write);
824 tx_chunk->memory += bytes_to_write;
825 tx_chunk->size -= bytes_to_write;
826 rtn = bytes_to_write;
830 EVEL_DEBUG("Reached EOF");
837 /**************************************************************************//**
838 * Callback function to provide returned data.
840 * Copy data into the supplied buffer, write_callback::ptr, checking size
843 * @returns Number of bytes placed into write_callback::ptr. 0 for EOF.
844 *****************************************************************************/
845 size_t evel_write_callback(void *contents,
850 size_t realsize = size * nmemb;
851 MEMORY_CHUNK * rx_chunk = (MEMORY_CHUNK *)userp;
855 EVEL_DEBUG("Called with %d chunks of %d size = %d", nmemb, size, realsize);
856 EVEL_DEBUG("rx chunk size is %d", rx_chunk->size);
858 rx_chunk->memory = realloc(rx_chunk->memory, rx_chunk->size + realsize + 1);
859 if(rx_chunk->memory == NULL) {
861 printf("not enough memory (realloc returned NULL)\n");
865 memcpy(&(rx_chunk->memory[rx_chunk->size]), contents, realsize);
866 rx_chunk->size += realsize;
867 rx_chunk->memory[rx_chunk->size] = 0;
869 EVEL_DEBUG("Rx data: %s", rx_chunk->memory);
870 EVEL_DEBUG("Returning: %d", realsize);
876 /**************************************************************************//**
879 * Watch for messages coming on the internal queue and send them to the
882 * param[in] arg Argument - unused.
883 *****************************************************************************/
884 static void * event_handler(void * arg __attribute__ ((unused)))
887 EVENT_HEADER * msg = NULL;
888 EVENT_INTERNAL * internal_msg = NULL;
890 char json_body[EVEL_MAX_JSON_BODY];
891 int rc = EVEL_SUCCESS;
894 EVEL_INFO("Event handler thread started");
896 /***************************************************************************/
897 /* Set this thread to be cancellable immediately. */
898 /***************************************************************************/
899 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type);
901 /***************************************************************************/
902 /* Set the handler as active, defending against weird situations like */
903 /* immediately shutting down after initializing the library so the */
904 /* handler never gets started up properly. */
905 /***************************************************************************/
906 if (evt_handler_state == EVT_HANDLER_INACTIVE)
908 evt_handler_state = EVT_HANDLER_ACTIVE;
912 EVEL_ERROR("Event Handler State was not INACTIVE at start-up - "
913 "Handler will exit immediately!");
916 while (evt_handler_state == EVT_HANDLER_ACTIVE)
918 /*************************************************************************/
919 /* Wait for a message to be received. */
920 /*************************************************************************/
921 EVEL_DEBUG("Event handler getting any messages");
922 msg = ring_buffer_read(&event_buffer);
924 /*************************************************************************/
925 /* Internal events get special treatment while regular events get posted */
926 /* to the far side. */
927 /*************************************************************************/
928 if (msg->event_domain == EVEL_DOMAIN_BATCH )
930 EVEL_DEBUG("Batch event received");
932 /***********************************************************************/
933 /* Encode the event in JSON. */
934 /***********************************************************************/
935 json_size = evel_json_encode_batch_event(json_body, EVEL_MAX_JSON_BODY, msg);
937 /***************************************************************************/
938 /* Set the URL for the API. */
939 /***************************************************************************/
940 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_batch_api_url);
941 if (curl_rc != CURLE_OK)
943 rc = EVEL_CURL_LIBRARY_FAIL;
944 log_error_state("Failed to initialize libCURL with the Batch API URL. "
945 "Error code=%d (%s)", curl_rc, curl_err_string);
948 /***********************************************************************/
949 /* Send the JSON across the API. */
950 /***********************************************************************/
951 EVEL_DEBUG("Sending Batch JSON of size %d is: %s", json_size, json_body);
952 rc = evel_post_api(json_body, json_size);
953 if (rc != EVEL_SUCCESS)
955 EVEL_ERROR("Failed to transfer the data. Error code=%d", rc);
958 else if (msg->event_domain != EVEL_DOMAIN_INTERNAL )
960 EVEL_DEBUG("External event received");
962 /***********************************************************************/
963 /* Encode the event in JSON. */
964 /***********************************************************************/
965 json_size = evel_json_encode_event(json_body, EVEL_MAX_JSON_BODY, msg);
967 /***************************************************************************/
968 /* Set the URL for the API. */
969 /***************************************************************************/
970 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_event_api_url);
971 if (curl_rc != CURLE_OK)
973 rc = EVEL_CURL_LIBRARY_FAIL;
974 log_error_state("Failed to initialize libCURL with the API URL. "
975 "Error code=%d (%s)", curl_rc, curl_err_string);
978 /***********************************************************************/
979 /* Send the JSON across the API. */
980 /***********************************************************************/
981 EVEL_DEBUG("Sending JSON of size %d is: %s", json_size, json_body);
982 rc = evel_post_api(json_body, json_size);
983 if (rc != EVEL_SUCCESS)
985 EVEL_ERROR("Failed to transfer the data. Error code=%d", rc);
990 EVEL_DEBUG("Internal event received");
991 internal_msg = (EVENT_INTERNAL *) msg;
992 assert(internal_msg->command == EVT_CMD_TERMINATE);
993 evt_handler_state = EVT_HANDLER_TERMINATING;
996 /*************************************************************************/
997 /* We are responsible for freeing the memory. */
998 /*************************************************************************/
999 evel_free_event(msg);
1002 /*************************************************************************/
1003 /* There may be a single priority post to be sent. */
1004 /*************************************************************************/
1005 if (priority_post.memory != NULL)
1007 EVEL_DEBUG("Priority Post");
1009 /***********************************************************************/
1010 /* Set the URL for the throttling API. */
1011 /***********************************************************************/
1012 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_throt_api_url);
1013 if (curl_rc != CURLE_OK)
1015 /*********************************************************************/
1016 /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which */
1017 /* case we carry on regardless. */
1018 /*********************************************************************/
1019 EVEL_ERROR("Failed to set throttling URL. Error code=%d", rc);
1023 rc = evel_post_api(priority_post.memory, priority_post.size);
1024 if (rc != EVEL_SUCCESS)
1026 EVEL_ERROR("Failed to transfer priority post. Error code=%d", rc);
1030 /***********************************************************************/
1031 /* Reinstate the URL for the event API. */
1032 /***********************************************************************/
1033 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_event_api_url);
1034 if (curl_rc != CURLE_OK)
1036 /*********************************************************************/
1037 /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which */
1038 /* case we carry on regardless. */
1039 /*********************************************************************/
1040 EVEL_ERROR("Failed to reinstate events URL. Error code=%d", rc);
1043 /***********************************************************************/
1044 /* We are responsible for freeing the memory. */
1045 /***********************************************************************/
1046 free(priority_post.memory);
1047 priority_post.memory = NULL;
1051 /***************************************************************************/
1052 /* The event handler is now exiting. The ring-buffer could contain events */
1053 /* which have not been processed, so deplete those. Because we've been */
1054 /* asked to exit we can be confident that the foreground will have stopped */
1055 /* sending events in so we know that this process will conclude! */
1056 /***************************************************************************/
1057 evt_handler_state = EVT_HANDLER_TERMINATING;
1058 while (!ring_buffer_is_empty(&event_buffer))
1060 EVEL_DEBUG("Reading event from buffer");
1061 msg = ring_buffer_read(&event_buffer);
1062 evel_free_event(msg);
1064 evt_handler_state = EVT_HANDLER_TERMINATED;
1065 EVEL_INFO("Event handler thread stopped");
1070 /**************************************************************************//**
1071 * Handle a JSON response from the listener, contained in a ::MEMORY_CHUNK.
1073 * Tokenize the response, and decode any tokens found.
1075 * @param chunk The memory chunk containing the response.
1076 * @param post The memory chunk in which to place any resulting POST.
1077 *****************************************************************************/
1078 void evel_handle_event_response(const MEMORY_CHUNK * const chunk,
1079 MEMORY_CHUNK * const post)
1081 jsmn_parser json_parser;
1082 jsmntok_t json_tokens[EVEL_MAX_RESPONSE_TOKENS];
1087 /***************************************************************************/
1088 /* Check preconditions. */
1089 /***************************************************************************/
1090 assert(chunk != NULL);
1091 assert(priority_post.memory == NULL);
1093 EVEL_DEBUG("Response size = %d", chunk->size);
1094 EVEL_DEBUG("Response = %s", chunk->memory);
1096 /***************************************************************************/
1097 /* Initialize the parser and tokenize the response. */
1098 /***************************************************************************/
1099 jsmn_init(&json_parser);
1100 num_tokens = jsmn_parse(&json_parser,
1104 EVEL_MAX_RESPONSE_TOKENS);
1108 EVEL_ERROR("Failed to parse JSON response. "
1109 "Error code=%d", num_tokens);
1111 else if (num_tokens == 0)
1113 EVEL_DEBUG("No tokens found in JSON response");
1117 EVEL_DEBUG("Decode JSON response tokens");
1118 if (!evel_handle_response_tokens(chunk, json_tokens, num_tokens, post))
1120 EVEL_ERROR("Failed to handle JSON response.");
1127 /**************************************************************************//**
1128 * Handle a JSON response from the listener, as a list of tokens from JSMN.
1130 * @param chunk Memory chunk containing the JSON buffer.
1131 * @param json_tokens Array of tokens to handle.
1132 * @param num_tokens The number of tokens to handle.
1133 * @param post The memory chunk in which to place any resulting POST.
1134 * @return true if we handled the response, false otherwise.
1135 *****************************************************************************/
1136 bool evel_handle_response_tokens(const MEMORY_CHUNK * const chunk,
1137 const jsmntok_t * const json_tokens,
1138 const int num_tokens,
1139 MEMORY_CHUNK * const post)
1141 bool json_ok = false;
1145 /***************************************************************************/
1146 /* Check preconditions. */
1147 /***************************************************************************/
1148 assert(chunk != NULL);
1149 assert(json_tokens != NULL);
1150 assert(num_tokens < EVEL_MAX_RESPONSE_TOKENS);
1152 /***************************************************************************/
1153 /* Peek at the tokens to decide what the response it, then call the */
1154 /* appropriate handler to handle it. There is only one handler at this */
1156 /***************************************************************************/
1157 if (evel_tokens_match_command_list(chunk, json_tokens, num_tokens))
1159 json_ok = evel_handle_command_list(chunk, json_tokens, num_tokens, post);
1167 /**************************************************************************//**
1168 * Determine whether a list of tokens looks like a "commandList" response.
1170 * @param chunk Memory chunk containing the JSON buffer.
1171 * @param json_tokens Token to check.
1172 * @param num_tokens The number of tokens to handle.
1173 * @return true if the tokens look like a "commandList" match, or false.
1174 *****************************************************************************/
1175 bool evel_tokens_match_command_list(const MEMORY_CHUNK * const chunk,
1176 const jsmntok_t * const json_tokens,
1177 const int num_tokens)
1179 bool result = false;
1183 /***************************************************************************/
1184 /* Make some checks on the basic layout of the commandList. */
1185 /***************************************************************************/
1186 if ((num_tokens > 3) &&
1187 (json_tokens[0].type == JSMN_OBJECT) &&
1188 (json_tokens[1].type == JSMN_STRING) &&
1189 (json_tokens[2].type == JSMN_ARRAY) &&
1190 (evel_token_equals_string(chunk, &json_tokens[1], "commandList")))
1200 /**************************************************************************//**
1201 * Check that a string token matches a given input string.
1203 * @param chunk Memory chunk containing the JSON buffer.
1204 * @param json_token Token to check.
1205 * @param check_string String to check it against.
1206 * @return true if the strings match, or false.
1207 *****************************************************************************/
1208 bool evel_token_equals_string(const MEMORY_CHUNK * const chunk,
1209 const jsmntok_t * json_token,
1210 const char * check_string)
1212 bool result = false;
1216 const int token_length = json_token->end - json_token->start;
1217 const char * const token_string = chunk->memory + json_token->start;
1219 if (token_length == (int)strlen(check_string))
1221 result = (strncmp(token_string, check_string, token_length) == 0);