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