5c6ca4b375452ea4e78e810e1f858cf4d4b918a1
[demo.git] / vnfs / VES5.0 / evel / evel-library / code / evel_library / evel_event_mgr.c
1 /*************************************************************************//**
2  *
3  * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
4  *
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
10  *
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  ****************************************************************************/
18
19 /**************************************************************************//**
20  * @file
21  * Event Manager
22  *
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.
25  *
26  ****************************************************************************/
27
28 #include <string.h>
29 #include <assert.h>
30 #include <stdlib.h>
31 #include <pthread.h>
32
33 #include <curl/curl.h>
34
35 #include "evel.h"
36 #include "evel_internal.h"
37 #include "ring_buffer.h"
38 #include "evel_throttle.h"
39
40 /**************************************************************************//**
41  * How long we're prepared to wait for the API service to respond in
42  * seconds.
43  *****************************************************************************/
44 static const int EVEL_API_TIMEOUT = 5;
45
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,
53                                         const int num_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);
61
62 /**************************************************************************//**
63  * Buffers for error strings from libcurl.
64  *****************************************************************************/
65 static char curl_err_string[CURL_ERROR_SIZE] = "<NULL>";
66
67 /**************************************************************************//**
68  * Handle for the API into libcurl.
69  *****************************************************************************/
70 static CURL * curl_handle = NULL;
71
72 /**************************************************************************//**
73  * Special headers that we send.
74  *****************************************************************************/
75 static struct curl_slist * hdr_chunk = NULL;
76
77 /**************************************************************************//**
78  * Message queue for sending events to the API.
79  *****************************************************************************/
80 static ring_buffer event_buffer;
81
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;
87
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;
93
94 /**************************************************************************//**
95  * Variable to convey to the event handler thread what the foreground wants it
96  * to do.
97  *****************************************************************************/
98 static EVT_HANDLER_STATE evt_handler_state = EVT_HANDLER_UNINITIALIZED;
99
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;
106
107 /**************************************************************************//**
108  * Initialize the event handler.
109  *
110  * Primarily responsible for getting CURL ready for use.
111  *
112  * @param[in] event_api_url
113  *                      The URL where the Vendor Event Listener API is expected
114  *                      to be.
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
128  *                        logs.
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,
133                                         int secure,
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,
138                                         long verify_peer,
139                                         long verify_host,
140                                         const char * const username,
141                                         const char * const password,
142                                         int verbosity)
143 {
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];
148
149   EVEL_ENTER();
150
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);
158
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);
169
170
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);
176   }
177   else {
178      EVEL_INFO("Old Curl version.");
179   }
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)
187   {
188     rc = EVEL_CURL_LIBRARY_FAIL;
189     log_error_state("Failed to initialize libCURL. Error code=%d", curl_rc);
190     goto exit_label;
191   }
192
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)
198   {
199     rc = EVEL_CURL_LIBRARY_FAIL;
200     log_error_state("Failed to get libCURL handle");
201     goto exit_label;
202   }
203
204   /***************************************************************************/
205   /* Prime the library to give friendly error codes.                         */
206   /***************************************************************************/
207   curl_rc = curl_easy_setopt(curl_handle,
208                              CURLOPT_ERRORBUFFER,
209                              curl_err_string);
210   if (curl_rc != CURLE_OK)
211   {
212     rc = EVEL_CURL_LIBRARY_FAIL;
213     log_error_state("Failed to initialize libCURL to provide friendly errors. "
214                     "Error code=%d", curl_rc);
215     goto exit_label;
216   }
217
218   /***************************************************************************/
219   /* If running in verbose mode generate more output.                        */
220   /***************************************************************************/
221   if (verbosity > 0)
222   {
223     curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
224     if (curl_rc != CURLE_OK)
225     {
226       rc = EVEL_CURL_LIBRARY_FAIL;
227       log_error_state("Failed to initialize libCURL to be verbose. "
228                       "Error code=%d", curl_rc);
229       goto exit_label;
230     }
231   }
232
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)
238   {
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);
242     goto exit_label;
243   }
244   EVEL_INFO("Initializing CURL to send events to: %s", event_api_url);
245
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)
253   {
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);
257     goto exit_label;
258   }
259
260   /***************************************************************************/
261   /* configure local ip address if provided */
262   /* Default ip if NULL */
263   /***************************************************************************/
264   if( source_ip != NULL )
265   {
266     snprintf(local_address,sizeof(local_address),source_ip);
267     if( local_address[0] != '\0' )
268     {
269       curl_rc = curl_easy_setopt(curl_handle,
270                              CURLOPT_INTERFACE,
271                              local_address);
272       if (curl_rc != CURLE_OK)
273       {
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);
277         goto exit_label;
278       }
279     }
280   }
281
282   /***************************************************************************/
283   /* configure SSL options for HTTPS transfers */
284   /***************************************************************************/
285   if( secure )
286   {
287     if( cert_file_path != NULL )
288     {
289       curl_rc = curl_easy_setopt(curl_handle,
290                              CURLOPT_SSLCERT,
291                              cert_file_path);
292       if (curl_rc != CURLE_OK)
293       {
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);
297         goto exit_label;
298       }
299     }
300
301     if( key_file_path != NULL )
302     {
303       curl_rc = curl_easy_setopt(curl_handle,
304                              CURLOPT_SSLKEY,
305                              key_file_path);
306       if (curl_rc != CURLE_OK)
307       {
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);
311         goto exit_label;
312       }
313     }
314
315     if( ca_info != NULL )
316     {
317       curl_rc = curl_easy_setopt(curl_handle,
318                              CURLOPT_CAINFO,
319                              ca_info);
320       if (curl_rc != CURLE_OK)
321       {
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);
325         goto exit_label;
326       }
327     }
328
329     if( ca_file_path != NULL )
330     {
331       curl_rc = curl_easy_setopt(curl_handle,
332                              CURLOPT_CAPATH,
333                              ca_file_path);
334       if (curl_rc != CURLE_OK)
335       {
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);
339         goto exit_label;
340       }
341     }
342
343       curl_rc = curl_easy_setopt(curl_handle,
344                              CURLOPT_SSL_VERIFYPEER,
345                              verify_peer);
346       if (curl_rc != CURLE_OK)
347       {
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);
351         goto exit_label;
352       }
353       curl_rc = curl_easy_setopt(curl_handle,
354                              CURLOPT_SSL_VERIFYHOST,
355                              verify_host);
356       if (curl_rc != CURLE_OK)
357       {
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);
361         goto exit_label;
362       }
363
364   }
365
366
367
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,
373                              CURLOPT_USERAGENT,
374                              "libcurl-agent/1.0");
375   if (curl_rc != CURLE_OK)
376   {
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);
380     goto exit_label;
381   }
382
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)
388   {
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);
392     goto exit_label;
393   }
394
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)
400   {
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);
404     goto exit_label;
405   }
406
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.                                                  */
411   /*                                                                         */
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:");
416
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)
422   {
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);
426     goto exit_label;
427   }
428
429   /***************************************************************************/
430   /* Set the timeout for the operation.                                      */
431   /***************************************************************************/
432   curl_rc = curl_easy_setopt(curl_handle,
433                              CURLOPT_TIMEOUT,
434                              EVEL_API_TIMEOUT);
435   if (curl_rc != CURLE_OK)
436   {
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);
440     goto exit_label;
441   }
442
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)
449   {
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);
453     goto exit_label;
454   }
455   curl_rc = curl_easy_setopt(curl_handle, CURLOPT_USERNAME, username);
456   if (curl_rc != CURLE_OK)
457   {
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);
461     goto exit_label;
462   }
463   curl_rc = curl_easy_setopt(curl_handle, CURLOPT_PASSWORD, password);
464   if (curl_rc != CURLE_OK)
465   {
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);
469     goto exit_label;
470   }
471
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);
477
478   /***************************************************************************/
479   /* Initialize the priority post buffer to empty.                           */
480   /***************************************************************************/
481   priority_post.memory = NULL;
482
483 exit_label:
484   EVEL_EXIT();
485
486   return(rc);
487 }
488
489 /**************************************************************************//**
490  * Run the event handler.
491  *
492  * Spawns the thread responsible for handling events and sending them to the
493  * API.
494  *
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()
500 {
501   EVEL_ERR_CODES rc = EVEL_SUCCESS;
502   int pthread_rc = 0;
503
504   EVEL_ENTER();
505
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);
511   if (pthread_rc != 0)
512   {
513     rc = EVEL_PTHREAD_LIBRARY_FAIL;
514     log_error_state("Failed to start event handler thread. "
515                     "Error code=%d", pthread_rc);
516   }
517
518   EVEL_EXIT()
519   return rc;
520 }
521
522 /**************************************************************************//**
523  * Terminate the event handler.
524  *
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.
528  *
529  * Having achieved an orderly shutdown of the event handler thread, clean up
530  * the cURL library's resources cleanly.
531  *
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()
537 {
538   EVEL_ERR_CODES rc = EVEL_SUCCESS;
539
540   EVEL_ENTER();
541   EVENT_INTERNAL *event = NULL;
542
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)
548   {
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");
553     if (event == NULL)
554     {
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   */
558       /* anyway.                                                             */
559       /***********************************************************************/
560       EVEL_ERROR("Failed to get internal event - perform dirty exit instead!");
561     }
562     else
563     {
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.");
573     }
574   }
575   else
576   {
577     EVEL_DEBUG("Event handler was not initialized, so no need to kill it");
578   }
579
580   /***************************************************************************/
581   /* Clean-up the cURL library.                                              */
582   /***************************************************************************/
583   if (curl_handle != NULL)
584   {
585     curl_easy_cleanup(curl_handle);
586     curl_handle = NULL;
587   }
588   if (hdr_chunk != NULL)
589   {
590     curl_slist_free_all(hdr_chunk);
591     hdr_chunk = NULL;
592   }
593
594   /***************************************************************************/
595   /* Free off the stored API URL strings.                                    */
596   /***************************************************************************/
597   if (evel_event_api_url != NULL)
598   {
599     free(evel_event_api_url);
600     evel_event_api_url = NULL;
601   }
602   if (evel_batch_api_url != NULL)
603   {
604     free(evel_batch_api_url);
605     evel_batch_api_url = NULL;
606   }
607   if (evel_throt_api_url != NULL)
608   {
609     free(evel_throt_api_url);
610     evel_throt_api_url = NULL;
611   }
612
613   EVEL_EXIT();
614   return rc;
615 }
616
617 /**************************************************************************//**
618  * Post an event.
619  *
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.
623
624  * @param event   The event to be posted.
625  *
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)
631 {
632   int rc = EVEL_SUCCESS;
633
634   EVEL_ENTER();
635
636   /***************************************************************************/
637   /* Check preconditions.                                                    */
638   /***************************************************************************/
639   assert(event != NULL);
640
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))
649   {
650     if (ring_buffer_write(&event_buffer, event) == 0)
651     {
652       log_error_state("Failed to write event to buffer - event dropped!");
653       rc = EVEL_EVENT_BUFFER_FULL;
654       evel_free_event(event);
655     }
656   }
657   else
658   {
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);
665   }
666
667   EVEL_EXIT();
668   return (rc);
669 }
670
671 /**************************************************************************//**
672  * Post an event to the Vendor Event Listener API.
673  *
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)
679 {
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;
685
686   EVEL_ENTER();
687
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);
694   rx_chunk.size = 0;
695
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);
702
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)
708   {
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);
712     goto exit_label;
713   }
714   EVEL_DEBUG("Initialized data to receive");
715
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)
721   {
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);
725     goto exit_label;
726   }
727   EVEL_DEBUG("Initialized data to send");
728
729   /***************************************************************************/
730   /* Size of the data to transmit.                                           */
731   /***************************************************************************/
732   curl_rc = curl_easy_setopt(curl_handle,
733                              CURLOPT_POSTFIELDSIZE,
734                              tx_chunk.size);
735   if (curl_rc != CURLE_OK)
736   {
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);
740     goto exit_label;
741   }
742   EVEL_DEBUG("Initialized length of data to send");
743
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)
749   {
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);
754     goto exit_label;
755   }
756
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)
763   {
764     /*************************************************************************/
765     /* If the server responded with data it may be interesting but not a     */
766     /* problem.                                                              */
767     /*************************************************************************/
768     if ((rx_chunk.size > 0) && (rx_chunk.memory != NULL))
769     {
770       EVEL_DEBUG("Server returned data = %d (%s)",
771                  rx_chunk.size,
772                  rx_chunk.memory);
773
774       /***********************************************************************/
775       /* If this is a response to priority post, then we're not interested.  */
776       /***********************************************************************/
777       if (priority_post.memory != NULL)
778       {
779         EVEL_ERROR("Ignoring priority post response");
780       }
781       else
782       {
783         evel_handle_event_response(&rx_chunk, &priority_post);
784       }
785     }
786   }
787   else
788   {
789     EVEL_ERROR("Unexpected HTTP response code: %d with data size %d (%s)",
790                 http_response_code,
791                 rx_chunk.size,
792                 rx_chunk.size > 0 ? rx_chunk.memory : "NONE");
793     EVEL_ERROR("Potentially dropped event: %s", msg);
794   }
795
796 exit_label:
797   free(rx_chunk.memory);
798   EVEL_EXIT();
799   return(rc);
800 }
801
802 /**************************************************************************//**
803  * Callback function to provide data to send.
804  *
805  * Copy data into the supplied buffer, read_callback::ptr, checking size
806  * limits.
807  *
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)
811 {
812   size_t rtn = 0;
813   size_t bytes_to_write = 0;
814   MEMORY_CHUNK *tx_chunk = (MEMORY_CHUNK *)userp;
815
816   EVEL_ENTER();
817
818   bytes_to_write = min(size*nmemb, tx_chunk->size);
819
820   if (bytes_to_write > 0)
821   {
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;
827   }
828   else
829   {
830     EVEL_DEBUG("Reached EOF");
831   }
832
833   EVEL_EXIT();
834   return rtn;
835 }
836
837 /**************************************************************************//**
838  * Callback function to provide returned data.
839  *
840  * Copy data into the supplied buffer, write_callback::ptr, checking size
841  * limits.
842  *
843  * @returns   Number of bytes placed into write_callback::ptr. 0 for EOF.
844  *****************************************************************************/
845 size_t evel_write_callback(void *contents,
846                              size_t size,
847                              size_t nmemb,
848                              void *userp)
849 {
850   size_t realsize = size * nmemb;
851   MEMORY_CHUNK * rx_chunk = (MEMORY_CHUNK *)userp;
852
853   EVEL_ENTER();
854
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);
857
858   rx_chunk->memory = realloc(rx_chunk->memory, rx_chunk->size + realsize + 1);
859   if(rx_chunk->memory == NULL) {
860     /* out of memory! */
861     printf("not enough memory (realloc returned NULL)\n");
862     return 0;
863   }
864
865   memcpy(&(rx_chunk->memory[rx_chunk->size]), contents, realsize);
866   rx_chunk->size += realsize;
867   rx_chunk->memory[rx_chunk->size] = 0;
868
869   EVEL_DEBUG("Rx data: %s", rx_chunk->memory);
870   EVEL_DEBUG("Returning: %d", realsize);
871
872   EVEL_EXIT();
873   return realsize;
874 }
875
876 /**************************************************************************//**
877  * Event Handler.
878  *
879  * Watch for messages coming on the internal queue and send them to the
880  * listener.
881  *
882  * param[in]  arg  Argument - unused.
883  *****************************************************************************/
884 static void * event_handler(void * arg __attribute__ ((unused)))
885 {
886   int old_type = 0;
887   EVENT_HEADER * msg = NULL;
888   EVENT_INTERNAL * internal_msg = NULL;
889   int json_size = 0;
890   char json_body[EVEL_MAX_JSON_BODY];
891   int rc = EVEL_SUCCESS;
892   CURLcode curl_rc;
893
894   EVEL_INFO("Event handler thread started");
895
896   /***************************************************************************/
897   /* Set this thread to be cancellable immediately.                          */
898   /***************************************************************************/
899   pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type);
900
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)
907   {
908     evt_handler_state = EVT_HANDLER_ACTIVE;
909   }
910   else
911   {
912     EVEL_ERROR("Event Handler State was not INACTIVE at start-up - "
913                "Handler will exit immediately!");
914   }
915
916   while (evt_handler_state == EVT_HANDLER_ACTIVE)
917   {
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);
923
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 )
929     {
930       EVEL_DEBUG("Batch event received");
931
932       /***********************************************************************/
933       /* Encode the event in JSON.                                           */
934       /***********************************************************************/
935       json_size = evel_json_encode_batch_event(json_body, EVEL_MAX_JSON_BODY, msg);
936
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)
942       {
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);
946       }
947
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)
954       {
955         EVEL_ERROR("Failed to transfer the data. Error code=%d", rc);
956       }
957     }
958     else if (msg->event_domain != EVEL_DOMAIN_INTERNAL )
959     {
960       EVEL_DEBUG("External event received");
961
962       /***********************************************************************/
963       /* Encode the event in JSON.                                           */
964       /***********************************************************************/
965       json_size = evel_json_encode_event(json_body, EVEL_MAX_JSON_BODY, msg);
966
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)
972       {
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);
976       }
977
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)
984       {
985         EVEL_ERROR("Failed to transfer the data. Error code=%d", rc);
986       }
987     }
988     else
989     {
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;
994     }
995
996     /*************************************************************************/
997     /* We are responsible for freeing the memory.                            */
998     /*************************************************************************/
999     evel_free_event(msg);
1000     msg = NULL;
1001
1002     /*************************************************************************/
1003     /* There may be a single priority post to be sent.                       */
1004     /*************************************************************************/
1005     if (priority_post.memory != NULL)
1006     {
1007       EVEL_DEBUG("Priority Post");
1008
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)
1014       {
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);
1020       }
1021       else
1022       {
1023         rc = evel_post_api(priority_post.memory, priority_post.size);
1024         if (rc != EVEL_SUCCESS)
1025         {
1026           EVEL_ERROR("Failed to transfer priority post. Error code=%d", rc);
1027         }
1028       }
1029
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)
1035       {
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);
1041       }
1042
1043       /***********************************************************************/
1044       /* We are responsible for freeing the memory.                          */
1045       /***********************************************************************/
1046       free(priority_post.memory);
1047       priority_post.memory = NULL;
1048     }
1049   }
1050
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))
1059   {
1060     EVEL_DEBUG("Reading event from buffer");
1061     msg = ring_buffer_read(&event_buffer);
1062     evel_free_event(msg);
1063   }
1064   evt_handler_state = EVT_HANDLER_TERMINATED;
1065   EVEL_INFO("Event handler thread stopped");
1066
1067   return (NULL);
1068 }
1069
1070 /**************************************************************************//**
1071  * Handle a JSON response from the listener, contained in a ::MEMORY_CHUNK.
1072  *
1073  * Tokenize the response, and decode any tokens found.
1074  *
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)
1080 {
1081   jsmn_parser json_parser;
1082   jsmntok_t json_tokens[EVEL_MAX_RESPONSE_TOKENS];
1083   int num_tokens = 0;
1084
1085   EVEL_ENTER();
1086
1087   /***************************************************************************/
1088   /* Check preconditions.                                                    */
1089   /***************************************************************************/
1090   assert(chunk != NULL);
1091   assert(priority_post.memory == NULL);
1092
1093   EVEL_DEBUG("Response size = %d", chunk->size);
1094   EVEL_DEBUG("Response = %s", chunk->memory);
1095
1096   /***************************************************************************/
1097   /* Initialize the parser and tokenize the response.                        */
1098   /***************************************************************************/
1099   jsmn_init(&json_parser);
1100   num_tokens = jsmn_parse(&json_parser,
1101                           chunk->memory,
1102                           chunk->size,
1103                           json_tokens,
1104                           EVEL_MAX_RESPONSE_TOKENS);
1105
1106   if (num_tokens < 0)
1107   {
1108     EVEL_ERROR("Failed to parse JSON response.  "
1109                "Error code=%d", num_tokens);
1110   }
1111   else if (num_tokens == 0)
1112   {
1113     EVEL_DEBUG("No tokens found in JSON response");
1114   }
1115   else
1116   {
1117     EVEL_DEBUG("Decode JSON response tokens");
1118     if (!evel_handle_response_tokens(chunk, json_tokens, num_tokens, post))
1119     {
1120       EVEL_ERROR("Failed to handle JSON response.");
1121     }
1122   }
1123
1124   EVEL_EXIT();
1125 }
1126
1127 /**************************************************************************//**
1128  * Handle a JSON response from the listener, as a list of tokens from JSMN.
1129  *
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)
1140 {
1141   bool json_ok = false;
1142
1143   EVEL_ENTER();
1144
1145   /***************************************************************************/
1146   /* Check preconditions.                                                    */
1147   /***************************************************************************/
1148   assert(chunk != NULL);
1149   assert(json_tokens != NULL);
1150   assert(num_tokens < EVEL_MAX_RESPONSE_TOKENS);
1151
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    */
1155   /* point.                                                                  */
1156   /***************************************************************************/
1157   if (evel_tokens_match_command_list(chunk, json_tokens, num_tokens))
1158   {
1159     json_ok = evel_handle_command_list(chunk, json_tokens, num_tokens, post);
1160   }
1161
1162   EVEL_EXIT();
1163
1164   return json_ok;
1165 }
1166
1167 /**************************************************************************//**
1168  * Determine whether a list of tokens looks like a "commandList" response.
1169  *
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)
1178 {
1179   bool result = false;
1180
1181   EVEL_ENTER();
1182
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")))
1191   {
1192     result = true;
1193   }
1194
1195   EVEL_EXIT();
1196
1197   return result;
1198 }
1199
1200 /**************************************************************************//**
1201  * Check that a string token matches a given input string.
1202  *
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)
1211 {
1212   bool result = false;
1213
1214   EVEL_ENTER();
1215
1216   const int token_length = json_token->end - json_token->start;
1217   const char * const token_string = chunk->memory + json_token->start;
1218
1219   if (token_length == (int)strlen(check_string))
1220   {
1221     result = (strncmp(token_string, check_string, token_length) == 0);
1222   }
1223
1224   EVEL_EXIT();
1225
1226   return result;
1227 }