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] ring_buf_size Initial size of ring buffer
119 * @param[in] secure Whether Using http or https
120 * @param[in] cert_file_path Path to Client Certificate file
121 * @param[in] key_file_path Path to Client key file
122 * @param[in] ca_info Path to CA info file
123 * @param[in] ca_file_path Path to CA file
124 * @param[in] verify_peer Using peer verification or not 0 or 1
125 * @param[in] verify_host Using host verification or not 0 or 1
126 * @param[in] username The username for the Basic Authentication of requests.
127 * @param[in] password The password for the Basic Authentication of requests.
128 * @param verbosity 0 for normal operation, positive values for chattier
130 *****************************************************************************/
131 EVEL_ERR_CODES event_handler_initialize(const char * const event_api_url,
132 const char * const throt_api_url,
133 const char * const source_ip,
136 const char * const cert_file_path,
137 const char * const key_file_path,
138 const char * const ca_info,
139 const char * const ca_file_path,
142 const char * const username,
143 const char * const password,
146 int rc = EVEL_SUCCESS;
147 CURLcode curl_rc = CURLE_OK;
148 char batch_api_url[EVEL_MAX_URL_LEN + 1] = {0};
149 char local_address[64];
153 /***************************************************************************/
154 /* Check assumptions. */
155 /***************************************************************************/
156 assert(event_api_url != NULL);
157 assert(throt_api_url != NULL);
158 assert(username != NULL);
159 assert(password != NULL);
161 /***************************************************************************/
162 /* Store the API URLs. */
163 /***************************************************************************/
164 evel_event_api_url = strdup(event_api_url);
165 assert(evel_event_api_url != NULL);
166 sprintf(batch_api_url,"%s/eventBatch",event_api_url);
167 evel_batch_api_url = strdup(batch_api_url);
168 assert(evel_batch_api_url != NULL);
169 evel_throt_api_url = strdup(throt_api_url);
170 assert(evel_throt_api_url != NULL);
173 curl_version_info_data *d = curl_version_info(CURLVERSION_NOW);
174 /* compare with the 24 bit hex number in 8 bit fields */
175 if(d->version_num >= 0x072100) {
176 /* this is libcurl 7.33.0 or later */
177 EVEL_INFO("7.33 or later Curl version %x.",d->version_num);
180 EVEL_INFO("Old Curl version.");
182 /***************************************************************************/
183 /* Start the CURL library. Note that this initialization is not threadsafe */
184 /* which imposes a constraint that the EVEL library is initialized before */
185 /* any threads are started. */
186 /***************************************************************************/
187 curl_rc = curl_global_init(CURL_GLOBAL_SSL);
188 if (curl_rc != CURLE_OK)
190 rc = EVEL_CURL_LIBRARY_FAIL;
191 log_error_state("Failed to initialize libCURL. Error code=%d", curl_rc);
195 /***************************************************************************/
196 /* Get a curl handle which we'll use for all of our output. */
197 /***************************************************************************/
198 curl_handle = curl_easy_init();
199 if (curl_handle == NULL)
201 rc = EVEL_CURL_LIBRARY_FAIL;
202 log_error_state("Failed to get libCURL handle");
206 /***************************************************************************/
207 /* Prime the library to give friendly error codes. */
208 /***************************************************************************/
209 curl_rc = curl_easy_setopt(curl_handle,
212 if (curl_rc != CURLE_OK)
214 rc = EVEL_CURL_LIBRARY_FAIL;
215 log_error_state("Failed to initialize libCURL to provide friendly errors. "
216 "Error code=%d", curl_rc);
220 /***************************************************************************/
221 /* If running in verbose mode generate more output. */
222 /***************************************************************************/
225 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
226 if (curl_rc != CURLE_OK)
228 rc = EVEL_CURL_LIBRARY_FAIL;
229 log_error_state("Failed to initialize libCURL to be verbose. "
230 "Error code=%d", curl_rc);
235 /***************************************************************************/
236 /* Set the URL for the API. */
237 /***************************************************************************/
238 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, event_api_url);
239 if (curl_rc != CURLE_OK)
241 rc = EVEL_CURL_LIBRARY_FAIL;
242 log_error_state("Failed to initialize libCURL with the API URL. "
243 "Error code=%d (%s)", curl_rc, curl_err_string);
246 EVEL_INFO("Initializing CURL to send events to: %s", event_api_url);
248 /***************************************************************************/
249 /* send all data to this function. */
250 /***************************************************************************/
251 curl_rc = curl_easy_setopt(curl_handle,
252 CURLOPT_WRITEFUNCTION,
253 evel_write_callback);
254 if (curl_rc != CURLE_OK)
256 rc = EVEL_CURL_LIBRARY_FAIL;
257 log_error_state("Failed to initialize libCURL with the write callback. "
258 "Error code=%d (%s)", curl_rc, curl_err_string);
262 /***************************************************************************/
263 /* configure local ip address if provided */
264 /* Default ip if NULL */
265 /***************************************************************************/
266 if( source_ip != NULL )
268 snprintf(local_address,sizeof(local_address),source_ip);
269 if( local_address[0] != '\0' )
271 curl_rc = curl_easy_setopt(curl_handle,
274 if (curl_rc != CURLE_OK)
276 rc = EVEL_CURL_LIBRARY_FAIL;
277 log_error_state("Failed to initialize libCURL with the local address. "
278 "Error code=%d (%s)", curl_rc, curl_err_string);
284 /***************************************************************************/
285 /* configure SSL options for HTTPS transfers */
286 /***************************************************************************/
289 if( cert_file_path != NULL )
291 curl_rc = curl_easy_setopt(curl_handle,
294 if (curl_rc != CURLE_OK)
296 rc = EVEL_CURL_LIBRARY_FAIL;
297 log_error_state("Failed to initialize libCURL with the client cert. "
298 "Error code=%d (%s)", curl_rc, curl_err_string);
303 if( key_file_path != NULL )
305 curl_rc = curl_easy_setopt(curl_handle,
308 if (curl_rc != CURLE_OK)
310 rc = EVEL_CURL_LIBRARY_FAIL;
311 log_error_state("Failed to initialize libCURL with the client key. "
312 "Error code=%d (%s)", curl_rc, curl_err_string);
317 if( ca_info != NULL )
319 curl_rc = curl_easy_setopt(curl_handle,
322 if (curl_rc != CURLE_OK)
324 rc = EVEL_CURL_LIBRARY_FAIL;
325 log_error_state("Failed to initialize libCURL with the CA cert file. "
326 "Error code=%d (%s)", curl_rc, curl_err_string);
331 if( ca_file_path != NULL )
333 curl_rc = curl_easy_setopt(curl_handle,
336 if (curl_rc != CURLE_OK)
338 rc = EVEL_CURL_LIBRARY_FAIL;
339 log_error_state("Failed to initialize libCURL with the CA cert path. "
340 "Error code=%d (%s)", curl_rc, curl_err_string);
345 curl_rc = curl_easy_setopt(curl_handle,
346 CURLOPT_SSL_VERIFYPEER,
348 if (curl_rc != CURLE_OK)
350 rc = EVEL_CURL_LIBRARY_FAIL;
351 log_error_state("Failed to initialize libCURL with SSL Server verification. "
352 "Error code=%d (%s)", curl_rc, curl_err_string);
355 curl_rc = curl_easy_setopt(curl_handle,
356 CURLOPT_SSL_VERIFYHOST,
358 if (curl_rc != CURLE_OK)
360 rc = EVEL_CURL_LIBRARY_FAIL;
361 log_error_state("Failed to initialize libCURL with Client host verification. "
362 "Error code=%d (%s)", curl_rc, curl_err_string);
370 /***************************************************************************/
371 /* some servers don't like requests that are made without a user-agent */
372 /* field, so we provide one. */
373 /***************************************************************************/
374 curl_rc = curl_easy_setopt(curl_handle,
376 "libcurl-agent/1.0");
377 if (curl_rc != CURLE_OK)
379 rc = EVEL_CURL_LIBRARY_FAIL;
380 log_error_state("Failed to initialize libCURL to upload. "
381 "Error code=%d (%s)", curl_rc, curl_err_string);
385 /***************************************************************************/
386 /* Specify that we are going to POST data. */
387 /***************************************************************************/
388 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
389 if (curl_rc != CURLE_OK)
391 rc = EVEL_CURL_LIBRARY_FAIL;
392 log_error_state("Failed to initialize libCURL to upload. "
393 "Error code=%d (%s)", curl_rc, curl_err_string);
397 /***************************************************************************/
398 /* we want to use our own read function. */
399 /***************************************************************************/
400 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, read_callback);
401 if (curl_rc != CURLE_OK)
403 rc = EVEL_CURL_LIBRARY_FAIL;
404 log_error_state("Failed to initialize libCURL to upload using read "
405 "function. Error code=%d (%s)", curl_rc, curl_err_string);
409 /***************************************************************************/
410 /* All of our events are JSON encoded. We also suppress the */
411 /* Expect: 100-continue header that we would otherwise get since it */
412 /* confuses some servers. */
414 /* @TODO: do AT&T want this behavior? */
415 /***************************************************************************/
416 hdr_chunk = curl_slist_append(hdr_chunk, "Content-type: application/json");
417 hdr_chunk = curl_slist_append(hdr_chunk, "Expect:");
419 /***************************************************************************/
420 /* set our custom set of headers. */
421 /***************************************************************************/
422 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, hdr_chunk);
423 if (curl_rc != CURLE_OK)
425 rc = EVEL_CURL_LIBRARY_FAIL;
426 log_error_state("Failed to initialize libCURL to use custom headers. "
427 "Error code=%d (%s)", curl_rc, curl_err_string);
431 /***************************************************************************/
432 /* Set the timeout for the operation. */
433 /***************************************************************************/
434 curl_rc = curl_easy_setopt(curl_handle,
437 if (curl_rc != CURLE_OK)
439 rc = EVEL_CURL_LIBRARY_FAIL;
440 log_error_state("Failed to initialize libCURL for API timeout. "
441 "Error code=%d (%s)", curl_rc, curl_err_string);
445 /***************************************************************************/
446 /* Set that we want Basic authentication with username:password Base-64 */
447 /* encoded for the operation. */
448 /***************************************************************************/
449 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
450 if (curl_rc != CURLE_OK)
452 rc = EVEL_CURL_LIBRARY_FAIL;
453 log_error_state("Failed to initialize libCURL for Basic Authentication. "
454 "Error code=%d (%s)", curl_rc, curl_err_string);
457 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_USERNAME, username);
458 if (curl_rc != CURLE_OK)
460 rc = EVEL_CURL_LIBRARY_FAIL;
461 log_error_state("Failed to initialize libCURL with username. "
462 "Error code=%d (%s)", curl_rc, curl_err_string);
465 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_PASSWORD, password);
466 if (curl_rc != CURLE_OK)
468 rc = EVEL_CURL_LIBRARY_FAIL;
469 log_error_state("Failed to initialize libCURL with password. "
470 "Error code=%d (%s)", curl_rc, curl_err_string);
474 /***************************************************************************/
475 /* Initialize a message ring-buffer to be used between the foreground and */
476 /* the thread which sends the messages. This can't fail. */
477 /***************************************************************************/
478 if( ring_buf_size < EVEL_EVENT_BUFFER_DEPTH )
480 log_error_state("Warning: Failed to initialize Ring buffer size to %d. "
484 ring_buffer_initialize(&event_buffer, EVEL_EVENT_BUFFER_DEPTH);
486 /***************************************************************************/
487 /* Initialize the priority post buffer to empty. */
488 /***************************************************************************/
489 priority_post.memory = NULL;
497 /**************************************************************************//**
498 * Run the event handler.
500 * Spawns the thread responsible for handling events and sending them to the
503 * @return Status code.
504 * @retval ::EVEL_SUCCESS if everything OK.
505 * @retval One of ::EVEL_ERR_CODES if there was a problem.
506 *****************************************************************************/
507 EVEL_ERR_CODES event_handler_run()
509 EVEL_ERR_CODES rc = EVEL_SUCCESS;
514 /***************************************************************************/
515 /* Start the event handler thread. */
516 /***************************************************************************/
517 evt_handler_state = EVT_HANDLER_INACTIVE;
518 pthread_rc = pthread_create(&evt_handler_thread, NULL, event_handler, NULL);
521 rc = EVEL_PTHREAD_LIBRARY_FAIL;
522 log_error_state("Failed to start event handler thread. "
523 "Error code=%d", pthread_rc);
530 /**************************************************************************//**
531 * Terminate the event handler.
533 * Shuts down the event handler thread in as clean a way as possible. Sets the
534 * global exit flag and then signals the thread to interrupt it since it's
535 * most likely waiting on the ring-buffer.
537 * Having achieved an orderly shutdown of the event handler thread, clean up
538 * the cURL library's resources cleanly.
540 * @return Status code.
541 * @retval ::EVEL_SUCCESS if everything OK.
542 * @retval One of ::EVEL_ERR_CODES if there was a problem.
543 *****************************************************************************/
544 EVEL_ERR_CODES event_handler_terminate()
546 EVEL_ERR_CODES rc = EVEL_SUCCESS;
549 EVENT_INTERNAL *event = NULL;
551 /***************************************************************************/
552 /* Make sure that we were initialized before trying to terminate the */
553 /* event handler thread. */
554 /***************************************************************************/
555 if (evt_handler_state != EVT_HANDLER_UNINITIALIZED)
557 /*************************************************************************/
558 /* Make sure that the event handler knows it's time to die. */
559 /*************************************************************************/
560 event = evel_new_internal_event(EVT_CMD_TERMINATE,"EVELinternal","EVELid");
563 /***********************************************************************/
564 /* We failed to get an event, but we don't bail out - we will just */
565 /* clean up what we can and continue on our way, since we're exiting */
567 /***********************************************************************/
568 EVEL_ERROR("Failed to get internal event - perform dirty exit instead!");
572 /***********************************************************************/
573 /* Post the event then wait for the Event Handler to exit. Set the */
574 /* global command, too, in case the ring-buffer is full. */
575 /***********************************************************************/
576 EVEL_DEBUG("Sending event to Event Hander to request it to exit.");
577 evt_handler_state = EVT_HANDLER_REQUEST_TERMINATE;
578 evel_post_event((EVENT_HEADER *) event);
579 pthread_join(evt_handler_thread, NULL);
580 EVEL_DEBUG("Event Handler thread has exited.");
585 EVEL_DEBUG("Event handler was not initialized, so no need to kill it");
588 /***************************************************************************/
589 /* Clean-up the cURL library. */
590 /***************************************************************************/
591 if (curl_handle != NULL)
593 curl_easy_cleanup(curl_handle);
596 if (hdr_chunk != NULL)
598 curl_slist_free_all(hdr_chunk);
602 /***************************************************************************/
603 /* Free off the stored API URL strings. */
604 /***************************************************************************/
605 if (evel_event_api_url != NULL)
607 free(evel_event_api_url);
608 evel_event_api_url = NULL;
610 if (evel_batch_api_url != NULL)
612 free(evel_batch_api_url);
613 evel_batch_api_url = NULL;
615 if (evel_throt_api_url != NULL)
617 free(evel_throt_api_url);
618 evel_throt_api_url = NULL;
625 /**************************************************************************//**
628 * @note So far as the caller is concerned, successfully posting the event
629 * relinquishes all responsibility for the event - the library will take care
630 * of freeing the event in due course.
632 * @param event The event to be posted.
634 * @returns Status code
635 * @retval EVEL_SUCCESS On success
636 * @retval "One of ::EVEL_ERR_CODES" On failure.
637 *****************************************************************************/
638 EVEL_ERR_CODES evel_post_event(EVENT_HEADER * event)
640 int rc = EVEL_SUCCESS;
644 /***************************************************************************/
645 /* Check preconditions. */
646 /***************************************************************************/
647 assert(event != NULL);
649 /***************************************************************************/
650 /* We need to make sure that we are either initializing or running */
651 /* normally before writing the event into the buffer so that we can */
652 /* guarantee that the ring-buffer empties properly on exit. */
653 /***************************************************************************/
654 if ((evt_handler_state == EVT_HANDLER_ACTIVE) ||
655 (evt_handler_state == EVT_HANDLER_INACTIVE) ||
656 (evt_handler_state == EVT_HANDLER_REQUEST_TERMINATE))
658 if (ring_buffer_write(&event_buffer, event) == 0)
660 log_error_state("Failed to write event to buffer - event dropped!");
661 rc = EVEL_EVENT_BUFFER_FULL;
662 evel_free_event(event);
667 /*************************************************************************/
668 /* System is not in active operation, so reject the event. */
669 /*************************************************************************/
670 log_error_state("Event Handler system not active - event dropped!");
671 rc = EVEL_EVENT_HANDLER_INACTIVE;
672 evel_free_event(event);
679 /**************************************************************************//**
680 * Post an event to the Vendor Event Listener API.
682 * @returns Status code
683 * @retval EVEL_SUCCESS On success
684 * @retval "One of ::EVEL_ERR_CODES" On failure.
685 *****************************************************************************/
686 static EVEL_ERR_CODES evel_post_api(char * msg, size_t size)
688 int rc = EVEL_SUCCESS;
689 CURLcode curl_rc = CURLE_OK;
690 MEMORY_CHUNK rx_chunk;
691 MEMORY_CHUNK tx_chunk;
692 int http_response_code = 0;
696 /***************************************************************************/
697 /* Create the memory chunk to be used for the response to the post. The */
698 /* will be realloced. */
699 /***************************************************************************/
700 rx_chunk.memory = malloc(1);
701 assert(rx_chunk.memory != NULL);
704 /***************************************************************************/
705 /* Create the memory chunk to be sent as the body of the post. */
706 /***************************************************************************/
707 tx_chunk.memory = msg;
708 tx_chunk.size = size;
709 EVEL_DEBUG("Sending chunk of size %d", tx_chunk.size);
711 /***************************************************************************/
712 /* Point to the data to be received. */
713 /***************************************************************************/
714 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &rx_chunk);
715 if (curl_rc != CURLE_OK)
717 rc = EVEL_CURL_LIBRARY_FAIL;
718 log_error_state("Failed to initialize libCURL to upload. "
719 "Error code=%d (%s)", curl_rc, curl_err_string);
722 EVEL_DEBUG("Initialized data to receive");
724 /***************************************************************************/
725 /* Pointer to pass to our read function */
726 /***************************************************************************/
727 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READDATA, &tx_chunk);
728 if (curl_rc != CURLE_OK)
730 rc = EVEL_CURL_LIBRARY_FAIL;
731 log_error_state("Failed to set upload data for libCURL to upload. "
732 "Error code=%d (%s)", curl_rc, curl_err_string);
735 EVEL_DEBUG("Initialized data to send");
737 /***************************************************************************/
738 /* Size of the data to transmit. */
739 /***************************************************************************/
740 curl_rc = curl_easy_setopt(curl_handle,
741 CURLOPT_POSTFIELDSIZE,
743 if (curl_rc != CURLE_OK)
745 rc = EVEL_CURL_LIBRARY_FAIL;
746 log_error_state("Failed to set length of upload data for libCURL to "
747 "upload. Error code=%d (%s)", curl_rc, curl_err_string);
750 EVEL_DEBUG("Initialized length of data to send");
752 /***************************************************************************/
753 /* Now run off and do what you've been told! */
754 /***************************************************************************/
755 curl_rc = curl_easy_perform(curl_handle);
756 if (curl_rc != CURLE_OK)
758 rc = EVEL_CURL_LIBRARY_FAIL;
759 log_error_state("Failed to transfer an event to Vendor Event Listener! "
760 "Error code=%d (%s)", curl_rc, curl_err_string);
761 EVEL_ERROR("Dropped event: %s", msg);
765 /***************************************************************************/
766 /* See what response we got - any 2XX response is good. */
767 /***************************************************************************/
768 curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code);
769 EVEL_DEBUG("HTTP response code: %d", http_response_code);
770 if ((http_response_code / 100) == 2)
772 /*************************************************************************/
773 /* If the server responded with data it may be interesting but not a */
775 /*************************************************************************/
776 if ((rx_chunk.size > 0) && (rx_chunk.memory != NULL))
778 EVEL_DEBUG("Server returned data = %d (%s)",
782 /***********************************************************************/
783 /* If this is a response to priority post, then we're not interested. */
784 /***********************************************************************/
785 if (priority_post.memory != NULL)
787 EVEL_ERROR("Ignoring priority post response");
791 evel_handle_event_response(&rx_chunk, &priority_post);
797 EVEL_ERROR("Unexpected HTTP response code: %d with data size %d (%s)",
800 rx_chunk.size > 0 ? rx_chunk.memory : "NONE");
801 EVEL_ERROR("Potentially dropped event: %s", msg);
805 free(rx_chunk.memory);
810 /**************************************************************************//**
811 * Callback function to provide data to send.
813 * Copy data into the supplied buffer, read_callback::ptr, checking size
816 * @returns Number of bytes placed into read_callback::ptr. 0 for EOF.
817 *****************************************************************************/
818 static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
821 size_t bytes_to_write = 0;
822 MEMORY_CHUNK *tx_chunk = (MEMORY_CHUNK *)userp;
826 bytes_to_write = min(size*nmemb, tx_chunk->size);
828 if (bytes_to_write > 0)
830 EVEL_DEBUG("Going to try to write %d bytes", bytes_to_write);
831 strncpy((char *)ptr, tx_chunk->memory, bytes_to_write);
832 tx_chunk->memory += bytes_to_write;
833 tx_chunk->size -= bytes_to_write;
834 rtn = bytes_to_write;
838 EVEL_DEBUG("Reached EOF");
845 /**************************************************************************//**
846 * Callback function to provide returned data.
848 * Copy data into the supplied buffer, write_callback::ptr, checking size
851 * @returns Number of bytes placed into write_callback::ptr. 0 for EOF.
852 *****************************************************************************/
853 size_t evel_write_callback(void *contents,
858 size_t realsize = size * nmemb;
859 MEMORY_CHUNK * rx_chunk = (MEMORY_CHUNK *)userp;
863 EVEL_DEBUG("Called with %d chunks of %d size = %d", nmemb, size, realsize);
864 EVEL_DEBUG("rx chunk size is %d", rx_chunk->size);
866 rx_chunk->memory = realloc(rx_chunk->memory, rx_chunk->size + realsize + 1);
867 if(rx_chunk->memory == NULL) {
869 printf("not enough memory (realloc returned NULL)\n");
873 memcpy(&(rx_chunk->memory[rx_chunk->size]), contents, realsize);
874 rx_chunk->size += realsize;
875 rx_chunk->memory[rx_chunk->size] = 0;
877 EVEL_DEBUG("Rx data: %s", rx_chunk->memory);
878 EVEL_DEBUG("Returning: %d", realsize);
884 /**************************************************************************//**
887 * Watch for messages coming on the internal queue and send them to the
890 * param[in] arg Argument - unused.
891 *****************************************************************************/
892 static void * event_handler(void * arg __attribute__ ((unused)))
895 EVENT_HEADER * msg = NULL;
896 EVENT_INTERNAL * internal_msg = NULL;
898 char json_body[EVEL_MAX_JSON_BODY];
899 int rc = EVEL_SUCCESS;
902 EVEL_INFO("Event handler thread started");
904 /***************************************************************************/
905 /* Set this thread to be cancellable immediately. */
906 /***************************************************************************/
907 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type);
909 /***************************************************************************/
910 /* Set the handler as active, defending against weird situations like */
911 /* immediately shutting down after initializing the library so the */
912 /* handler never gets started up properly. */
913 /***************************************************************************/
914 if (evt_handler_state == EVT_HANDLER_INACTIVE)
916 evt_handler_state = EVT_HANDLER_ACTIVE;
920 EVEL_ERROR("Event Handler State was not INACTIVE at start-up - "
921 "Handler will exit immediately!");
924 while (evt_handler_state == EVT_HANDLER_ACTIVE)
926 /*************************************************************************/
927 /* Wait for a message to be received. */
928 /*************************************************************************/
929 EVEL_DEBUG("Event handler getting any messages");
930 msg = ring_buffer_read(&event_buffer);
932 /*************************************************************************/
933 /* Internal events get special treatment while regular events get posted */
934 /* to the far side. */
935 /*************************************************************************/
936 if (msg->event_domain == EVEL_DOMAIN_BATCH )
938 EVEL_DEBUG("Batch event received");
940 /***********************************************************************/
941 /* Encode the event in JSON. */
942 /***********************************************************************/
943 json_size = evel_json_encode_batch_event(json_body, EVEL_MAX_JSON_BODY, msg);
945 /***************************************************************************/
946 /* Set the URL for the API. */
947 /***************************************************************************/
948 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_batch_api_url);
949 if (curl_rc != CURLE_OK)
951 rc = EVEL_CURL_LIBRARY_FAIL;
952 log_error_state("Failed to initialize libCURL with the Batch API URL. "
953 "Error code=%d (%s)", curl_rc, curl_err_string);
956 /***********************************************************************/
957 /* Send the JSON across the API. */
958 /***********************************************************************/
959 EVEL_DEBUG("Sending Batch JSON of size %d is: %s", json_size, json_body);
960 rc = evel_post_api(json_body, json_size);
961 if (rc != EVEL_SUCCESS)
963 EVEL_ERROR("Failed to transfer the data. Error code=%d", rc);
966 else if (msg->event_domain != EVEL_DOMAIN_INTERNAL )
968 EVEL_DEBUG("External event received");
970 /***********************************************************************/
971 /* Encode the event in JSON. */
972 /***********************************************************************/
973 json_size = evel_json_encode_event(json_body, EVEL_MAX_JSON_BODY, msg);
975 /***************************************************************************/
976 /* Set the URL for the API. */
977 /***************************************************************************/
978 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_event_api_url);
979 if (curl_rc != CURLE_OK)
981 rc = EVEL_CURL_LIBRARY_FAIL;
982 log_error_state("Failed to initialize libCURL with the API URL. "
983 "Error code=%d (%s)", curl_rc, curl_err_string);
986 /***********************************************************************/
987 /* Send the JSON across the API. */
988 /***********************************************************************/
989 EVEL_DEBUG("Sending JSON of size %d is: %s", json_size, json_body);
990 rc = evel_post_api(json_body, json_size);
991 if (rc != EVEL_SUCCESS)
993 EVEL_ERROR("Failed to transfer the data. Error code=%d", rc);
998 EVEL_DEBUG("Internal event received");
999 internal_msg = (EVENT_INTERNAL *) msg;
1000 assert(internal_msg->command == EVT_CMD_TERMINATE);
1001 evt_handler_state = EVT_HANDLER_TERMINATING;
1004 /*************************************************************************/
1005 /* We are responsible for freeing the memory. */
1006 /*************************************************************************/
1007 evel_free_event(msg);
1010 /*************************************************************************/
1011 /* There may be a single priority post to be sent. */
1012 /*************************************************************************/
1013 if (priority_post.memory != NULL)
1015 EVEL_DEBUG("Priority Post");
1017 /***********************************************************************/
1018 /* Set the URL for the throttling API. */
1019 /***********************************************************************/
1020 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_throt_api_url);
1021 if (curl_rc != CURLE_OK)
1023 /*********************************************************************/
1024 /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which */
1025 /* case we carry on regardless. */
1026 /*********************************************************************/
1027 EVEL_ERROR("Failed to set throttling URL. Error code=%d", rc);
1031 rc = evel_post_api(priority_post.memory, priority_post.size);
1032 if (rc != EVEL_SUCCESS)
1034 EVEL_ERROR("Failed to transfer priority post. Error code=%d", rc);
1038 /***********************************************************************/
1039 /* Reinstate the URL for the event API. */
1040 /***********************************************************************/
1041 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_event_api_url);
1042 if (curl_rc != CURLE_OK)
1044 /*********************************************************************/
1045 /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which */
1046 /* case we carry on regardless. */
1047 /*********************************************************************/
1048 EVEL_ERROR("Failed to reinstate events URL. Error code=%d", rc);
1051 /***********************************************************************/
1052 /* We are responsible for freeing the memory. */
1053 /***********************************************************************/
1054 free(priority_post.memory);
1055 priority_post.memory = NULL;
1059 /***************************************************************************/
1060 /* The event handler is now exiting. The ring-buffer could contain events */
1061 /* which have not been processed, so deplete those. Because we've been */
1062 /* asked to exit we can be confident that the foreground will have stopped */
1063 /* sending events in so we know that this process will conclude! */
1064 /***************************************************************************/
1065 evt_handler_state = EVT_HANDLER_TERMINATING;
1066 while (!ring_buffer_is_empty(&event_buffer))
1068 EVEL_DEBUG("Reading event from buffer");
1069 msg = ring_buffer_read(&event_buffer);
1070 evel_free_event(msg);
1072 evt_handler_state = EVT_HANDLER_TERMINATED;
1073 EVEL_INFO("Event handler thread stopped");
1078 /**************************************************************************//**
1079 * Handle a JSON response from the listener, contained in a ::MEMORY_CHUNK.
1081 * Tokenize the response, and decode any tokens found.
1083 * @param chunk The memory chunk containing the response.
1084 * @param post The memory chunk in which to place any resulting POST.
1085 *****************************************************************************/
1086 void evel_handle_event_response(const MEMORY_CHUNK * const chunk,
1087 MEMORY_CHUNK * const post)
1089 jsmn_parser json_parser;
1090 jsmntok_t json_tokens[EVEL_MAX_RESPONSE_TOKENS];
1095 /***************************************************************************/
1096 /* Check preconditions. */
1097 /***************************************************************************/
1098 assert(chunk != NULL);
1099 assert(priority_post.memory == NULL);
1101 EVEL_DEBUG("Response size = %d", chunk->size);
1102 EVEL_DEBUG("Response = %s", chunk->memory);
1104 /***************************************************************************/
1105 /* Initialize the parser and tokenize the response. */
1106 /***************************************************************************/
1107 jsmn_init(&json_parser);
1108 num_tokens = jsmn_parse(&json_parser,
1112 EVEL_MAX_RESPONSE_TOKENS);
1116 EVEL_ERROR("Failed to parse JSON response. "
1117 "Error code=%d", num_tokens);
1119 else if (num_tokens == 0)
1121 EVEL_DEBUG("No tokens found in JSON response");
1125 EVEL_DEBUG("Decode JSON response tokens");
1126 if (!evel_handle_response_tokens(chunk, json_tokens, num_tokens, post))
1128 EVEL_ERROR("Failed to handle JSON response.");
1135 /**************************************************************************//**
1136 * Handle a JSON response from the listener, as a list of tokens from JSMN.
1138 * @param chunk Memory chunk containing the JSON buffer.
1139 * @param json_tokens Array of tokens to handle.
1140 * @param num_tokens The number of tokens to handle.
1141 * @param post The memory chunk in which to place any resulting POST.
1142 * @return true if we handled the response, false otherwise.
1143 *****************************************************************************/
1144 bool evel_handle_response_tokens(const MEMORY_CHUNK * const chunk,
1145 const jsmntok_t * const json_tokens,
1146 const int num_tokens,
1147 MEMORY_CHUNK * const post)
1149 bool json_ok = false;
1153 /***************************************************************************/
1154 /* Check preconditions. */
1155 /***************************************************************************/
1156 assert(chunk != NULL);
1157 assert(json_tokens != NULL);
1158 assert(num_tokens < EVEL_MAX_RESPONSE_TOKENS);
1160 /***************************************************************************/
1161 /* Peek at the tokens to decide what the response it, then call the */
1162 /* appropriate handler to handle it. There is only one handler at this */
1164 /***************************************************************************/
1165 if (evel_tokens_match_command_list(chunk, json_tokens, num_tokens))
1167 json_ok = evel_handle_command_list(chunk, json_tokens, num_tokens, post);
1175 /**************************************************************************//**
1176 * Determine whether a list of tokens looks like a "commandList" response.
1178 * @param chunk Memory chunk containing the JSON buffer.
1179 * @param json_tokens Token to check.
1180 * @param num_tokens The number of tokens to handle.
1181 * @return true if the tokens look like a "commandList" match, or false.
1182 *****************************************************************************/
1183 bool evel_tokens_match_command_list(const MEMORY_CHUNK * const chunk,
1184 const jsmntok_t * const json_tokens,
1185 const int num_tokens)
1187 bool result = false;
1191 /***************************************************************************/
1192 /* Make some checks on the basic layout of the commandList. */
1193 /***************************************************************************/
1194 if ((num_tokens > 3) &&
1195 (json_tokens[0].type == JSMN_OBJECT) &&
1196 (json_tokens[1].type == JSMN_STRING) &&
1197 (json_tokens[2].type == JSMN_ARRAY) &&
1198 (evel_token_equals_string(chunk, &json_tokens[1], "commandList")))
1208 /**************************************************************************//**
1209 * Check that a string token matches a given input string.
1211 * @param chunk Memory chunk containing the JSON buffer.
1212 * @param json_token Token to check.
1213 * @param check_string String to check it against.
1214 * @return true if the strings match, or false.
1215 *****************************************************************************/
1216 bool evel_token_equals_string(const MEMORY_CHUNK * const chunk,
1217 const jsmntok_t * json_token,
1218 const char * check_string)
1220 bool result = false;
1224 const int token_length = json_token->end - json_token->start;
1225 const char * const token_string = chunk->memory + json_token->start;
1227 if (token_length == (int)strlen(check_string))
1229 result = (strncmp(token_string, check_string, token_length) == 0);