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