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