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