diff options
Diffstat (limited to 'VES5.0/evel/evel-library/code/evel_library/evel_throttle.c')
-rw-r--r-- | VES5.0/evel/evel-library/code/evel_library/evel_throttle.c | 2116 |
1 files changed, 2116 insertions, 0 deletions
diff --git a/VES5.0/evel/evel-library/code/evel_library/evel_throttle.c b/VES5.0/evel/evel-library/code/evel_library/evel_throttle.c new file mode 100644 index 00000000..351c7da7 --- /dev/null +++ b/VES5.0/evel/evel-library/code/evel_library/evel_throttle.c @@ -0,0 +1,2116 @@ +/**************************************************************************//** + * @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; +} |