From bc5cd6daa1601dea3e6ad9c5f6b243324f56c153 Mon Sep 17 00:00:00 2001 From: "onkar.panchare1" Date: Thu, 21 Aug 2025 18:14:42 +0530 Subject: [PATCH] TELEMETRY-2: Dynamic telemetry markers for Dynamic Tables in Telemetry Description: Introduced marker type DataModelTable for dynamic tables in telemetry. Enhanced T2 agent to resolve and collect data from all instances of multi-instance object tables when dynamic markers are configured. Added logic to parse DataModelTable, filter markers based on telemetry profile configuration, and encode them in telemetry reports after collection, ensuring telemetry reports include per-instance data for configured dynamic markers. Added support for an "index" parameter in configuration to restrict data collection to specific indexes if required. Reason for change: Enable telemetry to capture dynamic, per-instance data from tables (e.g., Host table, Wi-Fi associated devices) where indexes are not fixed, ensuring richer and more flexible telemetry collection. Signed-off-by: onkar.panchare1 --- schemas/t2_reportProfileSchema.schema.json | 41 +- source/bulkdata/profile.c | 14 +- source/bulkdata/profile.h | 1 + source/bulkdata/profilexconf.c | 2 +- source/reportgen/reportgen.c | 436 ++++++++++++++++++--- source/reportgen/reportgen.h | 4 +- source/t2parser/t2parser.c | 298 +++++++++++++- source/t2parser/t2parser.h | 1 + source/test/reportgen/reportgenTest.cpp | 26 +- source/utils/t2common.c | 85 ++++ source/utils/t2common.h | 20 + 11 files changed, 849 insertions(+), 79 deletions(-) diff --git a/schemas/t2_reportProfileSchema.schema.json b/schemas/t2_reportProfileSchema.schema.json index 0cc6b60a..1b5733fc 100644 --- a/schemas/t2_reportProfileSchema.schema.json +++ b/schemas/t2_reportProfileSchema.schema.json @@ -95,6 +95,44 @@ } } ] + }, + "dataModelSimple": { + "title": "\"dataModel\" Parameter (restricted for use in dataModelTable)", + "type": "object", + "properties": { + "type": { "type": "string", "const": "dataModel" }, + "reference": { "type": "string" } + }, + "required": ["type", "reference"], + "additionalProperties": false + }, + "dataModelTable": { + "title": "\"dataModelTable\" Parameter", + "type": "object", + "properties": { + "type": { "type": "string", "const": "dataModelTable" }, + "reference": { + "type": "string", + "pattern": ".*\\.$", + "description": "Reference must end with a dot '.'" + }, + "index": { + "type": "string", + "pattern": "^(\\d+(-\\d+)?)(,(\\d+(-\\d+)?))*$", + "description": "Comma separated numbers, ranges (e.g. 1-4), or combinations (e.g. 1,2,5-7)." + }, + "Parameter": { + "type": "array", + "items": { + "oneOf": [ + { "$ref": "#/definitions/parmDefinitions/properties/dataModelSimple" }, + { "$ref": "#/definitions/parmDefinitions/properties/dataModelTable" } + ] + } + } + }, + "required": ["type", "reference", "Parameter"], + "additionalProperties": false } } }, @@ -234,7 +272,8 @@ "oneOf": [ { "$ref": "#/definitions/parmDefinitions/properties/grep", "title": "grep" }, { "$ref": "#/definitions/parmDefinitions/properties/event", "title": "event" }, - { "$ref": "#/definitions/parmDefinitions/properties/dataModel", "title": "dataModel" } + { "$ref": "#/definitions/parmDefinitions/properties/dataModel", "title": "dataModel" }, + { "$ref": "#/definitions/parmDefinitions/properties/dataModelTable", "title": "dataModelTable" } ] }, "description": "An array of objects which defines the data to be included in the generated report. Each object defines the type of data, the source of the data and an optional name to be used as the name (marker) for this data in the generated report. " diff --git a/source/bulkdata/profile.c b/source/bulkdata/profile.c index f5d9bb17..a9d9c227 100644 --- a/source/bulkdata/profile.c +++ b/source/bulkdata/profile.c @@ -29,6 +29,7 @@ #include "t2markers.h" #include "t2log_wrapper.h" #include "busInterface.h" +#include "ccspinterface.h" #include "curlinterface.h" #include "rbusmethodinterface.h" #include "scheduler.h" @@ -195,6 +196,10 @@ static void freeProfile(void *data) Vector_Destroy(profile->cachedReportList, free); profile->cachedReportList = NULL; } + if(profile->dataModelTableList) + { + Vector_Destroy(profile->dataModelTableList, freeDataModelTable); + } if(profile->jsonReportObj) { cJSON_Delete(profile->jsonReportObj); @@ -445,7 +450,14 @@ static void* CollectAndReport(void* data) profileParamVals = getProfileParameterValues(profile->paramList, count); if(profileParamVals != NULL) { - encodeParamResultInJSON(valArray, profile->paramList, profileParamVals); + if (Vector_Size(profile->dataModelTableList) > 0) + { + encodeParamResultInJSON(valArray, profile->paramList, profileParamVals, profile->dataModelTableList); + } + else + { + encodeParamResultInJSON(valArray, profile->paramList, profileParamVals, NULL); + } } Vector_Destroy(profileParamVals, freeProfileValues); } diff --git a/source/bulkdata/profile.h b/source/bulkdata/profile.h index 574e6558..53463c43 100644 --- a/source/bulkdata/profile.h +++ b/source/bulkdata/profile.h @@ -80,6 +80,7 @@ typedef struct _Profile Vector *gMarkerList; Vector *topMarkerList; Vector *cachedReportList; + Vector *dataModelTableList; // List of DataModelTable cJSON *jsonReportObj; pthread_t reportThread; pthread_mutex_t triggerCondMutex; diff --git a/source/bulkdata/profilexconf.c b/source/bulkdata/profilexconf.c index 69c94d04..5d9c56a1 100644 --- a/source/bulkdata/profilexconf.c +++ b/source/bulkdata/profilexconf.c @@ -280,7 +280,7 @@ static void* CollectAndReportXconf(void* data) T2Info("Fetch complete for TR-181 Object/Parameter Values for parameters \n"); if(profileParamVals != NULL) { - encodeParamResultInJSON(valArray, profile->paramList, profileParamVals); + encodeParamResultInJSON(valArray, profile->paramList, profileParamVals, NULL); } Vector_Destroy(profileParamVals, freeProfileValues); } diff --git a/source/reportgen/reportgen.c b/source/reportgen/reportgen.c index 88b47051..f8187fb9 100644 --- a/source/reportgen/reportgen.c +++ b/source/reportgen/reportgen.c @@ -216,7 +216,137 @@ static T2ERROR applyRegexToValue(char** inputValue, const char* regexPattern) return T2ERROR_SUCCESS; } -T2ERROR encodeParamResultInJSON(cJSON *valArray, Vector *paramNameList, Vector *paramValueList) +cJSON* findOrCreateArrayItem(cJSON *array, int targetIndex) +{ + int currentSize = cJSON_GetArraySize(array); + // Check if item already exists at any position + for (int i = 0; i < currentSize; i++) + { + cJSON *item = cJSON_GetArrayItem(array, i); + cJSON *indexObj = cJSON_GetObjectItem(item, "index"); + if (indexObj && atoi(indexObj->valuestring) == targetIndex) + { + return item; + } + } + + // If not found, create new item and add to array + cJSON *newItem = cJSON_CreateObject(); + if(newItem == NULL) + { + T2Error("cJSON_CreateObject failed.. arrayItem is NULL \n"); + return NULL; + } + char indexStr[10]; + snprintf(indexStr, sizeof(indexStr), "%d", targetIndex); + if(cJSON_AddStringToObject(newItem, "index", indexStr) == NULL) + { + T2Error("cJSON_AddStringToObject failed.\n"); + cJSON_Delete(newItem); + return NULL; + } + cJSON_AddItemToArray(array, newItem); + return newItem; +} + +//Function to get the basePath like Device.WiFi.AccessPoint. +int getBasePath(const char *input, char *basePath, size_t maxLength) +{ + const char *p = input; + + // Find a digit with . before and after (table index) + while (*p != '\0') + { + if (*(p + 1) != '\0' && *(p + 2) != '\0' && *p == '.' && isdigit((unsigned char) * (p + 1)) && *(p + 2) == '.') + { + size_t length = p - input + 1; // include the dot + if (length < maxLength) + { + strncpy(basePath, input, length); + basePath[length] = '\0'; + return 0; + } + else + { + return -1; + } + } + p++; + } + + // If not found, then the whole string - Fallback + size_t length = strlen(input); + + // Check if the base path fits within the buffer + if (length < maxLength) + { + strncpy(basePath, input, length); + basePath[length] = '\0'; // Null-terminate the string + return 0; // Success + } + else + { + // If truncation would occur, return failure + return -1; // Failure + } +} + +/** + * @brief Finds the best-matching DataModelTable entry based on a full parameter path. + * + * This function searches the given list of DataModelTables and returns the most specific + * table whose reference string is a prefix of the provided full parameter string. + * It ensures that the prefix match is on a valid boundary by checking the next character + * (must be either end-of-string, '.', or a digit indicating table index). + * + * @param dataModelTableList Pointer to a vector containing DataModelTable entries. + * @param fullParam Full TR-181 style parameter path to search for. + * + * @return Pointer to the best-matching DataModelTable, or NULL if no match is found. + * + * Example: + * fullParam = "Device.WiFi.AccessPoint.1.AssociatedDevice.2.MACAddress" + * matching table->reference = "Device.WiFi.AccessPoint." + */ +DataModelTable *findTableByReference(Vector *dataModelTableList, const char *fullParam) +{ + DataModelTable *table = NULL; + if (dataModelTableList != NULL && Vector_Size(dataModelTableList) > 0) + { + size_t bestMatchLength = 0; + for (size_t tableCount = 0; tableCount < Vector_Size(dataModelTableList); tableCount++) + { + DataModelTable *tbl = (DataModelTable *)Vector_At(dataModelTableList, tableCount); + if (!tbl || !tbl->reference) + { + continue; + } + if (strncmp(fullParam, tbl->reference, strlen(tbl->reference)) == 0) + { + char nextChar = fullParam[strlen(tbl->reference)]; + if (nextChar == '\0' || nextChar == '.' || isdigit(nextChar)) + { + if (strlen(tbl->reference) > bestMatchLength) + { + table = tbl; + bestMatchLength = strlen(tbl->reference); + T2Debug("Match found! table->reference = %s, fullParam = %s\n", table->reference, fullParam); + } + } + } + } + } + return table; +} + +bool isDataModelTable(const char *paramName) +{ + // Checking if the parameter name ends with a dot '.' + size_t paramNameLength = strlen(paramName); + return (paramNameLength > 0 && paramName[paramNameLength - 1] == '.'); +} + +T2ERROR encodeParamResultInJSON(cJSON *valArray, Vector *paramNameList, Vector *paramValueList, Vector *dataModelTableList) { if(valArray == NULL || paramNameList == NULL || paramValueList == NULL) { @@ -263,7 +393,7 @@ T2ERROR encodeParamResultInJSON(cJSON *valArray, Vector *paramNameList, Vector * continue ; } } - else if(paramValCount == 1) // Single value + else if(paramValCount == 1 && !isDataModelTable(param->name)) // Single value { if(paramValues[0]) { @@ -324,87 +454,271 @@ T2ERROR encodeParamResultInJSON(cJSON *valArray, Vector *paramNameList, Vector * } else { - cJSON *valList = NULL; - cJSON *valItem = NULL; - int valIndex = 0; - bool isTableEmpty = true ; - cJSON *arrayItem = cJSON_CreateObject(); - if(arrayItem == NULL) + if (((dataModelTableList != NULL) && (Vector_Size(dataModelTableList) > 0))) { - T2Error("cJSON_CreateObject failed.. arrayItem is NULL \n"); - return T2ERROR_FAILURE; - } - cJSON_AddItemToObject(arrayItem, param->name, valList = cJSON_CreateArray()); - for (; valIndex < paramValCount; valIndex++) - { - if(paramValues[valIndex]) + int valIndex = 0; + bool isTableEmpty = true; + cJSON *arrayItem = NULL; // Initialize to NULL + bool isNewPattern = true; + cJSON *existingItem = NULL; + + char basePattern[512]; + int result = getBasePath(param->name, basePattern, sizeof(basePattern)); + if (result != 0) + { + T2Error("basePattern is either truncated or invalid."); + return T2ERROR_FAILURE; + } + + if (valArray == NULL || cJSON_GetArraySize(valArray) == 0) + { + T2Error("ValArray is NULL\n"); + } + else { - if(param->reportEmptyParam || !checkForEmptyString(paramValues[0]->parameterValue)) + int arraySize = cJSON_GetArraySize(valArray); + for (int i = 0; i < arraySize; i++) { - valItem = cJSON_CreateObject(); - if(valItem == NULL) + cJSON *existingObj = cJSON_GetArrayItem(valArray, i); + if (existingObj && cJSON_HasObjectItem(existingObj, basePattern)) { - T2Error("cJSON_CreateObject failed.. valItem is NULL \n"); - cJSON_Delete(arrayItem); - return T2ERROR_FAILURE; + existingItem = existingObj; + T2Debug("Found existing object for pattern: %s\n", basePattern); + isNewPattern = false; + break; } - if(param->trimParam) - { - trimLeadingAndTrailingws(paramValues[valIndex]->parameterValue); - } - if(param->regexParam != NULL) + } + } + + if (isNewPattern) + { + T2Debug("Creating new object for pattern: %s\n", basePattern); + arrayItem = cJSON_CreateObject(); + if (arrayItem == NULL) + { + T2Error("cJSON_CreateObject failed.. arrayItem is NULL \n"); + return T2ERROR_FAILURE; + } + } + else + { + arrayItem = existingItem; + } + + DataModelTable *table = findTableByReference(dataModelTableList, param->name); + if (table != NULL) + { + + for (; valIndex < paramValCount; valIndex++) + { + if (!checkForEmptyString(paramValues[valIndex]->parameterValue)) { - regex_t regpattern; - int rc = 0; - size_t nmatch = 1; - regmatch_t pmatch[2]; - char string[256] = {'\0'}; - rc = regcomp(®pattern, param->regexParam, REG_EXTENDED); - if(rc != 0) + char *parameterName = strdup(paramValues[valIndex]->parameterName); + char *parameterWild = strdup(paramValues[valIndex]->parameterName); + size_t basePathLength = strlen(basePattern); + cJSON *currentObject = arrayItem; + char concatenatedKey[256] = ""; + bool firstIndexHandled = false; + bool parameterConfigured = false; + char *token = strtok(parameterName + basePathLength, "."); + + + //Filtering the list based on DataModelParam List + for (size_t listCount = 0; listCount < Vector_Size(table->paramList); listCount++) { - T2Warning("regcomp() failed, returning nonzero (%d)\n", rc); + DataModelParam *list = (DataModelParam *)Vector_At(table->paramList, listCount); + if (parameterWild && matchesParameter(list->name, parameterWild)) + { + T2Debug("Match found!: list->name = %s, parameterWild = %s\n", list->name, parameterWild); + parameterConfigured = true; + break; // Parameter found, proceed with processing + } } - else + + // If parameter is not configured, skip processing this parameter + if (!parameterConfigured) { - T2Debug("regcomp() successful, returning value (%d)\n", rc); - rc = regexec(®pattern, paramValues[valIndex]->parameterValue, nmatch, pmatch, 0); - if(rc != 0) + if (parameterName) { - T2Warning("regexec() failed, Failed to match '%s' with '%s',returning %d.\n", paramValues[valIndex]->parameterValue, param->regexParam, rc); - free(paramValues[valIndex]->parameterValue); - paramValues[valIndex]->parameterValue = strdup(""); + free(parameterName); // Free if allocated + } + if (parameterWild) + { + free(parameterWild); // Free if allocated + } + continue; + } + + + while (token != NULL) + { + char *nextToken = strtok(NULL, "."); + if (nextToken == NULL) + { + // Adding final parameter + if (cJSON_AddStringToObject(currentObject, token, paramValues[valIndex]->parameterValue) == NULL) + { + T2Error("cJSON_AddStringToObject failed.\n"); + cJSON_Delete(currentObject); + return T2ERROR_FAILURE; + } + } + else if (isdigit(token[0])) + { + // Handle array item + int index = atoi(token); + if (!firstIndexHandled) + { + strcpy(concatenatedKey, basePattern); + } + // Create or get array + cJSON *array = cJSON_GetObjectItem(currentObject, concatenatedKey); + if (array == NULL) + { + array = cJSON_CreateArray(); + cJSON_AddItemToObject(currentObject, concatenatedKey, array); + } + // Find or create array item at correct position + currentObject = findOrCreateArrayItem(array, index); + concatenatedKey[0] = '\0'; + firstIndexHandled = true; } else { - T2Debug("regexec successful, Match is found %.*s\n", pmatch[0].rm_eo - pmatch[0].rm_so, ¶mValues[valIndex]->parameterValue[pmatch[0].rm_so]); - sprintf(string, "%.*s", pmatch[0].rm_eo - pmatch[0].rm_so, ¶mValues[valIndex]->parameterValue[pmatch[0].rm_so]); - free(paramValues[valIndex]->parameterValue); - paramValues[valIndex]->parameterValue = strdup(string); + // Build concatenated key + if (concatenatedKey[0] != '\0') + { + strcat(concatenatedKey, "."); + } + strcat(concatenatedKey, token); + // Handle nested object creation only after the first index is done + if (firstIndexHandled && nextToken != NULL && !isdigit(nextToken[0])) + { + cJSON *nestedObject = cJSON_GetObjectItem(currentObject, concatenatedKey); + if (nestedObject == NULL) + { + nestedObject = cJSON_CreateObject(); + if (nestedObject == NULL) + { + T2Error("cJSON_CreateObject failed.. nestedObject is NULL \n"); + return T2ERROR_FAILURE; + } + cJSON_AddItemToObject(currentObject, concatenatedKey, nestedObject); + } + currentObject = nestedObject; + concatenatedKey[0] = '\0'; + } } - regfree(®pattern); + token = nextToken; + } // end of while + + if (parameterName) + { + free(parameterName); // Free if allocated } + if (parameterWild) + { + free(parameterWild); // Free if allocated + } + isTableEmpty = false; } + } // end of for + } // Table not NULL - if(cJSON_AddStringToObject(valItem, paramValues[valIndex]->parameterName, paramValues[valIndex]->parameterValue) == NULL) + if (isNewPattern && !isTableEmpty) + { + cJSON_AddItemToArray(valArray, arrayItem); + } + else if (isTableEmpty && isNewPattern && arrayItem) + { + cJSON_Delete(arrayItem); + arrayItem = NULL; + } + } + else + { + cJSON *valList = NULL; + cJSON *valItem = NULL; + int valIndex = 0; + bool isTableEmpty = true; + cJSON *arrayItem = cJSON_CreateObject(); + if (arrayItem == NULL) + { + T2Error("cJSON_CreateObject failed.. arrayItem is NULL \n"); + return T2ERROR_FAILURE; + } + cJSON_AddItemToObject(arrayItem, param->name, valList = cJSON_CreateArray()); + for (; valIndex < paramValCount; valIndex++) + { + if (paramValues[valIndex]) + { + if (param->reportEmptyParam || !checkForEmptyString(paramValues[0]->parameterValue)) { - T2Error("cJSON_AddStringToObject failed\n"); - cJSON_Delete(arrayItem); - cJSON_Delete(valItem); - return T2ERROR_FAILURE; + valItem = cJSON_CreateObject(); + if (valItem == NULL) + { + T2Error("cJSON_CreateObject failed.. valItem is NULL \n"); + cJSON_Delete(arrayItem); + return T2ERROR_FAILURE; + } + if (param->trimParam) + { + trimLeadingAndTrailingws(paramValues[valIndex]->parameterValue); + } + if (param->regexParam != NULL) + { + regex_t regpattern; + int rc = 0; + size_t nmatch = 1; + regmatch_t pmatch[2]; + char string[256] = {'\0'}; + rc = regcomp(®pattern, param->regexParam, REG_EXTENDED); + if (rc != 0) + { + T2Warning("regcomp() failed, returning nonzero (%d)\n", rc); + } + else + { + T2Debug("regcomp() successful, returning value (%d)\n", rc); + rc = regexec(®pattern, paramValues[valIndex]->parameterValue, nmatch, pmatch, 0); + if (rc != 0) + { + T2Warning("regexec() failed, Failed to match '%s' with '%s',returning %d.\n", paramValues[valIndex]->parameterValue, param->regexParam, rc); + free(paramValues[valIndex]->parameterValue); + paramValues[valIndex]->parameterValue = strdup(""); + } + else + { + T2Debug("regexec successful, Match is found %.*s\n", pmatch[0].rm_eo - pmatch[0].rm_so, ¶mValues[valIndex]->parameterValue[pmatch[0].rm_so]); + sprintf(string, "%.*s", pmatch[0].rm_eo - pmatch[0].rm_so, ¶mValues[valIndex]->parameterValue[pmatch[0].rm_so]); + free(paramValues[valIndex]->parameterValue); + paramValues[valIndex]->parameterValue = strdup(string); + } + regfree(®pattern); + } + } + + if (cJSON_AddStringToObject(valItem, paramValues[valIndex]->parameterName, paramValues[valIndex]->parameterValue) == NULL) + { + T2Error("cJSON_AddStringToObject failed\n"); + cJSON_Delete(arrayItem); + cJSON_Delete(valItem); + return T2ERROR_FAILURE; + } + cJSON_AddItemToArray(valList, valItem); + isTableEmpty = false; } - cJSON_AddItemToArray(valList, valItem); - isTableEmpty = false ; } } - } - if(!isTableEmpty) - { - cJSON_AddItemToArray(valArray, arrayItem); - } - else - { - cJSON_free(arrayItem); + if (!isTableEmpty) + { + cJSON_AddItemToArray(valArray, arrayItem); + } + else + { + cJSON_free(arrayItem); + } } } } diff --git a/source/reportgen/reportgen.h b/source/reportgen/reportgen.h index 4e028819..98e7bd20 100644 --- a/source/reportgen/reportgen.h +++ b/source/reportgen/reportgen.h @@ -67,7 +67,9 @@ void freeProfileValues(void* data); T2ERROR destroyJSONReport(cJSON *jsonObj); -T2ERROR encodeParamResultInJSON(cJSON *valArray, Vector *paramNameList, Vector *paramValueList); +bool isDataModelTable(const char *paramName); + +T2ERROR encodeParamResultInJSON(cJSON *valArray, Vector *paramNameList, Vector *paramValueList, Vector *dataModelTableList); T2ERROR encodeStaticParamsInJSON(cJSON *valArray, Vector *staticParamList); diff --git a/source/t2parser/t2parser.c b/source/t2parser/t2parser.c index 808cc399..7cc11612 100644 --- a/source/t2parser/t2parser.c +++ b/source/t2parser/t2parser.c @@ -839,6 +839,162 @@ void time_param_Reporting_Adjustments_valid_set(Profile *profile, cJSON *jprofil } } +static int buildFullPath(char* fullPath, const char* basePath, const char* reference) +{ + T2Debug("%s ++in\n", __FUNCTION__); + if (!basePath || !reference || !fullPath) + { + T2Error("Invalid input: basePath, reference, or fullPath is NULL\n"); + return -1; // Indicate failure + } + + // Check if reference is already part of basePath + if (strcmp(basePath, reference) == 0) + { + snprintf(fullPath, MAX_PATH_LENGTH, "%s", basePath); + T2Debug("Reference already part of basePath. Full path: %s\n", fullPath); + return 0; // Success + } + + // Construct the full path + int result = snprintf(fullPath, MAX_PATH_LENGTH, "%s%s%s", + basePath, + (basePath[strlen(basePath) - 1] == '.') ? "" : ".", + reference); + + if (result < 0 || (size_t)result >= MAX_PATH_LENGTH) + { + T2Error("Full path exceeded buffer size\n"); + return -1; // Indicate failure + } + + T2Debug("Built full path: %s\n", fullPath); + T2Debug("%s ++out\n", __FUNCTION__); + return 0; // Success +} + +static T2ERROR parseDataModelTableParams(Profile* profile, cJSON* tableItem, const char* parentPath, DataModelTable* parentTable) +{ + T2Debug("%s ++in\n", __FUNCTION__); + if (!tableItem || !parentPath || !profile) + { + T2Error("Invalid input parameters\n"); + return T2ERROR_FAILURE; + } + + T2Debug("Processing table with parent path: %s\n", parentPath); + + cJSON* jpReference = cJSON_GetObjectItem(tableItem, "reference"); + cJSON* jpIndex = cJSON_GetObjectItem(tableItem, "index"); + cJSON* jpParameters = cJSON_GetObjectItem(tableItem, "Parameter"); + + if (!jpReference || !jpParameters) + { + T2Error("Incomplete dataModelTable configuration\n"); + return T2ERROR_FAILURE; + } + + DataModelTable* currentTable = NULL; + // Create table only if this is the first call (no parent table passed) + if (!parentTable) + { + currentTable = (DataModelTable*)malloc(sizeof(DataModelTable)); + if (!currentTable) + { + T2Error("Failed to allocate memory for DataModelTable\n"); + return T2ERROR_FAILURE; + } + + // Initialize root table + currentTable->reference = strdup(jpReference->valuestring); + currentTable->index = jpIndex ? strdup(jpIndex->valuestring) : NULL; + Vector_Create(¤tTable->paramList); + + if (!profile->dataModelTableList) + { + Vector_Create(&profile->dataModelTableList); + } + Vector_PushBack(profile->dataModelTableList, currentTable); + } + else + { + // Use parent table for nested parameters + currentTable = parentTable; + } + + // Build the current path including wildcard + char currentPath[MAX_PATH_LENGTH]; + if (buildFullPath(currentPath, parentPath, jpReference->valuestring) != 0) + { + T2Error("Failed to build current path\n"); + return T2ERROR_FAILURE; + } + + char pathWithWildcard[MAX_PATH_LENGTH]; + if ((size_t)snprintf(pathWithWildcard, sizeof(pathWithWildcard), "%s*.", currentPath) >= sizeof(pathWithWildcard)) + { + T2Error("Path with wildcard exceeded buffer size\n"); + return T2ERROR_FAILURE; + } + + // Process parameters + int paramCount = cJSON_GetArraySize(jpParameters); + for (int i = 0; i < paramCount; i++) + { + cJSON* paramItem = cJSON_GetArrayItem(jpParameters, i); + if (!paramItem) + { + continue; + } + + cJSON* jpType = cJSON_GetObjectItem(paramItem, "type"); + cJSON* jpParamRef = cJSON_GetObjectItem(paramItem, "reference"); + + if (!jpType || !jpParamRef) + { + continue; + } + + if (strcmp(jpType->valuestring, "dataModelTable") == 0) + { + // Recursive call for nested tables + parseDataModelTableParams(profile, paramItem, pathWithWildcard, currentTable); + } + else if (strcmp(jpType->valuestring, "dataModel") == 0) + { + // Create DataModelParam + DataModelParam* param = (DataModelParam*)malloc(sizeof(DataModelParam)); + if (!param) + { + continue; + } + + param->reference = strdup(jpParamRef->valuestring); + //char* fullPath = buildFullPath(pathWithWildcard, jpParamRef->valuestring); + char fullPath[MAX_PATH_LENGTH]; + if (buildFullPath(fullPath, pathWithWildcard, jpParamRef->valuestring) != 0) + { + T2Error("Failed to build full path for parameter\n"); + if (param->reference) + { + free(param->reference); + } + free(param); + continue; + } + param->name = strdup(fullPath); + param->reportEmpty = false; + + // Add to table's parameter list + Vector_PushBack(currentTable->paramList, param); + T2Debug("Added parameter: %s\n", fullPath); + } + } + + T2Debug("%s ++out\n", __FUNCTION__); + return T2ERROR_SUCCESS; +} + T2ERROR addParameter_marker_config(Profile* profile, cJSON *jprofileParameter, int ThisProfileParameter_count) { if(profile == NULL || jprofileParameter == NULL) @@ -852,6 +1008,7 @@ T2ERROR addParameter_marker_config(Profile* profile, cJSON *jprofileParameter, i Vector_Create(&profile->gMarkerList); Vector_Create(&profile->topMarkerList); Vector_Create(&profile->cachedReportList); + Vector_Create(&profile->dataModelTableList); profile->grepSeekProfile = createGrepSeekProfile(0); @@ -884,6 +1041,7 @@ T2ERROR addParameter_marker_config(Profile* profile, cJSON *jprofileParameter, i reportEmpty = false; trim = false; rtformat = REPORTTIMESTAMP_NONE; + int index_flag = 0; cJSON* pSubitem = cJSON_GetArrayItem(jprofileParameter, ProfileParameterIndex); if(pSubitem != NULL) @@ -971,6 +1129,124 @@ T2ERROR addParameter_marker_config(Profile* profile, cJSON *jprofileParameter, i } } } + else if (!(strcmp(paramtype, "dataModelTable"))) + { + T2Debug("Processing dataModelTable configuration\n"); + char basePath[256] = ""; + char index[64] = ""; + cJSON *jpBaseRef = cJSON_GetObjectItem(pSubitem, "reference"); + if (jpBaseRef) + { + strncpy(basePath, jpBaseRef->valuestring, sizeof(basePath) - 1); + basePath[sizeof(basePath) - 1] = '\0'; + T2Debug("Base path for data model table: %s\n", basePath); + cJSON *jpIndex = cJSON_GetObjectItem(pSubitem, "index"); + if (jpIndex) + { + strncpy(index, jpIndex->valuestring, sizeof(index) - 1); + index[sizeof(index) - 1] = '\0'; + int i = 0, j = 0; + while (index[i]) + { + // removing whitespaces from index + if (!(index[i] == ' ' || index[i] == '\t' || index[i] == '\n' || + index[i] == '\r' || index[i] == '\v' || index[i] == '\f')) + { + index[j++] = index[i]; + } + i++; + } + index[j] = '\0'; + int duplicate[256] = {0}; + char *token = strtok(index, ","); + while (token != NULL) + { + int start, end; + if (sscanf(token, "%d-%d", &start, &end) == 2) // if the token consists - then loop through range + { + for (int i = start; i <= end; ++i) + { + if (i < 0 || i >= 256) + { + continue; // skip invalid indices + } + if (duplicate[i]) + { + continue; // skip duplicates + } + duplicate[i] = 1; + T2Debug("Processing index : %d\n", i); + char basePathWithIndex[256]; + int written = snprintf(basePathWithIndex, sizeof(basePathWithIndex), "%s%d.", basePath, i); + if (written < 0 || (size_t)written >= sizeof(basePathWithIndex)) + { + T2Error("%s: snprintf truncated or failed while building path: '%s'\n", __FUNCTION__, basePathWithIndex); + } + paramtype = "dataModel"; + ret = addParameter(profile, basePathWithIndex, basePathWithIndex, logfile, skipFrequency, firstSeekFromEOF, paramtype, use, reportEmpty, rtformat, trim, regex); // add Multiple Report Profile Parameter + if (ret != T2ERROR_SUCCESS) + { + T2Error("%s Error in adding parameter to profile %s\n", + __FUNCTION__, basePathWithIndex); + } + } + } + else + { + int val = atoi(token); // single index + if (val < 0 || val >= 256) + { + token = strtok(NULL, ","); + continue; + } + if (duplicate[val]) + { + token = strtok(NULL, ","); + continue; + } + duplicate[val] = 1; + T2Debug("Processing index : %d\n", val); + char basePathWithIndex[256]; + int written = snprintf(basePathWithIndex, sizeof(basePathWithIndex), "%s%d.", basePath, val); + if (written < 0 || (size_t)written >= sizeof(basePathWithIndex)) + { + T2Error("%s: snprintf truncated or failed while building path: '%s'\n", __FUNCTION__, basePathWithIndex); + } + paramtype = "dataModel"; + ret = addParameter(profile, basePathWithIndex, basePathWithIndex, logfile, skipFrequency, firstSeekFromEOF, paramtype, use, reportEmpty, rtformat, trim, regex); // add Multiple Report Profile Parameter + if (ret != T2ERROR_SUCCESS) + { + T2Error("%s Error in adding parameter to profile %s\n", __FUNCTION__, basePathWithIndex); + } + } + token = strtok(NULL, ","); // Get the next token + } + index_flag = 1; // Do not call the addParameter function again if the profile is configured with the index. + } + else + { + content = strdup(basePath); + header = strdup(basePath); + paramtype = "dataModel"; + if (!content || !header) + { + T2Error("Memory allocation failed for content/header\n"); + free(content); + free(header); + continue; + } + } + T2ERROR ret = parseDataModelTableParams(profile, pSubitem, basePath, NULL); + if (ret != T2ERROR_SUCCESS) + { + T2Error("Failed to parse data model table configuration\n"); + } + } + else + { + T2Error("Missing reference in dataModelTable configuration\n"); + } + } else if(!(strcmp(paramtype, "event"))) { @@ -1047,7 +1323,7 @@ T2ERROR addParameter_marker_config(Profile* profile, cJSON *jprofileParameter, i T2Debug("%s : reportTimestamp = %d\n", __FUNCTION__, rtformat); //CID 337454: Explicit null dereferenced (FORWARD_NULL) ;CID 337448: Explicit null dereferenced (FORWARD_NULL) - if (content != NULL && header != NULL) + if (content != NULL && header != NULL && index_flag == 0) { ret = addParameter(profile, header, content, logfile, skipFrequency, firstSeekFromEOF, paramtype, use, reportEmpty, rtformat, trim, regex); //add Multiple Report Profile Parameter } @@ -1059,12 +1335,32 @@ T2ERROR addParameter_marker_config(Profile* profile, cJSON *jprofileParameter, i if(ret != T2ERROR_SUCCESS) { T2Error("%s Error in adding parameter to profile %s \n", __FUNCTION__, profile->name); + if (content) + { + free(content); + content = NULL; + } + if (header) + { + free(header); + header = NULL; + } continue; } else { T2Debug("[[Added parameter:%s]]\n", header); } + if (content) + { + free(content); + content = NULL; + } + if (header) + { + free(header); + header = NULL; + } profileParamCount++; } } diff --git a/source/t2parser/t2parser.h b/source/t2parser/t2parser.h index 05b1ce9c..55610a8f 100644 --- a/source/t2parser/t2parser.h +++ b/source/t2parser/t2parser.h @@ -27,6 +27,7 @@ #include "telemetry2_0.h" #include "profile.h" #include "msgpack.h" +#define MAX_PATH_LENGTH 512 #define T2REPORTCOMPONENT "RBUS_SUBSCRIPTION" //TR-181 event's component name diff --git a/source/test/reportgen/reportgenTest.cpp b/source/test/reportgen/reportgenTest.cpp index dd727ce3..7bdf0f15 100644 --- a/source/test/reportgen/reportgenTest.cpp +++ b/source/test/reportgen/reportgenTest.cpp @@ -93,9 +93,9 @@ TEST(ENCODEPARAMRESINJSON, NULL_CHECK) Vector_Create(¶mVlist); Vector_PushBack(paramNlist, (void*)strdup("param1")); Vector_PushBack(paramVlist, (void*)strdup("value1")); - EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeParamResultInJSON(NULL, paramNlist, paramVlist)); - EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeParamResultInJSON(valarray, NULL, paramVlist)); - EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeParamResultInJSON(valarray, paramNlist, NULL)); + EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeParamResultInJSON(NULL, paramNlist, paramVlist, NULL)); + EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeParamResultInJSON(valarray, NULL, paramVlist, NULL)); + EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeParamResultInJSON(valarray, paramNlist, NULL, NULL)); Vector_Destroy(paramNlist, free); Vector_Destroy(paramVlist, free); cJSON_Delete(valarray); @@ -503,7 +503,7 @@ TEST_F(reportgenTestFixture, encodeParamResultInJSON) EXPECT_CALL(*m_reportgenMock, cJSON_CreateObject()) .Times(1) .WillOnce(Return(mockobj)); - EXPECT_EQ(T2ERROR_FAILURE, encodeParamResultInJSON(valArray,paramNameList,paramValueList)); + EXPECT_EQ(T2ERROR_FAILURE, encodeParamResultInJSON(valArray,paramNameList,paramValueList,NULL)); cJSON_Delete(valArray); if(valArray != NULL) { @@ -542,7 +542,7 @@ TEST_F(reportgenTestFixture, encodeParamResultInJSON1) EXPECT_CALL(*m_reportgenMock, cJSON_CreateObject()) .Times(1) .WillOnce(Return(mockobj)); - EXPECT_EQ(T2ERROR_FAILURE, encodeParamResultInJSON(valArray,paramNameList,paramValueList)); + EXPECT_EQ(T2ERROR_FAILURE, encodeParamResultInJSON(valArray,paramNameList,paramValueList,NULL)); cJSON_Delete(valArray); if(valArray != NULL) { @@ -590,7 +590,7 @@ TEST_F(reportgenTestFixture, encodeParamResultInJSON2) EXPECT_CALL(*m_reportgenMock, cJSON_CreateObject()) .Times(1) .WillOnce(::testing::ReturnNull()); - EXPECT_EQ(T2ERROR_FAILURE, encodeParamResultInJSON(valArray,paramNameList,paramValueList)); + EXPECT_EQ(T2ERROR_FAILURE, encodeParamResultInJSON(valArray,paramNameList,paramValueList,NULL)); cJSON_Delete(valArray); if(valArray != NULL) { @@ -643,7 +643,7 @@ TEST_F(reportgenTestFixture, encodeParamResultInJSON3) EXPECT_CALL(*m_reportgenMock, cJSON_CreateArray()) .Times(1) .WillOnce(Return(mockobj)); - EXPECT_EQ(T2ERROR_FAILURE, encodeParamResultInJSON(valArray,paramNameList,paramValueList)); + EXPECT_EQ(T2ERROR_FAILURE, encodeParamResultInJSON(valArray,paramNameList,paramValueList,NULL)); cJSON_Delete(valArray); if(valArray != NULL) { @@ -685,7 +685,7 @@ TEST_F(reportgenTestFixture, encodeParamResultInJSON4) .Times(1) .WillOnce(::testing::ReturnNull()); - EXPECT_EQ(T2ERROR_FAILURE, encodeParamResultInJSON(valArray,paramNameList,paramValueList)); + EXPECT_EQ(T2ERROR_FAILURE, encodeParamResultInJSON(valArray,paramNameList,paramValueList,NULL)); cJSON_Delete(valArray); if(valArray != NULL) { @@ -726,7 +726,7 @@ TEST_F(reportgenTestFixture, encodeParamResultInJSON5) EXPECT_CALL(*m_reportgenMock, cJSON_AddStringToObject(_,_,_)) .Times(1) .WillOnce(::testing::ReturnNull()); - EXPECT_EQ(T2ERROR_FAILURE, encodeParamResultInJSON(valArray,paramNameList,paramValueList)); + EXPECT_EQ(T2ERROR_FAILURE, encodeParamResultInJSON(valArray,paramNameList,paramValueList,NULL)); cJSON_Delete(valArray); if(valArray != NULL) { @@ -784,7 +784,7 @@ TEST_F(reportgenTestFixture, encodeParamResultInJSON6) EXPECT_CALL(*m_reportgenMock, cJSON_AddStringToObject(_,_,_)) .Times(1) .WillOnce(::testing::ReturnNull()); - EXPECT_EQ(T2ERROR_FAILURE, encodeParamResultInJSON(valArray,paramNameList,paramValueList)); + EXPECT_EQ(T2ERROR_FAILURE, encodeParamResultInJSON(valArray,paramNameList,paramValueList,NULL)); cJSON_Delete(valArray); if(valArray != NULL) { @@ -828,7 +828,7 @@ TEST_F(reportgenTestFixture, encodeParamResultInJSON7) EXPECT_CALL(*m_reportgenMock, cJSON_AddItemToArray(_,_)) .Times(1) .WillOnce(Return(true)); - EXPECT_EQ(T2ERROR_SUCCESS, encodeParamResultInJSON(valArray,paramNameList,paramValueList)); + EXPECT_EQ(T2ERROR_SUCCESS, encodeParamResultInJSON(valArray,paramNameList,paramValueList,NULL)); cJSON_Delete(valArray); if(valArray != NULL) { @@ -875,7 +875,7 @@ TEST_F(reportgenTestFixture, encodeParamResultInJSON8) EXPECT_CALL(*m_reportgenMock, cJSON_AddItemToArray(_,_)) .Times(1) .WillOnce(Return(true)); - EXPECT_EQ(T2ERROR_SUCCESS, encodeParamResultInJSON(valArray,paramNameList,paramValueList)); + EXPECT_EQ(T2ERROR_SUCCESS, encodeParamResultInJSON(valArray,paramNameList,paramValueList,NULL)); cJSON_Delete(valArray); if(valArray != NULL) { @@ -957,7 +957,7 @@ TEST_F(reportgenTestFixture, encodeParamResultInJSON10) .WillOnce(Return(true)) .WillOnce(Return(true)) .WillOnce(Return(true)); - EXPECT_EQ(T2ERROR_SUCCESS, encodeParamResultInJSON(valArray,paramNameList,paramValueList)); + EXPECT_EQ(T2ERROR_SUCCESS, encodeParamResultInJSON(valArray,paramNameList,paramValueList,NULL)); cJSON_Delete(valArray); if(valArray != NULL) { diff --git a/source/utils/t2common.c b/source/utils/t2common.c index b125d0f7..036561c7 100644 --- a/source/utils/t2common.c +++ b/source/utils/t2common.c @@ -50,6 +50,44 @@ void freeParam(void *data) } } +void freeDataModelParam(void *data) +{ + if (data) + { + DataModelParam *param = (DataModelParam *)data; + if (param->reference) + { + free(param->reference); + } + if (param->name) + { + free(param->name); + } + free(param); + } +} + +void freeDataModelTable(void *data) +{ + if (data) + { + DataModelTable *table = (DataModelTable *)data; + if (table->reference) + { + free(table->reference); + } + if (table->index) + { + free(table->index); + } + if (table->paramList) + { + Vector_Destroy(table->paramList, freeDataModelParam); + } + free(table); + } +} + void freeStaticParam(void *data) { if(data != NULL) @@ -301,3 +339,50 @@ bool isWhoAmiEnabled(void) { return whoami_support; } + +// Function to check if configured parameter matches actual RBUS parameter +bool matchesParameter(const char* pattern, const char* paramName) +{ + if (!pattern || !paramName) + { + return false; + } + + const char* lastPatternSegment = strrchr(pattern, '.'); + const char* lastParamSegment = strrchr(paramName, '.'); + + if (lastPatternSegment && lastParamSegment) + { + if (strncmp(lastPatternSegment + 1, lastParamSegment + 1, strlen(lastPatternSegment + 1)) != 0) + { + return false; + } + } + + while (*pattern && *paramName) + { + if (*pattern == '*') + { + pattern++; + if (*pattern == '\0') + { + return true; + } + while (*paramName && *paramName != *pattern) + { + paramName++; + } + } + else + { + if (*pattern != *paramName) + { + return false; + } + pattern++; + paramName++; + } + } + + return (*pattern == '\0' && *paramName == '\0'); +} diff --git a/source/utils/t2common.h b/source/utils/t2common.h index 9ef9be73..dec4cbfa 100644 --- a/source/utils/t2common.h +++ b/source/utils/t2common.h @@ -147,8 +147,26 @@ typedef struct _TriggerCondition bool report; } TriggerCondition; +typedef struct _DataModelParam +{ + char *name; + char *reference; + bool reportEmpty; +} DataModelParam; + +typedef struct _DataModelTable +{ + char *reference; + char *index; + Vector *paramList; // List of DataModelParam +} DataModelTable; + void freeParam(void *data); +void freeDataModelParam(void *data); + +void freeDataModelTable(void *data); + void freeStaticParam(void *data); void freeEMarker(void *data); @@ -167,4 +185,6 @@ void initWhoamiSupport(void); bool isWhoAmiEnabled(void); +bool matchesParameter(const char* pattern, const char* paramName); + #endif /* _T2COMMON_H_ */