text: /**************************************************************************//**
   * @file
   * Event Manager
   *
   * Simple event manager that is responsible for taking events (Heartbeats,
   * Faults and Measurements) from the ring-buffer and posting them to the API.
   *
   * License
   * -------
   *
   * Copyright(c) <2016>, AT&T Intellectual Property.  All other rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   * 1. Redistributions of source code must retain the above copyright notice,
   *    this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright notice,
   *    this list of conditions and the following disclaimer in the documentation
   *    and/or other materials provided with the distribution.
   * 3. All advertising materials mentioning features or use of this software
   *    must display the following acknowledgement:  This product includes
   *    software developed by the AT&T.
   * 4. Neither the name of AT&T nor the names of its contributors may be used to
   *    endorse or promote products derived from this software without specific
   *    prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY AT&T INTELLECTUAL PROPERTY ''AS IS'' AND ANY
   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL AT&T INTELLECTUAL PROPERTY BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  
  #define _GNU_SOURCE
  #include <string.h>
  #include <assert.h>
  #include <stdlib.h>
  #include <limits.h>
  #include <pthread.h>
  #include <search.h>
  
  #include "evel_throttle.h"
  
  /*****************************************************************************/
  /* The Event Throttling State for all domains, indexed by                    */
  /* ::EVEL_EVENT_DOMAINS, corresponding to JSON eventDomain.                  */
  /*                                                                           */
  /* A given domain is in a throttled state if ::evel_throttle_spec is         */
  /* non-NULL.                                                                 */
  /*****************************************************************************/
  static EVEL_THROTTLE_SPEC * evel_throttle_spec[EVEL_MAX_DOMAINS];
  
  /*****************************************************************************/
  /* The current measurement interval.  Default: MEASUREMENT_INTERVAL_UKNOWN.  */
  /* Must be protected by evel_measurement_interval_mutex.                     */
  /*****************************************************************************/
  static int evel_measurement_interval;
  
  /*****************************************************************************/
  /* Mutex protecting evel_measurement_interval from contention between an     */
  /* EVEL client reading it, and the EVEL event handler updating it.           */
  /*****************************************************************************/
  static pthread_mutex_t evel_measurement_interval_mutex;
  
  /*****************************************************************************/
  /* Flag stating that we have received a "provideThrottlingState" command.    */
  /* Set during JSON processing and cleared on sending the throttling state.   */
  /*****************************************************************************/
  static bool evel_provide_throttling_state;
  
  /*****************************************************************************/
  /* Holder for the "commandType" value during JSON processing.                */
  /*****************************************************************************/
  static char * evel_command_type_value;
  
  /*****************************************************************************/
  /* Holder for the "measurementInterval" value during JSON processing.        */
  /*****************************************************************************/
  static char * evel_measurement_interval_value;
  
  /*****************************************************************************/
  /* Holder for the "eventDomain" value during JSON processing.                */
  /*****************************************************************************/
  static char * evel_throttle_spec_domain_value;
  
  /*****************************************************************************/
  /* Decoded version of ::evel_throttle_spec_domain_value.                     */
  /*****************************************************************************/
  static EVEL_EVENT_DOMAINS evel_throttle_spec_domain;
  
  /*****************************************************************************/
  /* During JSON processing of a single throttling specification, we collect   */
  /* parameters in this working ::EVEL_THROTTLE_SPEC                           */
  /*****************************************************************************/
  static EVEL_THROTTLE_SPEC * evel_temp_throttle;
  
  /*****************************************************************************/
  /* State tracking our progress through the command list                      */
  /*****************************************************************************/
  EVEL_JSON_COMMAND_STATE evel_json_command_state;
  
  /*****************************************************************************/
  /* Debug strings for ::EVEL_JSON_COMMAND_STATE.                              */
  /*****************************************************************************/
  static const char * const evel_jcs_strings[EVEL_JCS_MAX] = {
    "EVEL_JCS_START",
    "EVEL_JCS_COMMAND_LIST",
    "EVEL_JCS_COMMAND_LIST_ENTRY",
    "EVEL_JCS_COMMAND",
    "EVEL_JCS_SPEC",
    "EVEL_JCS_FIELD_NAMES",
    "EVEL_JCS_PAIRS_LIST",
    "EVEL_JCS_PAIRS_LIST_ENTRY",
    "EVEL_JCS_NV_PAIR_NAMES"
  };
  
  /*****************************************************************************/
  /* Debug strings for JSON token type.                                        */
  /*****************************************************************************/
  #define JSON_TOKEN_TYPES                (JSMN_PRIMITIVE + 1)
  static const char * const evel_json_token_strings[JSON_TOKEN_TYPES] = {
    "JSMN_UNDEFINED",
    "JSMN_OBJECT",
    "JSMN_ARRAY",
    "JSMN_STRING",
    "JSMN_PRIMITIVE"
  };
  
  /*****************************************************************************/
  /* Debug strings for JSON domains.                                           */
  /*****************************************************************************/
  static const char * evel_domain_strings[EVEL_MAX_DOMAINS] = {
    "internal",
    "heartbeat",
    "fault",
    "measurementsForVfScaling",
    "mobileFlow",
    "report",
    "serviceEvents",
    "signaling",
    "stateChange",
    "syslog",
    "other"
    "voiceQuality",
    "maxDomain"
  };
  
  /*****************************************************************************/
  /* Local prototypes.                                                         */
  /*****************************************************************************/
  static void evel_throttle_finalize(EVEL_THROTTLE_SPEC * throttle_spec);
  static struct hsearch_data * evel_throttle_hash_create(DLIST * hash_keys);
  static void evel_throttle_free(EVEL_THROTTLE_SPEC * throttle_spec);
  static void evel_throttle_free_nv_pair(EVEL_SUPPRESSED_NV_PAIRS * nv_pairs);
  static void evel_init_json_stack(EVEL_JSON_STACK * json_stack,
                                   const MEMORY_CHUNK * const chunk);
  static bool evel_stack_push(EVEL_JSON_STACK * const json_stack,
                              const int num_required,
                              const EVEL_JSON_STATE new_state);
  static void evel_stack_pop(EVEL_JSON_STACK * const json_stack);
  static void evel_stack_cleanup(EVEL_JSON_STACK * const json_stack);
  static char * evel_stack_strdup(const MEMORY_CHUNK * const chunk,
                                  const jsmntok_t * const token);
  static void evel_stack_store_key(EVEL_JSON_STACK * const json_stack,
                                   const jsmntok_t * const token);
  static void evel_stack_store_value(EVEL_JSON_STACK * const json_stack,
                                     const jsmntok_t * const token);
  static void evel_stack_store_item(EVEL_JSON_STACK * const json_stack,
                                    const jsmntok_t * const token);
  static void evel_set_command_state(const EVEL_JSON_COMMAND_STATE new_state);
  static void evel_debug_token(const MEMORY_CHUNK * const chunk,
                               const jsmntok_t * const token);
  static void evel_command_list_response(MEMORY_CHUNK * const post);
  static int evel_json_encode_throttle(char * const json, const int max_size);
  static int evel_json_encode_throttle_spec(char * const json,
                                            const int max_size,
                                            const EVEL_EVENT_DOMAINS domain);
  static int evel_json_encode_nv_pairs(char * const json,
                                       const int max_size,
                                       EVEL_SUPPRESSED_NV_PAIRS * nv_pairs);
  static void evel_close_command();
  static void evel_open_command();
  static void evel_set_throttling_spec();
  static void evel_set_measurement_interval();
  static void evel_open_throttle_spec();
  static void evel_close_throttle_spec();
  static EVEL_EVENT_DOMAINS evel_decode_domain(char * domain_value);
  static void evel_open_nv_pairs_list_entry();
  static void evel_close_nv_pairs_list_entry();
  static void evel_store_nv_pair_field_name(char * const value);
  static void evel_store_nv_pair_name(char * const item);
  static void evel_store_suppressed_field_name(char * const item);
  static EVEL_SUPPRESSED_NV_PAIRS * evel_get_last_nv_pairs();
  
  /**************************************************************************//**
   * Return the current measurement interval provided by the Event Listener.
   *
   * @returns The current measurement interval
   * @retval  EVEL_MEASUREMENT_INTERVAL_UKNOWN (0) - interval has not been
   *          specified
   *****************************************************************************/
  int evel_get_measurement_interval()
  {
    int result;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Lock, read, unlock.                                                     */
    /***************************************************************************/
    pthread_mutex_lock(&evel_measurement_interval_mutex);
    result = evel_measurement_interval;
    pthread_mutex_unlock(&evel_measurement_interval_mutex);
  
    EVEL_EXIT();
  
    return result;
  }
  
  /**************************************************************************//**
   * Return the ::EVEL_THROTTLE_SPEC for a given domain.
   *
   * @param domain        The domain for which to return state.
   *****************************************************************************/
  EVEL_THROTTLE_SPEC * evel_get_throttle_spec(EVEL_EVENT_DOMAINS domain)
  {
    EVEL_THROTTLE_SPEC * result;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(domain < EVEL_MAX_DOMAINS);
  
    result = evel_throttle_spec[domain];
  
    EVEL_EXIT();
  
    return result;
  }
  
  /**************************************************************************//**
   * Determine whether a field_name should be suppressed.
   *
   * @param throttle_spec Throttle specification for the domain being encoded.
   * @param field_name    The field name to encoded or suppress.
   * @return true if the field_name should be suppressed, false otherwise.
   *****************************************************************************/
  bool evel_throttle_suppress_field(EVEL_THROTTLE_SPEC * throttle_spec,
                                    const char * const field_name)
  {
    bool suppress = false;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(field_name != NULL);
  
    /***************************************************************************/
    /* If the throttle spec and hash table exist, query the field_names table. */
    /***************************************************************************/
    if ((throttle_spec != NULL) && (throttle_spec->hash_field_names != NULL))
    {
      ENTRY hash_query;
      ENTRY * hash_result;
      hash_query.key = (char * const) field_name;
      suppress = (hsearch_r(hash_query,
                            FIND,
                            &hash_result,
                            throttle_spec->hash_field_names) != 0);
    }
  
    EVEL_EXIT();
  
    return suppress;
  }
  
  /**************************************************************************//**
   * Determine whether a name-value pair should be allowed (not suppressed).
   *
   * @param throttle_spec Throttle specification for the domain being encoded.
   * @param field_name    The field name holding the name-value pairs.
   * @param name          The name of the name-value pair to encoded or suppress.
   * @return true if the name-value pair should be suppressed, false otherwise.
   *****************************************************************************/
  bool evel_throttle_suppress_nv_pair(EVEL_THROTTLE_SPEC * throttle_spec,
                                      const char * const field_name,
                                      const char * const name)
  {
    EVEL_SUPPRESSED_NV_PAIRS * nv_pairs;
    bool hit = false;
    bool suppress = false;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(field_name != NULL);
    assert(name != NULL);
  
    /***************************************************************************/
    /* If the throttle spec and hash table exist, query the nv_pairs table.    */
    /***************************************************************************/
    if ((throttle_spec != NULL) && (throttle_spec->hash_nv_pairs_list != NULL))
    {
      ENTRY hash_query;
      ENTRY * hash_result;
      hash_query.key = (char * const) field_name;
      hit = (hsearch_r(hash_query,
                       FIND,
                       &hash_result,
                       throttle_spec->hash_nv_pairs_list) != 0);
      if (hit)
      {
        nv_pairs = hash_result->data;
      }
    }
  
    /***************************************************************************/
    /* If we got a hit, and the nv_pairs and hash table exist, query the       */
    /* nv_pairs table.                                                         */
    /***************************************************************************/
    if (hit && (nv_pairs != NULL) && (nv_pairs->hash_nv_pair_names != NULL))
    {
      ENTRY hash_query;
      ENTRY * hash_result;
      hash_query.key = (char * const) name;
      suppress = (hsearch_r(hash_query,
                            FIND,
                            &hash_result,
                            nv_pairs->hash_nv_pair_names) != 0);
    }
  
    EVEL_EXIT();
  
    return suppress;
  }
  
  /**************************************************************************//**
   * Initialize event throttling to the default state.
   *
   * Called from ::evel_initialize.
   *****************************************************************************/
  void evel_throttle_initialize()
  {
    int pthread_rc;
    int ii;
  
    EVEL_ENTER();
  
    for (ii = 0; ii < EVEL_MAX_DOMAINS; ii++)
    {
      evel_throttle_spec[ii] = NULL;
    }
  
    pthread_rc = pthread_mutex_init(&evel_measurement_interval_mutex, NULL);
    assert(pthread_rc == 0);
  
    evel_measurement_interval = EVEL_MEASUREMENT_INTERVAL_UKNOWN;
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Clean up event throttling.
   *
   * Called from ::evel_terminate.
   *****************************************************************************/
  void evel_throttle_terminate()
  {
    int pthread_rc;
    int ii;
  
    EVEL_ENTER();
  
    for (ii = 0; ii < EVEL_MAX_DOMAINS; ii++)
    {
      if (evel_throttle_spec[ii] != NULL)
      {
        evel_throttle_free(evel_throttle_spec[ii]);
        evel_throttle_spec[ii] = NULL;
      }
    }
  
    pthread_rc = pthread_mutex_destroy(&evel_measurement_interval_mutex);
    assert(pthread_rc == 0);
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Finalize a single ::EVEL_THROTTLE_SPEC.
   *
   * Now that the specification is collected, build hash tables to simplify the
   * throttling itself.
   *
   * @param throttle_spec The ::EVEL_THROTTLE_SPEC to finalize.
   *****************************************************************************/
  void evel_throttle_finalize(EVEL_THROTTLE_SPEC * throttle_spec)
  {
    int nv_pairs_count;
    DLIST_ITEM * dlist_item;
    ENTRY * add_result;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(throttle_spec != NULL);
  
    /***************************************************************************/
    /* Populate the hash table for suppressed field names.                     */
    /***************************************************************************/
    throttle_spec->hash_field_names =
               evel_throttle_hash_create(&throttle_spec->suppressed_field_names);
  
    /***************************************************************************/
    /* Create the hash table for suppressed nv pairs.                          */
    /***************************************************************************/
    nv_pairs_count = dlist_count(&throttle_spec->suppressed_nv_pairs_list);
    if (nv_pairs_count > 0)
    {
      throttle_spec->hash_nv_pairs_list = calloc(1, sizeof(struct hsearch_data));
      assert(throttle_spec->hash_nv_pairs_list != NULL);
  
      /*************************************************************************/
      /* Provide plenty of space in the table - see hcreate_r notes.           */
      /*************************************************************************/
      if (hcreate_r(nv_pairs_count * 2, throttle_spec->hash_nv_pairs_list) == 0)
      {
        EVEL_ERROR("Failed to create hash table");
        free(throttle_spec->hash_nv_pairs_list);
        throttle_spec->hash_nv_pairs_list = NULL;
      }
    }
  
    /***************************************************************************/
    /* Populate the hash tables under suppressed field names.                  */
    /***************************************************************************/
    dlist_item = dlist_get_first(&throttle_spec->suppressed_nv_pairs_list);
    while (dlist_item != NULL)
    {
      EVEL_SUPPRESSED_NV_PAIRS * nv_pairs = dlist_item->item;
      ENTRY hash_add;
  
      /*************************************************************************/
      /* Set the key to the string, and the item to the nv_pairs.              */
      /*************************************************************************/
      assert(nv_pairs != NULL);
      hash_add.key = nv_pairs->nv_pair_field_name;
      hash_add.data = nv_pairs;
      hsearch_r(hash_add, ENTER, &add_result, throttle_spec->hash_nv_pairs_list);
  
      /*************************************************************************/
      /* Create the nv_pair_names hash since we're in here.                    */
      /*************************************************************************/
      nv_pairs->hash_nv_pair_names =
        evel_throttle_hash_create(&nv_pairs->suppressed_nv_pair_names);
  
      dlist_item = dlist_get_next(dlist_item);
    }
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Create and populate a hash table from a DLIST of keys.
   *
   * @param hash_keys     Pointer to a DLIST of hash table keys.
   * @return Pointer to the created hash-table, or NULL on failure.
   *****************************************************************************/
  struct hsearch_data * evel_throttle_hash_create(DLIST * hash_keys)
  {
    int key_count;
    struct hsearch_data * hash_table;
    ENTRY * add_result;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(hash_keys != NULL);
  
    /***************************************************************************/
    /* Count the keys and if there are any, populate the hash table with them. */
    /***************************************************************************/
    key_count = dlist_count(hash_keys);
    if (key_count > 0)
    {
      EVEL_DEBUG("Populating table for %d keys", key_count);
  
      hash_table = calloc(1, sizeof(struct hsearch_data));
      assert(hash_table != NULL);
  
      /*************************************************************************/
      /* We need to leave plenty of space in the table - see hcreate_r notes.  */
      /*************************************************************************/
      if (hcreate_r(key_count * 2, hash_table) != 0)
      {
        DLIST_ITEM * dlist_item;
        dlist_item = dlist_get_first(hash_keys);
        while (dlist_item != NULL)
        {
          assert(dlist_item->item != NULL);
  
          /*********************************************************************/
          /* Set the key and data to the item, which is a string in this case. */
          /*********************************************************************/
          ENTRY hash_add;
          hash_add.key = dlist_item->item;
          hash_add.data = dlist_item->item;
          hsearch_r(hash_add, ENTER, &add_result, hash_table);
          dlist_item = dlist_get_next(dlist_item);
        }
      }
      else
      {
        EVEL_ERROR("Failed to create hash table");
        free(hash_table);
        hash_table = NULL;
      }
    }
    else
    {
      hash_table = NULL;
    }
  
    EVEL_EXIT();
  
    return hash_table;
  }
  
  /**************************************************************************//**
   * Free resources associated with a single ::EVEL_THROTTLE_SPEC.
   *
   * @param throttle_spec The ::EVEL_THROTTLE_SPEC to free.
   *****************************************************************************/
  void evel_throttle_free(EVEL_THROTTLE_SPEC * throttle_spec)
  {
    char * field_name;
    EVEL_SUPPRESSED_NV_PAIRS * nv_pairs;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(throttle_spec != NULL);
  
    /***************************************************************************/
    /* Free any hash tables.                                                   */
    /***************************************************************************/
    if (throttle_spec->hash_field_names != NULL)
    {
      hdestroy_r(throttle_spec->hash_field_names);
      free(throttle_spec->hash_field_names);
    }
    if (throttle_spec->hash_nv_pairs_list != NULL)
    {
      hdestroy_r(throttle_spec->hash_nv_pairs_list);
      free(throttle_spec->hash_nv_pairs_list);
    }
  
    /***************************************************************************/
    /* Iterate through the linked lists, freeing memory.                       */
    /***************************************************************************/
    field_name = dlist_pop_last(&throttle_spec->suppressed_field_names);
    while (field_name != NULL)
    {
      free(field_name);
      field_name = dlist_pop_last(&throttle_spec->suppressed_field_names);
    }
  
    nv_pairs = dlist_pop_last(&throttle_spec->suppressed_nv_pairs_list);
    while (nv_pairs != NULL)
    {
      evel_throttle_free_nv_pair(nv_pairs);
      nv_pairs = dlist_pop_last(&throttle_spec->suppressed_nv_pairs_list);
    }
  
    free(throttle_spec);
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Free resources associated with a single ::EVEL_SUPPRESSED_NV_PAIR.
   *
   * @param nv_pair       The ::EVEL_SUPPRESSED_NV_PAIR to free.
   *****************************************************************************/
  void evel_throttle_free_nv_pair(EVEL_SUPPRESSED_NV_PAIRS * nv_pairs)
  {
    char * suppressed_name;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(nv_pairs != NULL);
  
    /***************************************************************************/
    /* Free any hash tables.                                                   */
    /***************************************************************************/
    if (nv_pairs->hash_nv_pair_names != NULL)
    {
      hdestroy_r(nv_pairs->hash_nv_pair_names);
      free(nv_pairs->hash_nv_pair_names);
    }
  
    /***************************************************************************/
    /* Iterate through the linked lists, freeing memory.                       */
    /***************************************************************************/
    suppressed_name = dlist_pop_last(&nv_pairs->suppressed_nv_pair_names);
    while (suppressed_name != NULL)
    {
      free(suppressed_name);
      suppressed_name = dlist_pop_last(&nv_pairs->suppressed_nv_pair_names);
    }
    if (nv_pairs->nv_pair_field_name != NULL)
    {
      free(nv_pairs->nv_pair_field_name);
    }
    free(nv_pairs);
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Handle a JSON response from the listener, as a list of tokens from JSMN.
   *
   * @param chunk         Memory chunk containing the JSON buffer.
   * @param json_tokens   Array of tokens to handle.
   * @param num_tokens    The number of tokens to handle.
   * @param post          The memory chunk in which to place any resulting POST.
   * @return true if the command was handled, false otherwise.
   *****************************************************************************/
  bool evel_handle_command_list(const MEMORY_CHUNK * const chunk,
                                const jsmntok_t * const json_tokens,
                                const int num_tokens,
                                MEMORY_CHUNK * const post)
  {
    EVEL_JSON_STACK stack;
    EVEL_JSON_STACK * json_stack = &stack;
    EVEL_JSON_STACK_ENTRY * entry;
  
    bool json_ok = true;
    int token_index = 0;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(chunk != NULL);
    assert(json_tokens != NULL);
    assert(num_tokens < EVEL_MAX_RESPONSE_TOKENS);
  
    /***************************************************************************/
    /* Collect one top-level item.                                             */
    /***************************************************************************/
    evel_init_json_stack(json_stack, chunk);
  
    /***************************************************************************/
    /* Initialize JSON processing variables.                                   */
    /***************************************************************************/
    evel_provide_throttling_state = false;
    evel_command_type_value = NULL;
    evel_measurement_interval_value = NULL;
    evel_throttle_spec_domain_value = NULL;
    evel_throttle_spec_domain = EVEL_MAX_DOMAINS;
    evel_temp_throttle = NULL;
    evel_json_command_state = EVEL_JCS_START;
  
    /***************************************************************************/
    /* Loop through the tokens, keeping a stack of state representing the      */
    /* nested JSON structure (see json_state). We also track our way through   */
    /* the ::EVEL_JSON_COMMAND_STATE as we go.                                 */
    /***************************************************************************/
    while (json_ok && (token_index < num_tokens))
    {
      const jsmntok_t * const token = &json_tokens[token_index];
  
      if (EVEL_DEBUG_ON())
      {
        evel_debug_token(chunk, token);
      }
  
      /*************************************************************************/
      /* We may have popped or pushed, so always re-evaluate the stack entry.  */
      /*************************************************************************/
      entry = &json_stack->entry[json_stack->level];
  
      switch(token->type)
      {
        case JSMN_OBJECT:
          if ((entry->json_state == EVEL_JSON_ITEM) ||
              (entry->json_state == EVEL_JSON_VALUE))
          {
            json_ok = evel_stack_push(json_stack, token->size, EVEL_JSON_KEY);
          }
          else
          {
            EVEL_ERROR("Unexpected JSON state %d at token %d (%d)",
                       entry->json_state, token_index, token->type);
            json_ok = false;
          }
          break;
  
        case JSMN_ARRAY:
          if ((entry->json_state == EVEL_JSON_ITEM) ||
              (entry->json_state == EVEL_JSON_VALUE))
          {
            json_ok = evel_stack_push(json_stack, token->size, EVEL_JSON_ITEM);
          }
          else
          {
            EVEL_ERROR("Unexpected JSON state %d at token %d (%d)",
                       entry->json_state, token_index, token->type);
            json_ok = false;
          }
          break;
  
        case JSMN_STRING:
          if (entry->json_state == EVEL_JSON_KEY)
          {
            evel_stack_store_key(json_stack, token);
          }
          else if (entry->json_state == EVEL_JSON_VALUE)
          {
            evel_stack_store_value(json_stack, token);
          }
          else if (entry->json_state == EVEL_JSON_ITEM)
          {
            evel_stack_store_item(json_stack, token);
          }
          else
          {
            EVEL_ERROR("Unexpected JSON state %d at token %d (%d)",
                       entry->json_state, token_index, token->type);
            json_ok = false;
          }
          break;
  
        case JSMN_PRIMITIVE:
          if (entry->json_state == EVEL_JSON_VALUE)
          {
            evel_stack_store_value(json_stack, token);
          }
          else if (entry->json_state == EVEL_JSON_ITEM)
          {
            evel_stack_store_item(json_stack, token);
          }
          else
          {
            EVEL_ERROR("Unexpected JSON state %d at token %d (%d)",
                       entry->json_state, token_index, token->type);
            json_ok = false;
          }
          break;
  
        case JSMN_UNDEFINED:
        default:
          EVEL_ERROR("Unexpected JSON format at token %d (%d)",
                     token_index, token->type);
          json_ok = false;
          break;
      }
  
      /*************************************************************************/
      /* Pop the stack if we're counted enough nested items.                   */
      /*************************************************************************/
      evel_stack_pop(json_stack);
  
      token_index++;
    }
  
    /***************************************************************************/
    /* Cleanup the stack - we may have exited without winding it back, if the  */
    /* input was not well formed.                                              */
    /***************************************************************************/
    evel_stack_cleanup(json_stack);
  
    /***************************************************************************/
    /* We may want to generate and POST a response to the command list.        */
    /***************************************************************************/
    if (json_ok)
    {
      evel_command_list_response(post);
    }
  
    /***************************************************************************/
    /* Make sure we're clean on exit.                                          */
    /***************************************************************************/
    assert(evel_command_type_value == NULL);
    assert(evel_measurement_interval_value == NULL);
    assert(evel_throttle_spec_domain_value == NULL);
    assert(evel_throttle_spec_domain == EVEL_MAX_DOMAINS);
    assert(evel_temp_throttle == NULL);
  
    EVEL_EXIT();
  
    return json_ok;
  }
  
  /**************************************************************************//**
   * Copy a copy of an element, in string form.
   *
   * The caller must manage memory allocated for the copied string.
   *
   * @param chunk         Memory chunk containing the JSON buffer.
   * @param token         The token to copy from.
   * @return the copy of the element.
   *****************************************************************************/
  char * evel_stack_strdup(const MEMORY_CHUNK * const chunk,
                           const jsmntok_t * const token)
  {
    char temp_char;
    char * result;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Call strdup to copy the string, inserting a temporary \0 for the call.  */
    /***************************************************************************/
    temp_char = chunk->memory[token->end];
    chunk->memory[token->end] = '\0';
    result = strdup(chunk->memory + token->start);
    assert(result != NULL);
    chunk->memory[token->end] = temp_char;
  
    EVEL_EXIT();
  
    return result;
  }
  
  /**************************************************************************//**
   * Copy a copy of an element, in string form.
   *
   * @param json_stack    The JSON stack to initialize.
   * @param chunk         The underlying memory chunk used for parsing.
   *****************************************************************************/
  void evel_init_json_stack(EVEL_JSON_STACK * json_stack,
                            const MEMORY_CHUNK * const chunk)
  {
    EVEL_JSON_STACK_ENTRY * entry;
  
    EVEL_ENTER();
  
    json_stack->level = 0;
    entry = json_stack->entry;
    entry->json_state = EVEL_JSON_ITEM;
    entry->json_count = 0;
    entry->num_required = 1;
    entry->json_key = NULL;
    json_stack->chunk = chunk;
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Push a new entry on the stack
   *
   * @param json_stack    The stack.
   * @param num_required  The number of elements required.
   * @param new_state     The state for the new entry.
   * @return false if we cannot push onto the stack.
   *****************************************************************************/
  bool evel_stack_push(EVEL_JSON_STACK * const json_stack,
                       const int num_required,
                       const EVEL_JSON_STATE new_state)
  {
    EVEL_JSON_STACK_ENTRY * entry;
    char * key;
    bool result;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(json_stack != NULL);
    assert(json_stack->level >= 0);
    assert(json_stack->level < EVEL_JSON_STACK_DEPTH);
    assert((new_state == EVEL_JSON_ITEM) || (new_state == EVEL_JSON_KEY));
  
    /***************************************************************************/
    /* Check nesting depth, and stop processing if we hit the limit.           */
    /***************************************************************************/
    if ((json_stack->level + 1) >= EVEL_JSON_STACK_DEPTH)
    {
      EVEL_ERROR("JSON Nesting is too deep - stop processing");
      result = false;
      goto exit_label;
    }
  
    /***************************************************************************/
    /* Evaluate cases where we recurse and are interested in the contents.     */
    /***************************************************************************/
    entry = &json_stack->entry[json_stack->level];
    key = entry->json_key;
  
    /***************************************************************************/
    /* Note that this is the key before we drop a level.                       */
    /***************************************************************************/
    if (key != NULL)
    {
      EVEL_DEBUG("Push with key: %s", key);
  
      switch (evel_json_command_state)
      {
        case EVEL_JCS_START:
          if (strcmp(key, "commandList") == 0)
          {
            evel_set_command_state(EVEL_JCS_COMMAND_LIST);
          }
          break;
  
        case EVEL_JCS_COMMAND_LIST_ENTRY:
          if (strcmp(key, "command") == 0)
          {
            evel_open_command();
            evel_set_command_state(EVEL_JCS_COMMAND);
          }
          break;
  
        case EVEL_JCS_COMMAND:
          if (strcmp(key, "eventDomainThrottleSpecification") == 0)
          {
            evel_open_throttle_spec();
            evel_set_command_state(EVEL_JCS_SPEC);
          }
          break;
  
        case EVEL_JCS_SPEC:
          if (strcmp(key, "suppressedFieldNames") == 0)
          {
            evel_set_command_state(EVEL_JCS_FIELD_NAMES);
          }
          else if (strcmp(key, "suppressedNvPairsList") == 0)
          {
            evel_set_command_state(EVEL_JCS_PAIRS_LIST);
          }
          break;
  
        case EVEL_JCS_PAIRS_LIST_ENTRY:
          if (strcmp(key, "suppressedNvPairNames") == 0)
          {
            evel_set_command_state(EVEL_JCS_NV_PAIR_NAMES);
          }
          break;
  
        case EVEL_JCS_FIELD_NAMES:
        case EVEL_JCS_PAIRS_LIST:
        case EVEL_JCS_NV_PAIR_NAMES:
        default:
          EVEL_ERROR("Unexpected JSON key %s in state %d",
                     key,
                     evel_json_command_state);
          break;
      }
    }
    else
    {
      EVEL_DEBUG("Push with no key");
  
      /*************************************************************************/
      /* If we're pushing without a key, then we're in an array.  We switch    */
      /* state based on the existing state and stack level.                    */
      /*************************************************************************/
      const int COMMAND_LIST_LEVEL = 2;
      const int NV_PAIRS_LIST_LEVEL = 6;
  
      if ((evel_json_command_state == EVEL_JCS_PAIRS_LIST) &&
          (json_stack->level == NV_PAIRS_LIST_LEVEL))
      {
        /***********************************************************************/
        /* We are entering an object within the "suppressedNvPairsList" array. */
        /***********************************************************************/
        evel_open_nv_pairs_list_entry();
        evel_set_command_state(EVEL_JCS_PAIRS_LIST_ENTRY);
      }
  
      if ((evel_json_command_state == EVEL_JCS_COMMAND_LIST) &&
          (json_stack->level == COMMAND_LIST_LEVEL))
      {
        /***********************************************************************/
        /* We are entering an object within the "commandList" array.           */
        /***********************************************************************/
        evel_set_command_state(EVEL_JCS_COMMAND_LIST_ENTRY);
      }
    }
  
    /***************************************************************************/
    /* Push the stack and initialize the entry.                                */
    /***************************************************************************/
    json_stack->level++;
    entry++;
    EVEL_DEBUG("Stack Push -> %d", json_stack->level);
    entry = &json_stack->entry[json_stack->level];
    entry->json_count = 0;
    entry->num_required = num_required;
    entry->json_state = new_state;
    entry->json_key = NULL;
    result = true;
  
  exit_label:
  
    EVEL_EXIT();
  
    return result;
  }
  
  /**************************************************************************//**
   * Pop any stack entries which have collected the required number of items.
   *
   * @param json_stack    The stack.
   *****************************************************************************/
  void evel_stack_pop(EVEL_JSON_STACK * const json_stack)
  {
    EVEL_JSON_STACK_ENTRY * entry;
    char * key;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(json_stack != NULL);
    assert(json_stack->level >= 0);
    assert(json_stack->level < EVEL_JSON_STACK_DEPTH);
  
    entry = &json_stack->entry[json_stack->level];
    while ((json_stack->level > 0) && (entry->json_count == entry->num_required))
    {
      key = entry->json_key;
  
      switch (evel_json_command_state)
      {
        case EVEL_JCS_COMMAND_LIST:
          evel_set_command_state(EVEL_JCS_START);
          break;
  
        case EVEL_JCS_COMMAND_LIST_ENTRY:
          evel_set_command_state(EVEL_JCS_COMMAND_LIST);
          break;
  
        case EVEL_JCS_COMMAND:
          evel_close_command();
          evel_set_command_state(EVEL_JCS_COMMAND_LIST_ENTRY);
          break;
  
        case EVEL_JCS_SPEC:
          evel_close_throttle_spec();
          evel_set_command_state(EVEL_JCS_COMMAND);
          break;
  
        case EVEL_JCS_FIELD_NAMES:
          evel_set_command_state(EVEL_JCS_SPEC);
          break;
  
        case EVEL_JCS_PAIRS_LIST:
          evel_set_command_state(EVEL_JCS_SPEC);
          break;
  
        case EVEL_JCS_PAIRS_LIST_ENTRY:
          evel_close_nv_pairs_list_entry();
          evel_set_command_state(EVEL_JCS_PAIRS_LIST);
          break;
  
        case EVEL_JCS_NV_PAIR_NAMES:
          evel_set_command_state(EVEL_JCS_PAIRS_LIST_ENTRY);
          break;
  
        default:
          break;
      }
  
      /*************************************************************************/
      /* Free off any key that was duplicated and stored.                      */
      /*************************************************************************/
      if (key != NULL)
      {
        free(key);
        entry->json_key = NULL;
      }
  
      /*************************************************************************/
      /* We just reached the required number of key-value pairs or items, so   */
      /* pop the stack.                                                        */
      /*************************************************************************/
      json_stack->level--;
      entry--;
  
      EVEL_DEBUG("Stack Pop  -> %d", json_stack->level);
  
      /*************************************************************************/
      /* We just completed collection of an ITEM (within an ARRAY) or a VALUE  */
      /* (within an OBJECT).  Either way, we need to count it.                 */
      /*************************************************************************/
      entry->json_count++;
  
      /*************************************************************************/
      /* If we just completed a VALUE, then we expect the next element to be a */
      /* key, if there is a next element.                                      */
      /*************************************************************************/
      if (entry->json_state == EVEL_JSON_VALUE)
      {
        entry->json_state = EVEL_JSON_KEY;
      }
    }
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Pop all stack entries, freeing any memory as we go.
   *
   * @param json_stack    The stack.
   *****************************************************************************/
  void evel_stack_cleanup(EVEL_JSON_STACK * const json_stack)
  {
    EVEL_JSON_STACK_ENTRY * entry;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(json_stack != NULL);
    assert(json_stack->level >= 0);
    assert(json_stack->level < EVEL_JSON_STACK_DEPTH);
  
    entry = &json_stack->entry[json_stack->level];
    while ((json_stack->level > 0))
    {
      /*************************************************************************/
      /* Free off any key that was duplicated and stored.                      */
      /*************************************************************************/
      if (entry->json_key != NULL)
      {
        free(entry->json_key);
        entry->json_key = NULL;
      }
  
      /*************************************************************************/
      /* We just reached the required number of key-value pairs or items, so   */
      /* pop the stack.                                                        */
      /*************************************************************************/
      json_stack->level--;
      entry--;
    }
  
    /***************************************************************************/
    /* If we hit EVEL_JSON_STACK_DEPTH, we exit the loop and can leave these   */
    /* values hanging - so clean them up.                                      */
    /***************************************************************************/
    if (evel_command_type_value != NULL)
    {
      free(evel_command_type_value);
      evel_command_type_value = NULL;
    }
    if (evel_measurement_interval_value != NULL)
    {
      free(evel_measurement_interval_value);
      evel_measurement_interval_value = NULL;
    }
    if (evel_throttle_spec_domain_value != NULL)
    {
      free(evel_throttle_spec_domain_value);
      evel_throttle_spec_domain_value = NULL;
    }
    evel_throttle_spec_domain = EVEL_MAX_DOMAINS;
    if (evel_temp_throttle != NULL)
    {
      evel_throttle_free(evel_temp_throttle);
      evel_temp_throttle = NULL;
    }
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Store a key in the JSON stack.
   *
   * We always store the most recent key at each level in the stack.
   *
   * @param json_stack    The stack.
   * @param token         The token holding the key.
   *****************************************************************************/
  void evel_stack_store_key(EVEL_JSON_STACK * const json_stack,
                            const jsmntok_t * const token)
  {
    EVEL_JSON_STACK_ENTRY * entry;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(json_stack != NULL);
    assert(json_stack->level >= 0);
    assert(json_stack->level < EVEL_JSON_STACK_DEPTH);
  
    /***************************************************************************/
    /* Free any previously stored key, replacing it with the new one.          */
    /***************************************************************************/
    entry = &json_stack->entry[json_stack->level];
    if (entry->json_key != NULL)
    {
      free(entry->json_key);
    }
    entry->json_key = evel_stack_strdup(json_stack->chunk, token);
  
    /***************************************************************************/
    /* Switch state to collecting the corresponding value.                     */
    /***************************************************************************/
    entry->json_state = EVEL_JSON_VALUE;
  
    EVEL_DEBUG("Stored key: %s", entry->json_key);
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Store a value in the JSON stack.
   *
   * @param json_stack    The stack.
   * @param token         The token holding the value.
   *****************************************************************************/
  void evel_stack_store_value(EVEL_JSON_STACK * const json_stack,
                              const jsmntok_t * const token)
  {
    EVEL_JSON_STACK_ENTRY * entry;
    char * value;
    bool stored;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(json_stack != NULL);
    assert(json_stack->level >= 0);
    assert(json_stack->level < EVEL_JSON_STACK_DEPTH);
  
    /***************************************************************************/
    /* Based on the (key, state), work out whether we're expecting a value,    */
    /* then store or ignore it as required.                                    */
    /***************************************************************************/
    entry = &json_stack->entry[json_stack->level];
    value = evel_stack_strdup(json_stack->chunk, token);
    stored = false;
    EVEL_DEBUG("Store value: %s", value);
  
    switch (evel_json_command_state)
    {
      case EVEL_JCS_COMMAND:
        if (strcmp(entry->json_key, "commandType") == 0)
        {
          evel_command_type_value = value;
          stored = true;
        }
        else if (strcmp(entry->json_key, "measurementInterval") == 0)
        {
          evel_measurement_interval_value = value;
          stored = true;
        }
        break;
  
      case EVEL_JCS_SPEC:
        if (strcmp(entry->json_key, "eventDomain") == 0)
        {
          evel_throttle_spec_domain_value = value;
          stored = true;
        }
        break;
  
      case EVEL_JCS_PAIRS_LIST_ENTRY:
        if (strcmp(entry->json_key, "nvPairFieldName") == 0)
        {
          evel_store_nv_pair_field_name(value);
          stored = true;
        }
        break;
  
      default:
        EVEL_DEBUG("Ignoring value in state: %s",
                   evel_jcs_strings[evel_json_command_state]);
        break;
    }
  
    if (!stored)
    {
      EVEL_DEBUG("Ignored value: %s", value);
      free(value);
    }
  
    /***************************************************************************/
    /* Switch state to another key.                                            */
    /***************************************************************************/
    entry->json_state = EVEL_JSON_KEY;
  
    /***************************************************************************/
    /* Count the key-value pair.                                               */
    /***************************************************************************/
    entry->json_count++;
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Store an item in the JSON stack - a string or primitive in an array.
   *
   * @param json_stack    The stack.
   * @param token         The token holding the item.
   *****************************************************************************/
  void evel_stack_store_item(EVEL_JSON_STACK * const json_stack,
                             const jsmntok_t * const token)
  {
    EVEL_JSON_STACK_ENTRY * entry;
    char * item;
    bool stored;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(json_stack != NULL);
    assert(json_stack->level >= 0);
    assert(json_stack->level < EVEL_JSON_STACK_DEPTH);
  
    /***************************************************************************/
    /* Based on the state, work out whether we're expecting an item, then      */
    /* store or ignore it as required.                                         */
    /***************************************************************************/
    entry = &json_stack->entry[json_stack->level];
    item = evel_stack_strdup(json_stack->chunk, token);
    stored = false;
    EVEL_DEBUG("Store item: %s", item);
  
    switch (evel_json_command_state)
    {
      case EVEL_JCS_NV_PAIR_NAMES:
        evel_store_nv_pair_name(item);
        stored = true;
        break;
  
      case EVEL_JCS_FIELD_NAMES:
        evel_store_suppressed_field_name(item);
        stored = true;
        break;
  
      default:
        EVEL_DEBUG("Ignoring item in state: %s",
                   evel_jcs_strings[evel_json_command_state]);
        break;
    }
  
    if (!stored)
    {
      EVEL_DEBUG("Ignored item: %s", item);
      free(item);
    }
  
    /***************************************************************************/
    /* We need another item.  This is purely defensive.                        */
    /***************************************************************************/
    entry->json_state = EVEL_JSON_ITEM;
  
    /***************************************************************************/
    /* Count the item.                                                         */
    /***************************************************************************/
    entry->json_count++;
  }
  
  /**************************************************************************//**
   * Set the JSON command state to a new value.
   *
   * @param new_state     The new state to set.
   *****************************************************************************/
  void evel_set_command_state(const EVEL_JSON_COMMAND_STATE new_state)
  {
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(evel_json_command_state < EVEL_JCS_MAX);
    assert(new_state < EVEL_JCS_MAX);
  
    /***************************************************************************/
    /* Provide common debug, and set the new state.                            */
    /***************************************************************************/
    EVEL_DEBUG("Command State: %s -> %s",
               evel_jcs_strings[evel_json_command_state],
               evel_jcs_strings[new_state]);
    evel_json_command_state = new_state;
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Produce debug output from a JSON token.
   *
   * @param chunk         Memory chunk containing the JSON buffer.
   * @param token         Token to dump.
   *****************************************************************************/
  void evel_debug_token(const MEMORY_CHUNK * const chunk,
                        const jsmntok_t * const token)
  {
    char temp_char;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(token->type > 0);
    assert(token->type < JSON_TOKEN_TYPES);
  
    /***************************************************************************/
    /* Log the token, leaving it in the state in which it started.             */
    /***************************************************************************/
    temp_char = chunk->memory[token->end];
    chunk->memory[token->end] = '\0';
    EVEL_DEBUG("JSON token type: %s", evel_json_token_strings[token->type]);
    EVEL_DEBUG("JSON token: %s", chunk->memory + token->start);
    chunk->memory[token->end] = temp_char;
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Post a response to the commandList.
   *
   * @param post          Memory chunk in which to post a response.
   *****************************************************************************/
  void evel_command_list_response(MEMORY_CHUNK * const post)
  {
    char * json_post;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(post != NULL);
    assert(post->memory == NULL);
  
    if (evel_provide_throttling_state)
    {
      EVEL_DEBUG("Provide throttling state");
  
      /*************************************************************************/
      /* Encode the response, making it printf-able for debug.                 */
      /*************************************************************************/
      json_post = malloc(EVEL_MAX_JSON_BODY);
      assert(json_post != NULL);
      post->size = evel_json_encode_throttle(json_post, EVEL_MAX_JSON_BODY - 1);
      post->memory = json_post;
      post->memory[post->size] = '\0';
      evel_provide_throttling_state = false;
    }
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Encode the full throttling specification according to AT&T's schema.
   *
   * @param json          Pointer to where to store the JSON encoded data.
   * @param max_size      Size of storage available in json_body.
   * @returns Number of bytes actually written.
   *****************************************************************************/
  int evel_json_encode_throttle(char * const json, const int max_size)
  {
    bool throttled;
    int domain;
    int offset;
    bool domain_added;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(json != NULL);
    assert(max_size > 0);
  
    /***************************************************************************/
    /* Work out if we're throttled.                                            */
    /***************************************************************************/
    throttled = false;
    for (domain = EVEL_DOMAIN_FAULT; domain < EVEL_MAX_DOMAINS; domain++)
    {
      if (evel_throttle_spec[domain] != NULL)
      {
        throttled = true;
      }
    }
  
    /***************************************************************************/
    /* Encode the response.                                                    */
    /***************************************************************************/
    offset = 0;
    offset += snprintf(json + offset, max_size - offset,
                       "{\"eventThrottlingState\": {");
    offset += snprintf(json + offset, max_size - offset,
                       "\"eventThrottlingMode\": \"%s\"",
                       throttled ? "throttled" : "normal");
    if (throttled)
    {
      offset += snprintf(json + offset, max_size - offset,
                         ", \"eventDomainThrottleSpecificationList\": [");
  
      domain_added = false;
      for (domain = EVEL_DOMAIN_FAULT; domain < EVEL_MAX_DOMAINS; domain++)
      {
        if (evel_throttle_spec[domain] != NULL)
        {
          if (domain_added)
          {
            offset += snprintf(json + offset, max_size - offset, ", ");
          }
  
          offset += evel_json_encode_throttle_spec(json + offset,
                                                   max_size - offset,
                                                   domain);
          domain_added = true;
        }
      }
  
      offset += snprintf(json + offset, max_size - offset, "]");
    }
  
    offset += snprintf(json + offset, max_size - offset, "}}");
  
    EVEL_EXIT();
  
    return offset;
  }
  
  /**************************************************************************//**
   * Encode a throttling specification for a domain.
   *
   * @param json          Pointer to where to store the JSON encoded data.
   * @param max_size      Size of storage available in json_body.
   * @returns Number of bytes actually written.
   *****************************************************************************/
  int evel_json_encode_throttle_spec(char * const json,
                                     const int max_size,
                                     const EVEL_EVENT_DOMAINS domain)
  {
    int offset;
    EVEL_THROTTLE_SPEC * throttle_spec;
    DLIST_ITEM * dlist_item;
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(domain >= EVEL_DOMAIN_FAULT);
    assert(domain < EVEL_MAX_DOMAINS);
    assert(evel_throttle_spec[domain] != NULL);
  
    throttle_spec = evel_throttle_spec[domain];
  
    /***************************************************************************/
    /* Encode the domain.                                                      */
    /***************************************************************************/
    offset = 0;
    offset += snprintf(json + offset, max_size - offset,
                       "{");
    offset += snprintf(json + offset, max_size - offset,
                       "\"eventDomain\": \"%s\"",
                       evel_domain_strings[domain]);
  
    /***************************************************************************/
    /* Encode "suppressedFieldNames".                                          */
    /***************************************************************************/
    dlist_item = dlist_get_first(&throttle_spec->suppressed_field_names);
    if (dlist_item != NULL)
    {
      offset += snprintf(json + offset, max_size - offset,
                         ", \"suppressedFieldNames\": [");
      while (dlist_item != NULL)
      {
        char * suppressed_field = dlist_item->item;
        assert(suppressed_field != NULL);
  
        offset += snprintf(json + offset, max_size - offset,
                           "\"%s\"", suppressed_field);
        dlist_item = dlist_get_next(dlist_item);
        if (dlist_item != NULL)
        {
          offset += snprintf(json + offset, max_size - offset, ", ");
        }
      }
  
      offset += snprintf(json + offset, max_size - offset, "]");
    }
  
    /***************************************************************************/
    /* Encode "suppressedNvPairsList".                                         */
    /***************************************************************************/
    dlist_item = dlist_get_first(&throttle_spec->suppressed_nv_pairs_list);
    if (dlist_item != NULL)
    {
      offset += snprintf(json + offset, max_size - offset,
                         ", \"suppressedNvPairsList\": [");
      while (dlist_item != NULL)
      {
        offset += evel_json_encode_nv_pairs(json + offset,
                                            max_size - offset,
                                            dlist_item->item);
        dlist_item = dlist_get_next(dlist_item);
        if (dlist_item != NULL)
        {
          offset += snprintf(json + offset, max_size - offset, ", ");
        }
      }
  
      offset += snprintf(json + offset, max_size - offset, "]");
    }
  
    offset += snprintf(json + offset, max_size - offset, "}");
  
    EVEL_EXIT();
  
    return offset;
  }
  
  /**************************************************************************//**
   * Encode a single "suppressedNvPairsListEntry".
   *
   * @param json          Pointer to where to store the JSON encoded data.
   * @param max_size      Size of storage available in json_body.
   * @returns Number of bytes actually written.
   *****************************************************************************/
  int evel_json_encode_nv_pairs(char * const json,
                                const int max_size,
                                EVEL_SUPPRESSED_NV_PAIRS * nv_pairs)
  {
    DLIST_ITEM * dlist_item;
    char * name;
    int offset;
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(nv_pairs != NULL);
    assert(nv_pairs->nv_pair_field_name != NULL);
    assert(!dlist_is_empty(&nv_pairs->suppressed_nv_pair_names));
  
    /***************************************************************************/
    /* Encode it.                                                              */
    /***************************************************************************/
    offset = 0;
    offset += snprintf(json + offset, max_size - offset, "{");
    offset += snprintf(json + offset, max_size - offset,
                       "\"nvPairFieldName\": \"%s\"",
                       nv_pairs->nv_pair_field_name);
    dlist_item = dlist_get_first(&nv_pairs->suppressed_nv_pair_names);
    offset += snprintf(json + offset, max_size - offset,
                       ", \"suppressedNvPairNames\": [");
    while (dlist_item != NULL)
    {
      name = dlist_item->item;
      assert(name != NULL);
      offset += snprintf(json + offset, max_size - offset, "\"%s\"", name);
      dlist_item = dlist_get_next(dlist_item);
      if (dlist_item != NULL)
      {
        offset += snprintf(json + offset, max_size - offset, ", ");
      }
    }
    offset += snprintf(json + offset, max_size - offset, "]");
    offset += snprintf(json + offset, max_size - offset, "}");
  
    EVEL_EXIT();
  
    return offset;
  }
  
  /**************************************************************************//**
   * Method called when we open a "command" object.
   *****************************************************************************/
  void evel_open_command()
  {
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Make some assertions.                                                   */
    /***************************************************************************/
    assert(evel_command_type_value == NULL);
    assert(evel_measurement_interval_value == NULL);
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Method called when we close a "command" object.
   *****************************************************************************/
  void evel_close_command()
  {
    EVEL_ENTER();
  
    /***************************************************************************/
    /* If a commandType was provided, fan out and handle it now what we have   */
    /* fathered all related information.                                       */
    /*                                                                         */
    /* Note that we handle throttling specification and measurement interval   */
    /* updates immediately on closing the command (not the list). We could     */
    /* reject *all* commands in a list if any of them are invalid, but we are  */
    /* take a best-effort strategy here - any valid-looking command gets       */
    /* implemented regardless of what follows.                                 */
    /***************************************************************************/
    if (evel_command_type_value != NULL)
    {
      EVEL_DEBUG("Closing command %s", evel_command_type_value);
  
      if (strcmp(evel_command_type_value, "provideThrottlingState") == 0)
      {
        evel_provide_throttling_state = true;
      }
      else if (strcmp(evel_command_type_value, "throttlingSpecification") == 0)
      {
        evel_set_throttling_spec();
      }
      else if (strcmp(evel_command_type_value, "measurementIntervalChange") == 0)
      {
        evel_set_measurement_interval();
      }
      else
      {
        EVEL_ERROR("Ignoring unknown commandType: %s\n",
                   evel_command_type_value);
      }
  
      /*************************************************************************/
      /* Free the captured "commandType" value.                                */
      /*************************************************************************/
      free(evel_command_type_value);
      evel_command_type_value = NULL;
    }
  
    /***************************************************************************/
    /* There could be an unused working throttle spec at this point - if the   */
    /* "throttlingSpecification" commandType was not provided, or an invalid   */
    /* domain was provided, or was not provided at all.                        */
    /***************************************************************************/
    if (evel_temp_throttle != NULL)
    {
      evel_throttle_free(evel_temp_throttle);
      evel_temp_throttle = NULL;
    }
  
    /***************************************************************************/
    /* Similarly, the domain could be set.                                     */
    /***************************************************************************/
    evel_throttle_spec_domain = EVEL_MAX_DOMAINS;
  
    /***************************************************************************/
    /* There could be an unused measurement interval value at this point - if  */
    /* the "measurementIntervalChange" command was not provided.               */
    /***************************************************************************/
    if (evel_measurement_interval_value != NULL)
    {
      free(evel_measurement_interval_value);
      evel_measurement_interval_value = NULL;
    }
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Set the provided throttling specification, when the command closes.
   *****************************************************************************/
  void evel_set_throttling_spec()
  {
    EVEL_ENTER();
  
    if ((evel_throttle_spec_domain >= 0) &&
        (evel_throttle_spec_domain < EVEL_MAX_DOMAINS))
    {
      EVEL_DEBUG("Updating throttle spec for domain: %s",
                 evel_domain_strings[evel_throttle_spec_domain]);
  
      /*************************************************************************/
      /* Free off the previous throttle specification for the domain, if there */
      /* is one.                                                               */
      /*************************************************************************/
      if (evel_throttle_spec[evel_throttle_spec_domain] != NULL)
      {
        evel_throttle_free(evel_throttle_spec[evel_throttle_spec_domain]);
      }
  
      /*************************************************************************/
      /* Finalize the working throttling spec, if there is one.                */
      /*************************************************************************/
      if (evel_temp_throttle != NULL)
      {
        evel_throttle_finalize(evel_temp_throttle);
      }
  
      /*************************************************************************/
      /* Replace the throttle specification for the domain with the working    */
      /* throttle specification.  This could be NULL, if an empty throttle     */
      /* specification has been received for a domain.                         */
      /*************************************************************************/
      evel_throttle_spec[evel_throttle_spec_domain] = evel_temp_throttle;
      evel_temp_throttle = NULL;
    }
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Set the provided measurement interval, when the command closes.
   *****************************************************************************/
  void evel_set_measurement_interval()
  {
    EVEL_ENTER();
  
    if (evel_measurement_interval_value != NULL)
    {
      const long int value = strtol(evel_measurement_interval_value, NULL, 10);
  
      if ((value >= 0) && (value <= INT_MAX))
      {
        /***********************************************************************/
        /* Lock, update, unlock.                                               */
        /***********************************************************************/
        EVEL_DEBUG("Updating measurement interval to %d\n", value);
  
        pthread_mutex_lock(&evel_measurement_interval_mutex);
        evel_measurement_interval = value;
        pthread_mutex_unlock(&evel_measurement_interval_mutex);
      }
      else
      {
        EVEL_ERROR("Ignoring invalid measurement interval: %s",
                   evel_measurement_interval_value);
      }
    }
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Method called when we open an "eventDomainThrottleSpecification" object.
   *****************************************************************************/
  void evel_open_throttle_spec()
  {
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(evel_throttle_spec_domain_value == NULL);
    assert(evel_throttle_spec_domain == EVEL_MAX_DOMAINS);
    assert(evel_temp_throttle == NULL);
  
    /***************************************************************************/
    /* Allocate and initialize an ::EVEL_THROTTLE_SPEC in which to hold        */
    /* captured JSON elements.                                                 */
    /***************************************************************************/
    evel_temp_throttle = malloc(sizeof(EVEL_THROTTLE_SPEC));
    assert(evel_temp_throttle != NULL);
    dlist_initialize(&evel_temp_throttle->suppressed_field_names);
    dlist_initialize(&evel_temp_throttle->suppressed_nv_pairs_list);
    evel_temp_throttle->hash_field_names = NULL;
    evel_temp_throttle->hash_nv_pairs_list = NULL;
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Method called when we close an "eventDomainThrottleSpecification" object.
   *****************************************************************************/
  void evel_close_throttle_spec()
  {
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Decode, free and blank a captured event domain value.                   */
    /***************************************************************************/
    if (evel_throttle_spec_domain_value != NULL)
    {
      evel_throttle_spec_domain =
                             evel_decode_domain(evel_throttle_spec_domain_value);
      free(evel_throttle_spec_domain_value);
      evel_throttle_spec_domain_value = NULL;
    }
  
    /***************************************************************************/
    /* Free off an empty working throttle spec, to stop it being used.  This   */
    /* state should be represented by a NULL pointer for the domain.           */
    /***************************************************************************/
    if (evel_temp_throttle != NULL)
    {
      if (dlist_is_empty(&evel_temp_throttle->suppressed_field_names) &&
          dlist_is_empty(&evel_temp_throttle->suppressed_nv_pairs_list))
      {
        free(evel_temp_throttle);
        evel_temp_throttle = NULL;
      }
    }
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Convert a value for an "eventDomain" into an ::EVEL_EVENT_DOMAINS.
   *
   * @param domain_value  The domain string value to decode.
   * @returns The matching ::EVEL_EVENT_DOMAINS, or ::EVEL_MAX_DOMAINS on error.
   *****************************************************************************/
  EVEL_EVENT_DOMAINS evel_decode_domain(char * domain_value)
  {
    EVEL_EVENT_DOMAINS result;
    int ii;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(domain_value != NULL);
  
    result = EVEL_MAX_DOMAINS;
    for (ii = EVEL_DOMAIN_FAULT; ii < EVEL_MAX_DOMAINS; ii++)
    {
      assert(evel_domain_strings[ii] != NULL);
      if (strcmp(evel_domain_strings[ii], domain_value) == 0)
      {
        result = ii;
      }
    }
  
    EVEL_EXIT();
  
    return result;
  }
  
  /**************************************************************************//**
   * Method called when we open a "suppressedNvPairsListEntry" object.
   *****************************************************************************/
  void evel_open_nv_pairs_list_entry()
  {
    EVEL_SUPPRESSED_NV_PAIRS * nv_pairs;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(evel_temp_throttle != NULL);
  
    /***************************************************************************/
    /* Allocate and initialize an ::EVEL_SUPPRESSED_NV_PAIRS, and add it to    */
    /* the list.                                                               */
    /***************************************************************************/
    nv_pairs = malloc(sizeof(EVEL_SUPPRESSED_NV_PAIRS));
    assert(nv_pairs != NULL);
    nv_pairs->nv_pair_field_name = NULL;
    dlist_initialize(&nv_pairs->suppressed_nv_pair_names);
    nv_pairs->hash_nv_pair_names = NULL;
    dlist_push_last(&evel_temp_throttle->suppressed_nv_pairs_list, nv_pairs);
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Method called when we close a "suppressedNvPairsListEntry" object.
   *****************************************************************************/
  void evel_close_nv_pairs_list_entry()
  {
    EVEL_SUPPRESSED_NV_PAIRS * nv_pairs;
    EVEL_SUPPRESSED_NV_PAIRS * popped;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Get the latest nv pairs.  This also performs the required checks.       */
    /***************************************************************************/
    nv_pairs = evel_get_last_nv_pairs();
  
    /***************************************************************************/
    /* For a "suppressedNvPairsListEntry" to have any meaning, we need both    */
    /* "nvPairFieldName" and "suppressedNvPairNames".  If we don't, then pop   */
    /* and free whatever we just collected.                                    */
    /***************************************************************************/
    if ((nv_pairs->nv_pair_field_name == NULL) ||
        dlist_is_empty(&nv_pairs->suppressed_nv_pair_names))
    {
      popped = dlist_pop_last(&evel_temp_throttle->suppressed_nv_pairs_list);
      assert(popped == nv_pairs);
      evel_throttle_free_nv_pair(popped);
    }
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Store an "nvPairFieldName" value in the working throttle spec.
   *
   * @param value         The value to store.
   *****************************************************************************/
  void evel_store_nv_pair_field_name(char * const value)
  {
    EVEL_SUPPRESSED_NV_PAIRS * nv_pairs;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Get the latest nv pairs.  This also performs the required checks.       */
    /***************************************************************************/
    nv_pairs = evel_get_last_nv_pairs();
  
    /***************************************************************************/
    /* Store the value.                                                        */
    /***************************************************************************/
    nv_pairs->nv_pair_field_name = value;
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Store a "suppressedNvPairNames" item in the working throttle spec.
   *
   * @param item          The item to store.
   *****************************************************************************/
  void evel_store_nv_pair_name(char * const item)
  {
    EVEL_SUPPRESSED_NV_PAIRS * nv_pairs;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Get the latest nv pairs.  This also performs the required checks.       */
    /***************************************************************************/
    nv_pairs = evel_get_last_nv_pairs();
  
    /***************************************************************************/
    /* Store the item.                                                         */
    /***************************************************************************/
    dlist_push_last(&nv_pairs->suppressed_nv_pair_names, item);
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Store a "suppressedFieldNames" item in the working throttle spec.
   *
   * @param item          The item to store.
   *****************************************************************************/
  void evel_store_suppressed_field_name(char * const item)
  {
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(evel_temp_throttle != NULL);
  
    /***************************************************************************/
    /* Store the item.                                                         */
    /***************************************************************************/
    dlist_push_last(&evel_temp_throttle->suppressed_field_names, item);
  
    EVEL_EXIT();
  }
  
  /**************************************************************************//**
   * Get the last added suppressed nv pairs list entry in the working spec.
   *
   * @returns The last entry.
   *****************************************************************************/
  EVEL_SUPPRESSED_NV_PAIRS * evel_get_last_nv_pairs()
  {
    DLIST_ITEM * dlist_item;
    EVEL_SUPPRESSED_NV_PAIRS * nv_pairs;
  
    EVEL_ENTER();
  
    /***************************************************************************/
    /* Check preconditions.                                                    */
    /***************************************************************************/
    assert(evel_temp_throttle != NULL);
  
    /***************************************************************************/
    /* Get the pair that was added when we opened the list entry.              */
    /***************************************************************************/
    dlist_item = dlist_get_last(&evel_temp_throttle->suppressed_nv_pairs_list);
    assert(dlist_item != NULL);
    nv_pairs = dlist_item->item;
    assert(nv_pairs != NULL);
  
    EVEL_EXIT();
  
    return nv_pairs;
  }
  