Modified hardcoded eventname id to user defined
[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  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and 
14  * limitations under the License.
15  *
16  ****************************************************************************/
17
18 /**************************************************************************//**
19  * @file
20  * Event Manager
21  *
22  * Simple event manager that is responsible for taking events (Heartbeats,
23  * Faults and Measurements) from the ring-buffer and posting them to the API.
24  *
25  ****************************************************************************/
26
27 #include <string.h>
28 #include <assert.h>
29 #include <stdlib.h>
30 #include <pthread.h>
31
32 #include <curl/curl.h>
33
34 #include "evel.h"
35 #include "evel_internal.h"
36 #include "ring_buffer.h"
37 #include "evel_throttle.h"
38
39 /**************************************************************************//**
40  * How long we're prepared to wait for the API service to respond in
41  * seconds.
42  *****************************************************************************/
43 static const int EVEL_API_TIMEOUT = 5;
44
45 /*****************************************************************************/
46 /* Prototypes of locally scoped functions.                                   */
47 /*****************************************************************************/
48 static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp);
49 static void * event_handler(void *arg);
50 static bool evel_handle_response_tokens(const MEMORY_CHUNK * const chunk,
51                                         const jsmntok_t * const json_tokens,
52                                         const int num_tokens,
53                                         MEMORY_CHUNK * const post);
54 static bool evel_tokens_match_command_list(const MEMORY_CHUNK * const chunk,
55                                            const jsmntok_t * const json_token,
56                                            const int num_tokens);
57 static bool evel_token_equals_string(const MEMORY_CHUNK * const chunk,
58                                      const jsmntok_t * const json_token,
59                                      const char * check_string);
60
61 /**************************************************************************//**
62  * Buffers for error strings from libcurl.
63  *****************************************************************************/
64 static char curl_err_string[CURL_ERROR_SIZE] = "<NULL>";
65
66 /**************************************************************************//**
67  * Handle for the API into libcurl.
68  *****************************************************************************/
69 static CURL * curl_handle = NULL;
70
71 /**************************************************************************//**
72  * Special headers that we send.
73  *****************************************************************************/
74 static struct curl_slist * hdr_chunk = NULL;
75
76 /**************************************************************************//**
77  * Message queue for sending events to the API.
78  *****************************************************************************/
79 static ring_buffer event_buffer;
80
81 /**************************************************************************//**
82  * Single pending priority post, which can be generated as a result of a
83  * response to an event.  Currently only used to respond to a commandList.
84  *****************************************************************************/
85 static MEMORY_CHUNK priority_post;
86
87 /**************************************************************************//**
88  * The thread which is responsible for handling events off of the ring-buffer
89  * and posting them to the Event Handler API.
90  *****************************************************************************/
91 static pthread_t evt_handler_thread;
92
93 /**************************************************************************//**
94  * Variable to convey to the event handler thread what the foreground wants it
95  * to do.
96  *****************************************************************************/
97 static EVT_HANDLER_STATE evt_handler_state = EVT_HANDLER_UNINITIALIZED;
98
99 /**************************************************************************//**
100  * The configured API URL for event and throttling.
101  *****************************************************************************/
102 static char * evel_event_api_url;
103 static char * evel_throt_api_url;
104
105 /**************************************************************************//**
106  * Initialize the event handler.
107  *
108  * Primarily responsible for getting CURL ready for use.
109  *
110  * @param[in] event_api_url
111  *                      The URL where the Vendor Event Listener API is expected
112  *                      to be.
113  * @param[in] throt_api_url
114  *                      The URL where the Throttling API is expected to be.
115  * @param[in] username  The username for the Basic Authentication of requests.
116  * @param[in] password  The password for the Basic Authentication of requests.
117  * @param     verbosity 0 for normal operation, positive values for chattier
118  *                        logs.
119  *****************************************************************************/
120 EVEL_ERR_CODES event_handler_initialize(const char * const event_api_url,
121                                         const char * const throt_api_url,
122                                         const char * const username,
123                                         const char * const password,
124                                         int verbosity)
125 {
126   int rc = EVEL_SUCCESS;
127   CURLcode curl_rc = CURLE_OK;
128
129   EVEL_ENTER();
130
131   /***************************************************************************/
132   /* Check assumptions.                                                      */
133   /***************************************************************************/
134   assert(event_api_url != NULL);
135   assert(throt_api_url != NULL);
136   assert(username != NULL);
137   assert(password != NULL);
138
139   /***************************************************************************/
140   /* Store the API URLs.                                                     */
141   /***************************************************************************/
142   evel_event_api_url = strdup(event_api_url);
143   assert(evel_event_api_url != NULL);
144   evel_throt_api_url = strdup(throt_api_url);
145   assert(evel_throt_api_url != NULL);
146
147
148   curl_version_info_data *d = curl_version_info(CURLVERSION_NOW);
149   /* compare with the 24 bit hex number in 8 bit fields */
150   if(d->version_num >= 0x072100) {
151      /* this is libcurl 7.33.0 or later */
152      EVEL_INFO("7.33 or later Curl version %x.",d->version_num);
153   }
154   else {
155      EVEL_INFO("Old Curl version.");
156   }
157   /***************************************************************************/
158   /* Start the CURL library. Note that this initialization is not threadsafe */
159   /* which imposes a constraint that the EVEL library is initialized before  */
160   /* any threads are started.                                                */
161   /***************************************************************************/
162   curl_rc = curl_global_init(CURL_GLOBAL_SSL);
163   if (curl_rc != CURLE_OK)
164   {
165     rc = EVEL_CURL_LIBRARY_FAIL;
166     log_error_state("Failed to initialize libCURL. Error code=%d", curl_rc);
167     goto exit_label;
168   }
169
170   /***************************************************************************/
171   /* Get a curl handle which we'll use for all of our output.                */
172   /***************************************************************************/
173   curl_handle = curl_easy_init();
174   if (curl_handle == NULL)
175   {
176     rc = EVEL_CURL_LIBRARY_FAIL;
177     log_error_state("Failed to get libCURL handle");
178     goto exit_label;
179   }
180
181   /***************************************************************************/
182   /* Prime the library to give friendly error codes.                         */
183   /***************************************************************************/
184   curl_rc = curl_easy_setopt(curl_handle,
185                              CURLOPT_ERRORBUFFER,
186                              curl_err_string);
187   if (curl_rc != CURLE_OK)
188   {
189     rc = EVEL_CURL_LIBRARY_FAIL;
190     log_error_state("Failed to initialize libCURL to provide friendly errors. "
191                     "Error code=%d", curl_rc);
192     goto exit_label;
193   }
194
195   /***************************************************************************/
196   /* If running in verbose mode generate more output.                        */
197   /***************************************************************************/
198   if (verbosity > 0)
199   {
200     curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
201     if (curl_rc != CURLE_OK)
202     {
203       rc = EVEL_CURL_LIBRARY_FAIL;
204       log_error_state("Failed to initialize libCURL to be verbose. "
205                       "Error code=%d", curl_rc);
206       goto exit_label;
207     }
208   }
209
210   /***************************************************************************/
211   /* Set the URL for the API.                                                */
212   /***************************************************************************/
213   curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, event_api_url);
214   if (curl_rc != CURLE_OK)
215   {
216     rc = EVEL_CURL_LIBRARY_FAIL;
217     log_error_state("Failed to initialize libCURL with the API URL. "
218                     "Error code=%d (%s)", curl_rc, curl_err_string);
219     goto exit_label;
220   }
221   EVEL_INFO("Initializing CURL to send events to: %s", event_api_url);
222
223   /***************************************************************************/
224   /* send all data to this function.                                         */
225   /***************************************************************************/
226   curl_rc = curl_easy_setopt(curl_handle,
227                              CURLOPT_WRITEFUNCTION,
228                              evel_write_callback);
229   if (curl_rc != CURLE_OK)
230   {
231     rc = EVEL_CURL_LIBRARY_FAIL;
232     log_error_state("Failed to initialize libCURL with the write callback. "
233                     "Error code=%d (%s)", curl_rc, curl_err_string);
234     goto exit_label;
235   }
236
237   /***************************************************************************/
238   /* some servers don't like requests that are made without a user-agent     */
239   /* field, so we provide one.                                               */
240   /***************************************************************************/
241   curl_rc = curl_easy_setopt(curl_handle,
242                              CURLOPT_USERAGENT,
243                              "libcurl-agent/1.0");
244   if (curl_rc != CURLE_OK)
245   {
246     rc = EVEL_CURL_LIBRARY_FAIL;
247     log_error_state("Failed to initialize libCURL to upload. "
248                     "Error code=%d (%s)", curl_rc, curl_err_string);
249     goto exit_label;
250   }
251
252   /***************************************************************************/
253   /* Specify that we are going to POST data.                                 */
254   /***************************************************************************/
255   curl_rc = curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
256   if (curl_rc != CURLE_OK)
257   {
258     rc = EVEL_CURL_LIBRARY_FAIL;
259     log_error_state("Failed to initialize libCURL to upload. "
260                     "Error code=%d (%s)", curl_rc, curl_err_string);
261     goto exit_label;
262   }
263
264   /***************************************************************************/
265   /* we want to use our own read function.                                   */
266   /***************************************************************************/
267   curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, read_callback);
268   if (curl_rc != CURLE_OK)
269   {
270     rc = EVEL_CURL_LIBRARY_FAIL;
271     log_error_state("Failed to initialize libCURL to upload using read "
272                     "function. Error code=%d (%s)", curl_rc, curl_err_string);
273     goto exit_label;
274   }
275
276   /***************************************************************************/
277   /* All of our events are JSON encoded.  We also suppress the               */
278   /* Expect: 100-continue   header that we would otherwise get since it      */
279   /* confuses some servers.                                                  */
280   /*                                                                         */
281   /* @TODO: do AT&T want this behavior?                                      */
282   /***************************************************************************/
283   hdr_chunk = curl_slist_append(hdr_chunk, "Content-type: application/json");
284   hdr_chunk = curl_slist_append(hdr_chunk, "Expect:");
285
286   /***************************************************************************/
287   /* set our custom set of headers.                                         */
288   /***************************************************************************/
289   curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, hdr_chunk);
290   if (curl_rc != CURLE_OK)
291   {
292     rc = EVEL_CURL_LIBRARY_FAIL;
293     log_error_state("Failed to initialize libCURL to use custom headers. "
294                     "Error code=%d (%s)", curl_rc, curl_err_string);
295     goto exit_label;
296   }
297
298   /***************************************************************************/
299   /* Set the timeout for the operation.                                      */
300   /***************************************************************************/
301   curl_rc = curl_easy_setopt(curl_handle,
302                              CURLOPT_TIMEOUT,
303                              EVEL_API_TIMEOUT);
304   if (curl_rc != CURLE_OK)
305   {
306     rc = EVEL_CURL_LIBRARY_FAIL;
307     log_error_state("Failed to initialize libCURL for API timeout. "
308                     "Error code=%d (%s)", curl_rc, curl_err_string);
309     goto exit_label;
310   }
311
312   /***************************************************************************/
313   /* Set that we want Basic authentication with username:password Base-64    */
314   /* encoded for the operation.                                              */
315   /***************************************************************************/
316   curl_rc = curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
317   if (curl_rc != CURLE_OK)
318   {
319     rc = EVEL_CURL_LIBRARY_FAIL;
320     log_error_state("Failed to initialize libCURL for Basic Authentication. "
321                     "Error code=%d (%s)", curl_rc, curl_err_string);
322     goto exit_label;
323   }
324   curl_rc = curl_easy_setopt(curl_handle, CURLOPT_USERNAME, username);
325   if (curl_rc != CURLE_OK)
326   {
327     rc = EVEL_CURL_LIBRARY_FAIL;
328     log_error_state("Failed to initialize libCURL with username. "
329                     "Error code=%d (%s)", curl_rc, curl_err_string);
330     goto exit_label;
331   }
332   curl_rc = curl_easy_setopt(curl_handle, CURLOPT_PASSWORD, password);
333   if (curl_rc != CURLE_OK)
334   {
335     rc = EVEL_CURL_LIBRARY_FAIL;
336     log_error_state("Failed to initialize libCURL with password. "
337                     "Error code=%d (%s)", curl_rc, curl_err_string);
338     goto exit_label;
339   }
340
341   /***************************************************************************/
342   /* Initialize a message ring-buffer to be used between the foreground and  */
343   /* the thread which sends the messages.  This can't fail.                  */
344   /***************************************************************************/
345   ring_buffer_initialize(&event_buffer, EVEL_EVENT_BUFFER_DEPTH);
346
347   /***************************************************************************/
348   /* Initialize the priority post buffer to empty.                           */
349   /***************************************************************************/
350   priority_post.memory = NULL;
351
352 exit_label:
353   EVEL_EXIT();
354
355   return(rc);
356 }
357
358 /**************************************************************************//**
359  * Run the event handler.
360  *
361  * Spawns the thread responsible for handling events and sending them to the
362  * API.
363  *
364  *  @return Status code.
365  *  @retval ::EVEL_SUCCESS if everything OK.
366  *  @retval One of ::EVEL_ERR_CODES if there was a problem.
367  *****************************************************************************/
368 EVEL_ERR_CODES event_handler_run()
369 {
370   EVEL_ERR_CODES rc = EVEL_SUCCESS;
371   int pthread_rc = 0;
372
373   EVEL_ENTER();
374
375   /***************************************************************************/
376   /* Start the event handler thread.                                         */
377   /***************************************************************************/
378   evt_handler_state = EVT_HANDLER_INACTIVE;
379   pthread_rc = pthread_create(&evt_handler_thread, NULL, event_handler, NULL);
380   if (pthread_rc != 0)
381   {
382     rc = EVEL_PTHREAD_LIBRARY_FAIL;
383     log_error_state("Failed to start event handler thread. "
384                     "Error code=%d", pthread_rc);
385   }
386
387   EVEL_EXIT()
388   return rc;
389 }
390
391 /**************************************************************************//**
392  * Terminate the event handler.
393  *
394  * Shuts down the event handler thread in as clean a way as possible. Sets the
395  * global exit flag and then signals the thread to interrupt it since it's
396  * most likely waiting on the ring-buffer.
397  *
398  * Having achieved an orderly shutdown of the event handler thread, clean up
399  * the cURL library's resources cleanly.
400  *
401  *  @return Status code.
402  *  @retval ::EVEL_SUCCESS if everything OK.
403  *  @retval One of ::EVEL_ERR_CODES if there was a problem.
404  *****************************************************************************/
405 EVEL_ERR_CODES event_handler_terminate()
406 {
407   EVEL_ERR_CODES rc = EVEL_SUCCESS;
408
409   EVEL_ENTER();
410   EVENT_INTERNAL *event = NULL;
411
412   /***************************************************************************/
413   /* Make sure that we were initialized before trying to terminate the       */
414   /* event handler thread.                                                   */
415   /***************************************************************************/
416   if (evt_handler_state != EVT_HANDLER_UNINITIALIZED)
417   {
418     /*************************************************************************/
419     /* Make sure that the event handler knows it's time to die.              */
420     /*************************************************************************/
421     event = evel_new_internal_event(EVT_CMD_TERMINATE,"EVELinternal","EVELid");
422     if (event == NULL)
423     {
424       /***********************************************************************/
425       /* We failed to get an event, but we don't bail out - we will just     */
426       /* clean up what we can and continue on our way, since we're exiting   */
427       /* anyway.                                                             */
428       /***********************************************************************/
429       EVEL_ERROR("Failed to get internal event - perform dirty exit instead!");
430     }
431     else
432     {
433       /***********************************************************************/
434       /* Post the event then wait for the Event Handler to exit.  Set the    */
435       /* global command, too, in case the ring-buffer is full.               */
436       /***********************************************************************/
437       EVEL_DEBUG("Sending event to Event Hander to request it to exit.");
438       evt_handler_state = EVT_HANDLER_REQUEST_TERMINATE;
439       evel_post_event((EVENT_HEADER *) event);
440       pthread_join(evt_handler_thread, NULL);
441       EVEL_DEBUG("Event Handler thread has exited.");
442     }
443   }
444   else
445   {
446     EVEL_DEBUG("Event handler was not initialized, so no need to kill it");
447   }
448
449   /***************************************************************************/
450   /* Clean-up the cURL library.                                              */
451   /***************************************************************************/
452   if (curl_handle != NULL)
453   {
454     curl_easy_cleanup(curl_handle);
455     curl_handle = NULL;
456   }
457   if (hdr_chunk != NULL)
458   {
459     curl_slist_free_all(hdr_chunk);
460     hdr_chunk = NULL;
461   }
462
463   /***************************************************************************/
464   /* Free off the stored API URL strings.                                    */
465   /***************************************************************************/
466   if (evel_event_api_url != NULL)
467   {
468     free(evel_event_api_url);
469     evel_event_api_url = NULL;
470   }
471   if (evel_throt_api_url != NULL)
472   {
473     free(evel_throt_api_url);
474     evel_throt_api_url = NULL;
475   }
476
477   EVEL_EXIT();
478   return rc;
479 }
480
481 /**************************************************************************//**
482  * Post an event.
483  *
484  * @note  So far as the caller is concerned, successfully posting the event
485  * relinquishes all responsibility for the event - the library will take care
486  * of freeing the event in due course.
487
488  * @param event   The event to be posted.
489  *
490  * @returns Status code
491  * @retval  EVEL_SUCCESS On success
492  * @retval  "One of ::EVEL_ERR_CODES" On failure.
493  *****************************************************************************/
494 EVEL_ERR_CODES evel_post_event(EVENT_HEADER * event)
495 {
496   int rc = EVEL_SUCCESS;
497
498   EVEL_ENTER();
499
500   /***************************************************************************/
501   /* Check preconditions.                                                    */
502   /***************************************************************************/
503   assert(event != NULL);
504
505   /***************************************************************************/
506   /* We need to make sure that we are either initializing or running         */
507   /* normally before writing the event into the buffer so that we can        */
508   /* guarantee that the ring-buffer empties  properly on exit.               */
509   /***************************************************************************/
510   if ((evt_handler_state == EVT_HANDLER_ACTIVE) ||
511       (evt_handler_state == EVT_HANDLER_INACTIVE) ||
512       (evt_handler_state == EVT_HANDLER_REQUEST_TERMINATE))
513   {
514     if (ring_buffer_write(&event_buffer, event) == 0)
515     {
516       log_error_state("Failed to write event to buffer - event dropped!");
517       rc = EVEL_EVENT_BUFFER_FULL;
518       evel_free_event(event);
519     }
520   }
521   else
522   {
523     /*************************************************************************/
524     /* System is not in active operation, so reject the event.               */
525     /*************************************************************************/
526     log_error_state("Event Handler system not active - event dropped!");
527     rc = EVEL_EVENT_HANDLER_INACTIVE;
528     evel_free_event(event);
529   }
530
531   EVEL_EXIT();
532   return (rc);
533 }
534
535 /**************************************************************************//**
536  * Post an event to the Vendor Event Listener API.
537  *
538  * @returns Status code
539  * @retval  EVEL_SUCCESS On success
540  * @retval  "One of ::EVEL_ERR_CODES" On failure.
541  *****************************************************************************/
542 static EVEL_ERR_CODES evel_post_api(char * msg, size_t size)
543 {
544   int rc = EVEL_SUCCESS;
545   CURLcode curl_rc = CURLE_OK;
546   MEMORY_CHUNK rx_chunk;
547   MEMORY_CHUNK tx_chunk;
548   int http_response_code = 0;
549
550   EVEL_ENTER();
551
552   /***************************************************************************/
553   /* Create the memory chunk to be used for the response to the post.  The   */
554   /* will be realloced.                                                      */
555   /***************************************************************************/
556   rx_chunk.memory = malloc(1);
557   assert(rx_chunk.memory != NULL);
558   rx_chunk.size = 0;
559
560   /***************************************************************************/
561   /* Create the memory chunk to be sent as the body of the post.             */
562   /***************************************************************************/
563   tx_chunk.memory = msg;
564   tx_chunk.size = size;
565   EVEL_DEBUG("Sending chunk of size %d", tx_chunk.size);
566
567   /***************************************************************************/
568   /* Point to the data to be received.                                       */
569   /***************************************************************************/
570   curl_rc = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &rx_chunk);
571   if (curl_rc != CURLE_OK)
572   {
573     rc = EVEL_CURL_LIBRARY_FAIL;
574     log_error_state("Failed to initialize libCURL to upload. "
575                     "Error code=%d (%s)", curl_rc, curl_err_string);
576     goto exit_label;
577   }
578   EVEL_DEBUG("Initialized data to receive");
579
580   /***************************************************************************/
581   /* Pointer to pass to our read function                                    */
582   /***************************************************************************/
583   curl_rc = curl_easy_setopt(curl_handle, CURLOPT_READDATA, &tx_chunk);
584   if (curl_rc != CURLE_OK)
585   {
586     rc = EVEL_CURL_LIBRARY_FAIL;
587     log_error_state("Failed to set upload data for libCURL to upload. "
588                     "Error code=%d (%s)", curl_rc, curl_err_string);
589     goto exit_label;
590   }
591   EVEL_DEBUG("Initialized data to send");
592
593   /***************************************************************************/
594   /* Size of the data to transmit.                                           */
595   /***************************************************************************/
596   curl_rc = curl_easy_setopt(curl_handle,
597                              CURLOPT_POSTFIELDSIZE,
598                              tx_chunk.size);
599   if (curl_rc != CURLE_OK)
600   {
601     rc = EVEL_CURL_LIBRARY_FAIL;
602     log_error_state("Failed to set length of upload data for libCURL to "
603                     "upload.  Error code=%d (%s)", curl_rc, curl_err_string);
604     goto exit_label;
605   }
606   EVEL_DEBUG("Initialized length of data to send");
607
608   /***************************************************************************/
609   /* Now run off and do what you've been told!                               */
610   /***************************************************************************/
611   curl_rc = curl_easy_perform(curl_handle);
612   if (curl_rc != CURLE_OK)
613   {
614     rc = EVEL_CURL_LIBRARY_FAIL;
615     log_error_state("Failed to transfer an event to Vendor Event Listener! "
616                     "Error code=%d (%s)", curl_rc, curl_err_string);
617     EVEL_ERROR("Dropped event: %s", msg);
618     goto exit_label;
619   }
620
621   /***************************************************************************/
622   /* See what response we got - any 2XX response is good.                    */
623   /***************************************************************************/
624   curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code);
625   EVEL_DEBUG("HTTP response code: %d", http_response_code);
626   if ((http_response_code / 100) == 2)
627   {
628     /*************************************************************************/
629     /* If the server responded with data it may be interesting but not a     */
630     /* problem.                                                              */
631     /*************************************************************************/
632     if ((rx_chunk.size > 0) && (rx_chunk.memory != NULL))
633     {
634       EVEL_DEBUG("Server returned data = %d (%s)",
635                  rx_chunk.size,
636                  rx_chunk.memory);
637
638       /***********************************************************************/
639       /* If this is a response to priority post, then we're not interested.  */
640       /***********************************************************************/
641       if (priority_post.memory != NULL)
642       {
643         EVEL_ERROR("Ignoring priority post response");
644       }
645       else
646       {
647         evel_handle_event_response(&rx_chunk, &priority_post);
648       }
649     }
650   }
651   else
652   {
653     EVEL_ERROR("Unexpected HTTP response code: %d with data size %d (%s)",
654                 http_response_code,
655                 rx_chunk.size,
656                 rx_chunk.size > 0 ? rx_chunk.memory : "NONE");
657     EVEL_ERROR("Potentially dropped event: %s", msg);
658   }
659
660 exit_label:
661   free(rx_chunk.memory);
662   EVEL_EXIT();
663   return(rc);
664 }
665
666 /**************************************************************************//**
667  * Callback function to provide data to send.
668  *
669  * Copy data into the supplied buffer, read_callback::ptr, checking size
670  * limits.
671  *
672  * @returns   Number of bytes placed into read_callback::ptr. 0 for EOF.
673  *****************************************************************************/
674 static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
675 {
676   size_t rtn = 0;
677   size_t bytes_to_write = 0;
678   MEMORY_CHUNK *tx_chunk = (MEMORY_CHUNK *)userp;
679
680   EVEL_ENTER();
681
682   bytes_to_write = min(size*nmemb, tx_chunk->size);
683
684   if (bytes_to_write > 0)
685   {
686     EVEL_DEBUG("Going to try to write %d bytes", bytes_to_write);
687     strncpy((char *)ptr, tx_chunk->memory, bytes_to_write);
688     tx_chunk->memory += bytes_to_write;
689     tx_chunk->size -= bytes_to_write;
690     rtn = bytes_to_write;
691   }
692   else
693   {
694     EVEL_DEBUG("Reached EOF");
695   }
696
697   EVEL_EXIT();
698   return rtn;
699 }
700
701 /**************************************************************************//**
702  * Callback function to provide returned data.
703  *
704  * Copy data into the supplied buffer, write_callback::ptr, checking size
705  * limits.
706  *
707  * @returns   Number of bytes placed into write_callback::ptr. 0 for EOF.
708  *****************************************************************************/
709 size_t evel_write_callback(void *contents,
710                              size_t size,
711                              size_t nmemb,
712                              void *userp)
713 {
714   size_t realsize = size * nmemb;
715   MEMORY_CHUNK * rx_chunk = (MEMORY_CHUNK *)userp;
716
717   EVEL_ENTER();
718
719   EVEL_DEBUG("Called with %d chunks of %d size = %d", nmemb, size, realsize);
720   EVEL_DEBUG("rx chunk size is %d", rx_chunk->size);
721
722   rx_chunk->memory = realloc(rx_chunk->memory, rx_chunk->size + realsize + 1);
723   if(rx_chunk->memory == NULL) {
724     /* out of memory! */
725     printf("not enough memory (realloc returned NULL)\n");
726     return 0;
727   }
728
729   memcpy(&(rx_chunk->memory[rx_chunk->size]), contents, realsize);
730   rx_chunk->size += realsize;
731   rx_chunk->memory[rx_chunk->size] = 0;
732
733   EVEL_DEBUG("Rx data: %s", rx_chunk->memory);
734   EVEL_DEBUG("Returning: %d", realsize);
735
736   EVEL_EXIT();
737   return realsize;
738 }
739
740 /**************************************************************************//**
741  * Event Handler.
742  *
743  * Watch for messages coming on the internal queue and send them to the
744  * listener.
745  *
746  * param[in]  arg  Argument - unused.
747  *****************************************************************************/
748 static void * event_handler(void * arg __attribute__ ((unused)))
749 {
750   int old_type = 0;
751   EVENT_HEADER * msg = NULL;
752   EVENT_INTERNAL * internal_msg = NULL;
753   int json_size = 0;
754   char json_body[EVEL_MAX_JSON_BODY];
755   int rc = EVEL_SUCCESS;
756   CURLcode curl_rc;
757
758   EVEL_INFO("Event handler thread started");
759
760   /***************************************************************************/
761   /* Set this thread to be cancellable immediately.                          */
762   /***************************************************************************/
763   pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type);
764
765   /***************************************************************************/
766   /* Set the handler as active, defending against weird situations like      */
767   /* immediately shutting down after initializing the library so the         */
768   /* handler never gets started up properly.                                 */
769   /***************************************************************************/
770   if (evt_handler_state == EVT_HANDLER_INACTIVE)
771   {
772     evt_handler_state = EVT_HANDLER_ACTIVE;
773   }
774   else
775   {
776     EVEL_ERROR("Event Handler State was not INACTIVE at start-up - "
777                "Handler will exit immediately!");
778   }
779
780   while (evt_handler_state == EVT_HANDLER_ACTIVE)
781   {
782     /*************************************************************************/
783     /* Wait for a message to be received.                                    */
784     /*************************************************************************/
785     EVEL_DEBUG("Event handler getting any messages");
786     msg = ring_buffer_read(&event_buffer);
787
788     /*************************************************************************/
789     /* Internal events get special treatment while regular events get posted */
790     /* to the far side.                                                      */
791     /*************************************************************************/
792     if (msg->event_domain != EVEL_DOMAIN_INTERNAL)
793     {
794       EVEL_DEBUG("External event received");
795
796       /***********************************************************************/
797       /* Encode the event in JSON.                                           */
798       /***********************************************************************/
799       json_size = evel_json_encode_event(json_body, EVEL_MAX_JSON_BODY, msg);
800
801       /***********************************************************************/
802       /* Send the JSON across the API.                                       */
803       /***********************************************************************/
804       EVEL_DEBUG("Sending JSON of size %d is: %s", json_size, json_body);
805       rc = evel_post_api(json_body, json_size);
806       if (rc != EVEL_SUCCESS)
807       {
808         EVEL_ERROR("Failed to transfer the data. Error code=%d", rc);
809       }
810     }
811     else
812     {
813       EVEL_DEBUG("Internal event received");
814       internal_msg = (EVENT_INTERNAL *) msg;
815       assert(internal_msg->command == EVT_CMD_TERMINATE);
816       evt_handler_state = EVT_HANDLER_TERMINATING;
817     }
818
819     /*************************************************************************/
820     /* We are responsible for freeing the memory.                            */
821     /*************************************************************************/
822     evel_free_event(msg);
823     msg = NULL;
824
825     /*************************************************************************/
826     /* There may be a single priority post to be sent.                       */
827     /*************************************************************************/
828     if (priority_post.memory != NULL)
829     {
830       EVEL_DEBUG("Priority Post");
831
832       /***********************************************************************/
833       /* Set the URL for the throttling API.                                 */
834       /***********************************************************************/
835       curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_throt_api_url);
836       if (curl_rc != CURLE_OK)
837       {
838         /*********************************************************************/
839         /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which  */
840         /* case we carry on regardless.                                      */
841         /*********************************************************************/
842         EVEL_ERROR("Failed to set throttling URL. Error code=%d", rc);
843       }
844       else
845       {
846         rc = evel_post_api(priority_post.memory, priority_post.size);
847         if (rc != EVEL_SUCCESS)
848         {
849           EVEL_ERROR("Failed to transfer priority post. Error code=%d", rc);
850         }
851       }
852
853       /***********************************************************************/
854       /* Reinstate the URL for the event API.                                */
855       /***********************************************************************/
856       curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, evel_event_api_url);
857       if (curl_rc != CURLE_OK)
858       {
859         /*********************************************************************/
860         /* This is only likely to happen with CURLE_OUT_OF_MEMORY, in which  */
861         /* case we carry on regardless.                                      */
862         /*********************************************************************/
863         EVEL_ERROR("Failed to reinstate events URL. Error code=%d", rc);
864       }
865
866       /***********************************************************************/
867       /* We are responsible for freeing the memory.                          */
868       /***********************************************************************/
869       free(priority_post.memory);
870       priority_post.memory = NULL;
871     }
872   }
873
874   /***************************************************************************/
875   /* The event handler is now exiting. The ring-buffer could contain events  */
876   /* which have not been processed, so deplete those.  Because we've been    */
877   /* asked to exit we can be confident that the foreground will have stopped */
878   /* sending events in so we know that this process will conclude!           */
879   /***************************************************************************/
880   evt_handler_state = EVT_HANDLER_TERMINATING;
881   while (!ring_buffer_is_empty(&event_buffer))
882   {
883     EVEL_DEBUG("Reading event from buffer");
884     msg = ring_buffer_read(&event_buffer);
885     evel_free_event(msg);
886   }
887   evt_handler_state = EVT_HANDLER_TERMINATED;
888   EVEL_INFO("Event handler thread stopped");
889
890   return (NULL);
891 }
892
893 /**************************************************************************//**
894  * Handle a JSON response from the listener, contained in a ::MEMORY_CHUNK.
895  *
896  * Tokenize the response, and decode any tokens found.
897  *
898  * @param chunk         The memory chunk containing the response.
899  * @param post          The memory chunk in which to place any resulting POST.
900  *****************************************************************************/
901 void evel_handle_event_response(const MEMORY_CHUNK * const chunk,
902                                 MEMORY_CHUNK * const post)
903 {
904   jsmn_parser json_parser;
905   jsmntok_t json_tokens[EVEL_MAX_RESPONSE_TOKENS];
906   int num_tokens = 0;
907
908   EVEL_ENTER();
909
910   /***************************************************************************/
911   /* Check preconditions.                                                    */
912   /***************************************************************************/
913   assert(chunk != NULL);
914   assert(priority_post.memory == NULL);
915
916   EVEL_DEBUG("Response size = %d", chunk->size);
917   EVEL_DEBUG("Response = %s", chunk->memory);
918
919   /***************************************************************************/
920   /* Initialize the parser and tokenize the response.                        */
921   /***************************************************************************/
922   jsmn_init(&json_parser);
923   num_tokens = jsmn_parse(&json_parser,
924                           chunk->memory,
925                           chunk->size,
926                           json_tokens,
927                           EVEL_MAX_RESPONSE_TOKENS);
928
929   if (num_tokens < 0)
930   {
931     EVEL_ERROR("Failed to parse JSON response.  "
932                "Error code=%d", num_tokens);
933   }
934   else if (num_tokens == 0)
935   {
936     EVEL_DEBUG("No tokens found in JSON response");
937   }
938   else
939   {
940     EVEL_DEBUG("Decode JSON response tokens");
941     if (!evel_handle_response_tokens(chunk, json_tokens, num_tokens, post))
942     {
943       EVEL_ERROR("Failed to handle JSON response.");
944     }
945   }
946
947   EVEL_EXIT();
948 }
949
950 /**************************************************************************//**
951  * Handle a JSON response from the listener, as a list of tokens from JSMN.
952  *
953  * @param chunk         Memory chunk containing the JSON buffer.
954  * @param json_tokens   Array of tokens to handle.
955  * @param num_tokens    The number of tokens to handle.
956  * @param post          The memory chunk in which to place any resulting POST.
957  * @return true if we handled the response, false otherwise.
958  *****************************************************************************/
959 bool evel_handle_response_tokens(const MEMORY_CHUNK * const chunk,
960                                  const jsmntok_t * const json_tokens,
961                                  const int num_tokens,
962                                  MEMORY_CHUNK * const post)
963 {
964   bool json_ok = false;
965
966   EVEL_ENTER();
967
968   /***************************************************************************/
969   /* Check preconditions.                                                    */
970   /***************************************************************************/
971   assert(chunk != NULL);
972   assert(json_tokens != NULL);
973   assert(num_tokens < EVEL_MAX_RESPONSE_TOKENS);
974
975   /***************************************************************************/
976   /* Peek at the tokens to decide what the response it, then call the        */
977   /* appropriate handler to handle it.  There is only one handler at this    */
978   /* point.                                                                  */
979   /***************************************************************************/
980   if (evel_tokens_match_command_list(chunk, json_tokens, num_tokens))
981   {
982     json_ok = evel_handle_command_list(chunk, json_tokens, num_tokens, post);
983   }
984
985   EVEL_EXIT();
986
987   return json_ok;
988 }
989
990 /**************************************************************************//**
991  * Determine whether a list of tokens looks like a "commandList" response.
992  *
993  * @param chunk         Memory chunk containing the JSON buffer.
994  * @param json_tokens   Token to check.
995  * @param num_tokens    The number of tokens to handle.
996  * @return true if the tokens look like a "commandList" match, or false.
997  *****************************************************************************/
998 bool evel_tokens_match_command_list(const MEMORY_CHUNK * const chunk,
999                                     const jsmntok_t * const json_tokens,
1000                                     const int num_tokens)
1001 {
1002   bool result = false;
1003
1004   EVEL_ENTER();
1005
1006   /***************************************************************************/
1007   /* Make some checks on the basic layout of the commandList.                */
1008   /***************************************************************************/
1009   if ((num_tokens > 3) &&
1010       (json_tokens[0].type == JSMN_OBJECT) &&
1011       (json_tokens[1].type == JSMN_STRING) &&
1012       (json_tokens[2].type == JSMN_ARRAY) &&
1013       (evel_token_equals_string(chunk, &json_tokens[1], "commandList")))
1014   {
1015     result = true;
1016   }
1017
1018   EVEL_EXIT();
1019
1020   return result;
1021 }
1022
1023 /**************************************************************************//**
1024  * Check that a string token matches a given input string.
1025  *
1026  * @param chunk         Memory chunk containing the JSON buffer.
1027  * @param json_token    Token to check.
1028  * @param check_string  String to check it against.
1029  * @return true if the strings match, or false.
1030  *****************************************************************************/
1031 bool evel_token_equals_string(const MEMORY_CHUNK * const chunk,
1032                               const jsmntok_t * json_token,
1033                               const char * check_string)
1034 {
1035   bool result = false;
1036
1037   EVEL_ENTER();
1038
1039   const int token_length = json_token->end - json_token->start;
1040   const char * const token_string = chunk->memory + json_token->start;
1041
1042   if (token_length == (int)strlen(check_string))
1043   {
1044     result = (strncmp(token_string, check_string, token_length) == 0);
1045   }
1046
1047   EVEL_EXIT();
1048
1049   return result;
1050 }