From c56fef33df07f47c2ea91c26010548bf0a3baa36 Mon Sep 17 00:00:00 2001 From: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Date: Thu, 30 Apr 2026 23:22:42 +0530 Subject: [PATCH] Refactor issueTypeSplitter to include suffix handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update rrdEventProcess.c Update rrdExecuteScript.c Refactor rrdEventProcess.h to rrdExecuteScript.h Update rrdExecuteScript.h Update rrdExecuteScript.h Update rrdEventProcess.h Update rrdExecuteScript.h Update rrdJsonParser.c Update rrdJsonParser.h Update rrdInterface.c Update rrdCommon.h Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Fix indentation for appendMode assignment Update rrdCommon.h Fix appendMode assignment in rrdEventProcess.c Update rrdEventProcess.c Update rrdJsonParser.c Update rrdEventProcess.c Update rrdExecuteScript.h Update rrdExecuteScript.c Update rrdExecuteScript.c Update rrdExecuteScript.c Remove redundant logging from rrdJsonParser Update rrdJsonParser.c Update rrdEventProcess.c Update rrdJsonParser.c Update rrdEventProcess.c Update rrdJsonParser.c Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Update rrdEventProcess.c Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Update rrdJsonParser.c Fix heap overflow in issueTypeSplitter and memory leaks in suffix handling Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/fbc52780-966b-4912-825f-3030aa43c3e9 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Update rrdUnitTestRunner.cpp Delete .gitignore Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdUnitTestRunner.cpp Update rrdEventProcess.c Update rrdUnitTestRunner.cpp Update rrdUnitTestRunner.cpp Add gtest test cases for split_issue_type Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/9996d741-248e-4e58-8689-b4ba873cfaf2 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Remove build artifacts, add .gitignore Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/9996d741-248e-4e58-8689-b4ba873cfaf2 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Add explicit truncated content assertion in SuffixTruncatedWhenTooSmall test Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/9996d741-248e-4e58-8689-b4ba873cfaf2 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdJsonParser.c Update rrdJsonParser.c Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdUnitTestRunner.cpp Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Delete .gitignore Delete src/unittest/UTJson/device.properties Update rrdEventProcess.c Add gtest tests for split_issue_type, suffix field, and processIssueTypeEvent Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/76cad72a-f67f-4c05-8fb5-bfadf0c173b3 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Delete .gitignore Validate suffix prefix: only _Search- and _LogSearch- are allowed Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/125526c2-7b70-48f5-8bec-fd725eea8a04 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Base never contains underscore: split at first _ and discard invalid suffix Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/904bf10d-546b-4038-a60c-9bc76094a225 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Update rrdEventProcess.c Update rrdUnitTestRunner.cpp Update rrdEventProcess.c Update rrdEventProcess.c Fix segfault in GTEST_ENABLE mode when rbuf->jsonPath is NULL Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/efdc0d6b-89e6-4423-b202-a900f5683839 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Remove accidentally committed dummy directory and update .gitignore Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/efdc0d6b-89e6-4423-b202-a900f5683839 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Fix IssueTypeSplitterTest to match new issueTypeSplitter behavior (no built-in special-char removal) Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/00abcaba-8a41-4b88-ae12-07b5ff780ff9 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Fix empty/whitespace IssueType bypassing processIssueTypeEvent guards Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/4de31e24-70d8-496c-ac6a-a5376771d936 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> fix: preserve hyphens in archive filename so portal can parse it correctly rrd_logproc_convert_issue_type() was converting '-' to '_', turning the suffix '_Search-67768-67' into '_SEARCH_67768_67'. This added extra '_' separators into the archive filename that broke the analytics portal's filename parser — it could no longer identify the timestamp field, so download requests used an incorrect S3 key. Fix: keep '-' as '-' in the sanitized output. The archive filename now uses '_' to separate structural fields (MAC, issueType, timestamp) and '-' within the UUID suffix, giving the portal a reliable delimiter. Also increase issue_type_sanitized buffer in uploadRRDLogs.c from 64 to 256 bytes so a full UUID suffix never causes a silent truncation failure. Before: 04B86A12F9F8_DEVICE_DEVICEIP_SEARCH_67768_67__RRD_DEBUG_LOGS.tgz After: 04B86A12F9F8_DEVICE_DEVICEIP_SEARCH-67768-67__RRD_DEBUG_LOGS.tgz All 331 tests pass. Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/981b6bc1-c9d2-4150-9e9d-851004942ffc Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> fix: replace _Search-/_LogSearch- prefix check with suffix length <= 9 rule split_issue_type() now discards any suffix whose total length (including the leading '_') exceeds 9 characters. The old _Search-/_LogSearch- prefix validation is removed entirely. Rule: strlen(underscore) <= 9 → suffix accepted strlen(underscore) > 9 → suffix discarded This means: - Short session tokens (e.g. "_ab12345", 8 chars) are carried through - Long UUID-based suffixes like "_Search-b6877385-...) are discarded, preventing extra '_' delimiters from breaking the portal filename parser Affected tests updated: - UnderscoreSplitsBaseAndSuffix : uses a short accepted suffix - MultipleUnderscoresSplitsAtFirst: "_def_ghi" (8 chars) now kept - BaseTruncatedWhenTooSmall / ExactFitBase: "_suffix" (7 chars) now kept - SuffixTruncatedWhenTooSmall: uses 9-char suffix with 5-byte buffer - OnlyUnderscoreInput: "_" (1 char) now kept - LogSearchSuffixIsValid → NineCharSuffixIsAccepted (boundary test) - SearchSuffixIsValid → LongSuffixIsDiscarded - InvalidSuffixPrefixDiscarded / SearchWithoutHyphenIsInvalid / LogSearchWithoutHyphenIsInvalid: updated comments (same outcomes) - ProcessIssueTypeEvntTest comments updated to reflect new rule All 331 tests pass. Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/c6d6fc29-da2a-46af-a417-c2de67a18448 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> refactor: improve RRD_MAX_SUFFIX_LEN comment and rename length-based tests - Expand the RRD_MAX_SUFFIX_LEN comment to explain why 9 chars is the limit - Rename misleading test names that referenced old prefix-based validation: InvalidSuffixPrefixDiscarded → SuffixExceedingMaxLengthDiscarded SearchWithoutHyphenIsInvalid → SuffixSeventeenCharsDiscarded LogSearchWithoutHyphenIsInvalid → SuffixTwentyCharsDiscarded - LongSuffixIsDiscarded: switch to a neutral _1234567890 example so the test does not imply any Search-specific behavior All 331 tests pass. Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/c6d6fc29-da2a-46af-a417-c2de67a18448 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Update rrdJsonParser.c Update uploadRRDLogs.c Update uploadRRDLogs.c Update rrdJsonParser.c Delete .gitignore Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdEventProcess.c Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdJsonParser.c Update rrdEventProcess.c sanitize split_issue_type suffix to [A-Za-z0-9_-] to prevent injection Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/72e164ed-ae53-4076-8fb1-5ff1e21375e9 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Update rrdEventProcess.c Delete .gitignore Update rrdRunCmdThread.c Update rrdRunCmdThread.h Delete src/unittest/UTJson/device.properties Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdRunCmdThread.c Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/rrdCommon.h | 1 + src/rrdEventProcess.c | 78 +++++- src/rrdInterface.c | 5 + src/rrdJsonParser.c | 135 ++++++++++- src/rrdJsonParser.h | 2 + src/rrdRunCmdThread.c | 21 +- src/rrdRunCmdThread.h | 1 + src/rrd_logproc.c | 3 +- src/unittest/rrdUnitTestRunner.cpp | 376 ++++++++++++++++++++++++++++- 9 files changed, 596 insertions(+), 26 deletions(-) diff --git a/src/rrdCommon.h b/src/rrdCommon.h index b91ff6f7b..8f5544b77 100644 --- a/src/rrdCommon.h +++ b/src/rrdCommon.h @@ -97,6 +97,7 @@ typedef struct mbuffer { bool inDynamic; bool appendMode; deepsleep_event_et dsEvent; + char *suffix; // Holds the suffix split from issue type string, if any } data_buf; /*Structure for Message Header*/ diff --git a/src/rrdEventProcess.c b/src/rrdEventProcess.c index 5164e7832..eb3c882df 100644 --- a/src/rrdEventProcess.c +++ b/src/rrdEventProcess.c @@ -79,7 +79,35 @@ void processIssueTypeEvent(data_buf *rbuf) cmdBuff = (data_buf *)malloc(sizeof(data_buf)); if (cmdBuff) { - dataMsgLen = strlen(cmdMap[index]) + 1; + char base[128] = {0}; + char local_suffix[128] = {0}; + split_issue_type(cmdMap[index], base, sizeof(base), local_suffix, sizeof(local_suffix)); + if (base[0] == '\0') + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Empty issue type after parsing token [%s], skipping... \n", __FUNCTION__, __LINE__, cmdMap[index]); + free(cmdBuff); + cmdBuff = NULL; + if (cmdMap[index]) + { + free(cmdMap[index]); + cmdMap[index] = NULL; + } + continue; + } + removeSpecialCharacterfromIssueTypeList(base); + if (base[0] == '\0') + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Empty base after sanitization for token [%s], skipping... \n", __FUNCTION__, __LINE__, cmdMap[index]); + free(cmdBuff); + cmdBuff = NULL; + if (cmdMap[index]) + { + free(cmdMap[index]); + cmdMap[index] = NULL; + } + continue; + } + dataMsgLen = strlen(base) + 1; RRD_data_buff_init(cmdBuff, EVENT_MSG, RRD_DEEPSLEEP_INVALID_DEFAULT); /* Setting Deafult Values*/ cmdBuff->inDynamic = rbuf->inDynamic; if(cmdBuff->inDynamic) @@ -88,9 +116,19 @@ void processIssueTypeEvent(data_buf *rbuf) } cmdBuff->appendMode = rbuf->appendMode; cmdBuff->mdata = (char *)calloc(1, dataMsgLen); + + /* Store suffix for this issue type */ + cmdBuff->suffix = NULL; + if (local_suffix[0] != '\0') { + cmdBuff->suffix = strdup(local_suffix); + if (cmdBuff->suffix == NULL) + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Failed to allocate memory for suffix... \n", __FUNCTION__, __LINE__); + } + } if (cmdBuff->mdata) { - strncpy((char *)cmdBuff->mdata, cmdMap[index], dataMsgLen); + strncpy((char *)cmdBuff->mdata, base, dataMsgLen); processIssueType(cmdBuff); } else @@ -99,6 +137,11 @@ void processIssueTypeEvent(data_buf *rbuf) } if(cmdBuff) { + if (cmdBuff->suffix) + { + free(cmdBuff->suffix); + cmdBuff->suffix = NULL; + } free(cmdBuff); cmdBuff = NULL; } @@ -392,7 +435,10 @@ static void processIssueTypeInStaticProfile(data_buf *rbuf, issueNodeData *pIssu #if !defined(GTEST_ENABLE) jsonParsed = readAndParseJSON(RRD_JSON_FILE); #else - jsonParsed = readAndParseJSON(rbuf->jsonPath); + if (rbuf->jsonPath != NULL) + { + jsonParsed = readAndParseJSON(rbuf->jsonPath); + } #endif if (jsonParsed == NULL) { // Static Profile JSON Parsing or Read Fail @@ -555,6 +601,29 @@ static void processIssueTypeInInstalledPackage(data_buf *rbuf, issueNodeData *pI suffixlen = strlen(RDM_PKG_SUFFIX); dynJSONPath = (char *)malloc(persistentAppslen + prefixlen + suffixlen + strlen(pIssueNode->Node) + rrdjsonlen + 1); #else + if ((rbuf == NULL) || (rbuf->jsonPath == NULL)) + { + RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: jsonPath is NULL, skipping installed package check... \n", __FUNCTION__, __LINE__); + if (rbuf != NULL) + { + if (rbuf->mdata != NULL) + { + free(rbuf->mdata); + rbuf->mdata = NULL; + } + if (rbuf->suffix != NULL) + { + free(rbuf->suffix); + rbuf->suffix = NULL; + } + if (rbuf->jsonPath != NULL) + { + free(rbuf->jsonPath); + rbuf->jsonPath = NULL; + } + } + return; + } int utjsonlen = strlen(rbuf->jsonPath); dynJSONPath = (char *)malloc(utjsonlen + 1); #endif @@ -639,7 +708,7 @@ static void removeSpecialCharacterfromIssueTypeList(char *str) while (str[source] != '\0') { - if (isalnum(str[source]) || str[source] == ',' || str[source] == '.') + if (isalnum(str[source]) || str[source] == ',' || str[source] == '.') { str[destination] = str[source]; ++destination; @@ -663,7 +732,6 @@ static int issueTypeSplitter(char *input_str, const char delimeter, char ***args int cnt = 1, i = 0; char *str = input_str; - removeSpecialCharacterfromIssueTypeList(str); while (*str == delimeter) str++; diff --git a/src/rrdInterface.c b/src/rrdInterface.c index b69dd8936..30291cd84 100644 --- a/src/rrdInterface.c +++ b/src/rrdInterface.c @@ -275,6 +275,7 @@ void RRD_data_buff_init(data_buf *sbuf, message_type_et sndtype, deepsleep_event sbuf->inDynamic = false; sbuf->appendMode = false; sbuf->dsEvent = deepSleepEvent; + sbuf->suffix = NULL; } /*Function: RRD_data_buff_deAlloc @@ -295,6 +296,10 @@ void RRD_data_buff_deAlloc(data_buf *sbuf) { free(sbuf->jsonPath); } + if (sbuf->suffix) + { + free(sbuf->suffix); + } free(sbuf); } } diff --git a/src/rrdJsonParser.c b/src/rrdJsonParser.c index e06d93ac2..2fce93676 100644 --- a/src/rrdJsonParser.c +++ b/src/rrdJsonParser.c @@ -24,6 +24,8 @@ #include #include #include +/* Maximum suffix length, including the leading '_' character. */ +#define RRD_MAX_SUFFIX_LEN 9 /* * @function removeSpecialChar @@ -46,6 +48,102 @@ void removeSpecialChar(char *str) } } + +/* + * @function split_issue_type + * @brief Utility to split base and suffix from issue type string. + * The input is always split at the first '_'. The base is the part + * before the underscore (never contains '_'). The suffix is only + * kept when its total length (including the leading '_') is at most + * RRD_MAX_SUFFIX_LEN (9) characters; longer suffixes are discarded. + * After length validation the suffix is sanitized: only the characters + * [A-Za-z0-9_-] are retained so that the value is safe to use in file + * names and shell command arguments without risk of injection. + * If no underscore is present the full input is the base. + * Examples: + * "Device.DeviceTime_ab12345" → base="Device.DeviceTime", + * suffix="_ab12345" (8 chars, accepted) + * "Device.DeviceTime_Search-uuid-very-long" + * → base="Device.DeviceTime", + * suffix="" (>9 chars, discarded) + * "Device.DeviceTime" → base="Device.DeviceTime", + * suffix="" + * "Device.DeviceTime_ab;rm" → base="Device.DeviceTime", + * suffix="_abrm" (unsafe ';' stripped) + * @param const char *input - The input string to split. + * @param char *base - Buffer to store the base part (never contains '_'). + * @param size_t base_len - Size of the base buffer. + * @param char *suffix - Buffer to store the suffix part when valid, else "". + * @param size_t suffix_len - Size of the suffix buffer. + * @return void + */ + +void split_issue_type(const char *input, char *base, size_t base_len, char *suffix, size_t suffix_len) +{ + if (base && base_len > 0) + { + base[0] = '\0'; + } + if (suffix && suffix_len > 0) + { + suffix[0] = '\0'; + } + + if (!input || !base || !suffix) + { + return; + } + + if (base_len == 0 || suffix_len == 0) + { + return; + } + + const char *underscore = strchr(input, '_'); + if (underscore) + { + /* Always split at the first underscore — base never contains '_' */ + size_t b_len = (size_t)(underscore - input); + if (b_len >= base_len) b_len = base_len - 1; + strncpy(base, input, b_len); + base[b_len] = '\0'; + + /* Keep the suffix only when its total length (including '_') is + * within the allowed limit; longer tokens are discarded. */ + if (strlen(underscore) <= RRD_MAX_SUFFIX_LEN) + { + /* Sanitize: retain only [A-Za-z0-9_-] to prevent injection when + * the suffix is later embedded in file names or command arguments. */ + size_t si = 0, di = 0; + size_t max_copy = suffix_len - 1; + while (underscore[si] != '\0' && di < max_copy) + { + char ch = underscore[si]; + if (isalnum((unsigned char)ch) || ch == '_' || ch == '-') + { + suffix[di++] = ch; + } + si++; + } + suffix[di] = '\0'; + } + else + { + RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Suffix after '%s' exceeds max length (%zu > %d); discarding\n", + __FUNCTION__, __LINE__, base, strlen(underscore), RRD_MAX_SUFFIX_LEN); + suffix[0] = '\0'; + } + } + else + { + /* No underscore — full input is the base */ + strncpy(base, input, base_len - 1); + base[base_len - 1] = '\0'; + suffix[0] = '\0'; + } +} + + /* * @function getParamcount * @brief Calculates the total number of nodes (elements) in the input string, excluding delimiters. @@ -515,7 +613,11 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf { RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Memory allocation failed for rfcbuf\n",__FUNCTION__,__LINE__); free(buff->mdata); // free rfc data + buff->mdata = NULL; free(buff->jsonPath); // free rrd path info + buff->jsonPath = NULL; + free(buff->suffix); // free suffix + buff->suffix = NULL; return; } @@ -535,7 +637,11 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: %s Directory creation failed!!!\n",__FUNCTION__,__LINE__,outdir); free(rfcbuf); // free duplicated rfc data free(buff->mdata); // free rfc data + buff->mdata = NULL; free(buff->jsonPath); // free rrd path info + buff->jsonPath = NULL; + free(buff->suffix); // free suffix + buff->suffix = NULL; return; } else @@ -576,7 +682,26 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf else { RDK_LOG(RDK_LOG_DEBUG,LOG_REMDEBUG,"[%s:%d]: Continue uploading Debug Report for %s from %s... \n",__FUNCTION__,__LINE__,buff->mdata,outdir); - status = uploadDebugoutput(outdir,buff->mdata); + // Use the persisted suffix from buff for upload + char tarName[512] = {0}; + int tar_name_len = 0; + if (buff->suffix && buff->suffix[0] != '\0') + { + tar_name_len = snprintf(tarName, sizeof(tarName), "%s%s", buff->mdata, buff->suffix); + } + else + { + tar_name_len = snprintf(tarName, sizeof(tarName), "%s", buff->mdata); + } + if ((tar_name_len < 0) || ((size_t)tar_name_len >= sizeof(tarName))) + { + RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Failed to build upload file name for %s. snprintf result:%d, buffer size:%zu\n", __FUNCTION__,__LINE__,buff->mdata,tar_name_len,sizeof(tarName)); + status = -1; + } + else + { + status = uploadDebugoutput(outdir, tarName); + } if(status != 0) { RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: RRD Upload Script Execution Failed!!! status:%d\n",__FUNCTION__,__LINE__,status); @@ -588,14 +713,22 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf } free(rfcbuf); // free duplicated rfc data free(buff->mdata); // free rfc data + buff->mdata = NULL; free(buff->jsonPath); // free rrd path info + buff->jsonPath = NULL; + free(buff->suffix); // free suffix + buff->suffix = NULL; } else { RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: No Command excuted as RRD Failed to change directory:%s\n",__FUNCTION__,__LINE__,outdir); free(rfcbuf); // free duplicated rfc data free(buff->mdata); // free rfc data + buff->mdata = NULL; free(buff->jsonPath); // free rrd path info + buff->jsonPath = NULL; + free(buff->suffix); // free suffix + buff->suffix = NULL; } } } diff --git a/src/rrdJsonParser.h b/src/rrdJsonParser.h index 299436101..87c4a2622 100644 --- a/src/rrdJsonParser.h +++ b/src/rrdJsonParser.h @@ -47,6 +47,8 @@ issueData* getIssueCommandInfo(issueNodeData *issuestructNode, cJSON *jsoncfg,ch bool processAllDebugCommand(cJSON *jsoncfg, issueNodeData *issuestructNode, char *rfcbuf); bool processAllDeepSleepAwkMetricsCommands(cJSON *jsoncfg, issueNodeData *issuestructNode, char *rfcbuf); +void split_issue_type(const char *input, char *base, size_t base_len, char *suffix, size_t suffix_len); + #ifdef __cplusplus } #endif diff --git a/src/rrdRunCmdThread.c b/src/rrdRunCmdThread.c index ce873d5d5..650144cdb 100644 --- a/src/rrdRunCmdThread.c +++ b/src/rrdRunCmdThread.c @@ -287,7 +287,7 @@ bool executeCommands(issueData *cmdinfo) char pathname[BUF_LEN_256] = {'\0'}; char *outdirpath = NULL; char finalOutFile[BUF_LEN_256] = {'\0'}; - char remoteDebuggerServiceStr[BUF_LEN_256] = {'\0'}; + char remoteDebuggerServiceStr[BUF_LEN_512] = {'\0'}; char *printbuffer = NULL; FILE *filePointer; const char *remoteDebuggerPrefix = "remote_debugger_"; @@ -357,7 +357,7 @@ bool executeCommands(issueData *cmdinfo) strncat(finalOutFile,RRD_OUTPUT_FILE, strlen(RRD_OUTPUT_FILE) + 1); /* Open debug_output.txt file*/ - filePointer = fopen(finalOutFile, "a+"); + filePointer = fopen(finalOutFile, "w+"); if (filePointer == NULL) { RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Unable to Open File:%s\n",__FUNCTION__,__LINE__,finalOutFile); @@ -376,21 +376,18 @@ bool executeCommands(issueData *cmdinfo) /*Executing Commands using systemd-run*/ RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Executing following commands using systemd-run:\n \"%s\"\n",__FUNCTION__,__LINE__,cmdData->command); - - strncpy(remoteDebuggerServiceStr, remoteDebuggerPrefix, sizeof(remoteDebuggerServiceStr) - 1); - remoteDebuggerServiceStr[sizeof(remoteDebuggerServiceStr) - 1] = '\0'; - strncat(remoteDebuggerServiceStr, cmdData->rfcvalue, sizeof(remoteDebuggerServiceStr) - strlen(remoteDebuggerServiceStr) - 1); - + time_t epochTime = time(NULL); + snprintf(remoteDebuggerServiceStr, sizeof(remoteDebuggerServiceStr),"%s%s%ld", remoteDebuggerPrefix, cmdData->rfcvalue, (long)epochTime); removeQuotes(cmdData->command); FILE *systemdfp = v_secure_popen("r", "systemd-run -r --unit=%s --service-type=oneshot -p RemainAfterExit=yes /bin/sh -c %s", remoteDebuggerServiceStr, cmdData->command); if(systemdfp == NULL) { - RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Starting remote_debugger_%s service failed!!!\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Starting %s service failed!!!\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); } else { - RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Starting remote_debugger_%s service success...\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Starting %s service success...\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); copyDebugLogDestFile(systemdfp, filePointer); v_secure_pclose(systemdfp); } @@ -400,11 +397,11 @@ bool executeCommands(issueData *cmdinfo) FILE *journalctlfp = v_secure_popen("r", "journalctl -a -u %s", remoteDebuggerServiceStr); if(journalctlfp == NULL) { - RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: journalctl remote_debugger_%s service failed!!!\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: journalctl %s service failed!!!\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); } else { - RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: journalctl remote_debugger_%s service success...\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: journalctl %s service success...\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); copyDebugLogDestFile(journalctlfp, filePointer); v_secure_pclose(journalctlfp); } @@ -417,7 +414,7 @@ bool executeCommands(issueData *cmdinfo) sleep(cmdData->timeout); /*Stop or Reset runtime service for issue*/ - RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Stopping remote_debugger_%s service...\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Stopping %s service...\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); #if !defined(GTEST_ENABLE) v_secure_system("systemctl stop %s", remoteDebuggerServiceStr); free(cmdData->rfcvalue); // free rfcvalue received from RRDEventThreadFunc diff --git a/src/rrdRunCmdThread.h b/src/rrdRunCmdThread.h index a1bd9816a..fb92fb087 100644 --- a/src/rrdRunCmdThread.h +++ b/src/rrdRunCmdThread.h @@ -48,6 +48,7 @@ extern "C" #endif #define RRD_OUTPUT_FILE "debug_outputs.txt" #define BUF_LEN_256 256 +#define BUF_LEN_512 512 /*Public Function*/ void initCache(void); diff --git a/src/rrd_logproc.c b/src/rrd_logproc.c index 30c0cff1c..a33aee466 100644 --- a/src/rrd_logproc.c +++ b/src/rrd_logproc.c @@ -121,7 +121,8 @@ int rrd_logproc_convert_issue_type(const char *input, char *output, size_t size) for (size_t i = 0; input[i] && j < size-1; ++i) { char c = input[i]; if (isalnum((unsigned char)c)) output[j++] = toupper((unsigned char)c); - else if (c == '_' || c == '-' || c == '.') output[j++] = '_'; + else if (c == '_' || c == '.') output[j++] = '_'; + else if (c == '-') output[j++] = '-'; // preserve hyphens so suffix UUID tokens remain distinct // skip other chars } output[j] = 0; diff --git a/src/unittest/rrdUnitTestRunner.cpp b/src/unittest/rrdUnitTestRunner.cpp index 7b8c6c837..059e5ce41 100644 --- a/src/unittest/rrdUnitTestRunner.cpp +++ b/src/unittest/rrdUnitTestRunner.cpp @@ -1799,11 +1799,13 @@ TEST_F(RemoveItemTest, HandlesCacheNotNullAndCacheNotEqualsRrdCachecnode) node->mdata = strdup("PkgData"); node->issueString = strdup("IssueString"); node->next = NULL; + node->prev = NULL; cacheDataNode = node; cacheData *node_dummy = (cacheData *)malloc(sizeof(cacheData)); node_dummy->mdata = strdup("PkgData"); node_dummy->issueString = strdup("IssueString"); node_dummy->next = NULL; + node_dummy->prev = NULL; remove_item(node_dummy); EXPECT_NE(cacheDataNode, nullptr); @@ -1863,15 +1865,19 @@ TEST(RemoveSpecialCharacterfromIssueTypeListTest, HandlesStringWithConsecutiveSp /* --------------- Test issueTypeSplitter() from rrdEventProcess --------------- */ TEST(IssueTypeSplitterTest, HandlesStringWithSpecialCharacters) { + /* issueTypeSplitter now performs pure token splitting only; special-character + * removal is done separately by the caller (processIssueTypeEvent) on the + * extracted base, so raw tokens including special chars are returned here. */ char str[] = "a@,b,&,cd,ef"; char **args = NULL; int count = issueTypeSplitter(str, ',', &args); - ASSERT_EQ(count, 4); - ASSERT_STREQ(args[0], "a"); + ASSERT_EQ(count, 5); + ASSERT_STREQ(args[0], "a@"); ASSERT_STREQ(args[1], "b"); - ASSERT_STREQ(args[2], "cd"); - ASSERT_STREQ(args[3], "ef"); + ASSERT_STREQ(args[2], "&"); + ASSERT_STREQ(args[3], "cd"); + ASSERT_STREQ(args[4], "ef"); for (int i = 0; i < count; i++) { @@ -1907,6 +1913,237 @@ TEST(IssueTypeSplitterTest, HandlesEmptyString) free(args); } +/* --------------- Test split_issue_type() from rrdJsonParser --------------- */ +TEST(SplitIssueTypeTest, NoUnderscoreReturnsFull) +{ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, UnderscoreSplitsBaseAndSuffix) +{ + /* Short suffix (total length including '_' is ≤ 9) is accepted and preserved */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_ab12345", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_ab12345"); +} + +TEST(SplitIssueTypeTest, MultipleUnderscoresSplitsAtFirst) +{ + /* "abc_def_ghi": suffix "_def_ghi" is 8 chars (≤ 9) → accepted and preserved. + * Only the first '_' is used as the split point; base never contains '_'. */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_def_ghi", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_def_ghi"); +} + +TEST(SplitIssueTypeTest, EmptyInputProducesEmptyOutputs) +{ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, NullInputDoesNotCrash) +{ + char base[64] = {0}; + char suffix[64] = {0}; + /* Should return without crashing and clear provided outputs to empty strings */ + split_issue_type(NULL, base, sizeof(base), suffix, sizeof(suffix)); + /* NULL input clears the output buffers when buffer pointers are provided */ + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, BaseTruncatedWhenTooSmall) +{ + /* "abc_suffix": suffix "_suffix" is 7 chars (≤ 9) → accepted. + * Base = "abc" (before '_'); with a 4-byte buffer this fits exactly. */ + char base[4] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_suffix", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_suffix"); +} + +TEST(SplitIssueTypeTest, SuffixTruncatedWhenTooSmall) +{ + /* "abc_12345678": suffix "_12345678" is 9 chars (≤ 9, accepted). Suffix buffer + * is only 5 bytes so suffix is truncated to "_123" + NUL. */ + char base[64] = {0}; + char suffix[5] = {0}; + split_issue_type("abc_12345678", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_123"); + EXPECT_EQ(suffix[sizeof(suffix) - 1], '\0'); + EXPECT_EQ(strlen(suffix), (size_t)(sizeof(suffix) - 1)); +} + +TEST(SplitIssueTypeTest, LeadingUnderscoreGivesEmptyBase) +{ + /* "_suffixonly": split at '_' gives empty base; suffix "_suffixonly" is 11 chars + * (> 9) so it is discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("_suffixonly", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, NullBaseDoesNotCrash) +{ + char suffix[64] = {0}; + /* NULL base pointer: should return without crashing */ + split_issue_type("Device.DeviceTime_Search", NULL, 64, suffix, sizeof(suffix)); + /* suffix remains unchanged when base is NULL */ + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, NullSuffixDoesNotCrash) +{ + char base[64] = {0}; + /* NULL suffix pointer: should return without crashing */ + split_issue_type("Device.DeviceTime_Search", base, sizeof(base), NULL, 64); + /* base remains unchanged when suffix is NULL */ + EXPECT_STREQ(base, ""); +} + +TEST(SplitIssueTypeTest, ZeroBaseLenDoesNotCrash) +{ + char base[64] = {0}; + char suffix[64] = {0}; + /* base_len == 0: should return without writing anything */ + split_issue_type("abc_def", base, 0, suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, ZeroSuffixLenDoesNotCrash) +{ + char base[64] = {0}; + char suffix[64] = {0}; + /* suffix_len == 0: should return without writing anything */ + split_issue_type("abc_def", base, sizeof(base), suffix, 0); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, ExactFitBase) +{ + /* "abc_suffix": suffix "_suffix" is 7 chars (≤ 9, accepted). + * Base = "abc" (before '_'); 4-byte buffer fits exactly. */ + char base[4] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_suffix", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_EQ(base[3], '\0'); + EXPECT_STREQ(suffix, "_suffix"); +} + +TEST(SplitIssueTypeTest, OnlyUnderscoreInput) +{ + /* "_": split at '_' gives empty base; suffix is "_" (1 char, ≤ 9 → accepted) */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("_", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, "_"); +} + +TEST(SplitIssueTypeTest, NineCharSuffixIsAccepted) +{ + /* Suffix of exactly 9 chars (the upper boundary, inclusive) must be accepted */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_12345678", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_12345678"); +} + +TEST(SplitIssueTypeTest, LongSuffixIsDiscarded) +{ + /* Suffix longer than 9 chars is discarded regardless of its content */ + char base[64] = {0}; + char suffix[128] = {0}; + split_issue_type("Device.DeviceInfo_1234567890", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceInfo"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixExceedingMaxLengthDiscarded) +{ + /* "_Random-token" is 13 chars (> 9) → suffix discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_Random-token", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixSeventeenCharsDiscarded) +{ + /* "_Search_something" is 17 chars (> 9) → suffix discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_Search_something", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixTwentyCharsDiscarded) +{ + /* "_LogSearch_something" is 20 chars (> 9) → suffix discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_LogSearch_something", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixWithUnsafeCharsIsSanitized) +{ + /* "_ab;rm" is 6 chars (≤ 9, accepted) but ';' is unsafe and must be stripped. + * Expected sanitized suffix: "_abrm" */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_ab;rm", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_abrm"); +} + +TEST(SplitIssueTypeTest, SuffixWithOnlyUnsafeCharsBecomesUnderscore) +{ + /* "_!@#" is 4 chars (≤ 9, accepted length-wise) but all payload chars are unsafe. + * After sanitization only the leading '_' remains → suffix="_" */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_!@#", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_"); +} + +TEST(SplitIssueTypeTest, SuffixHyphensPreserved) +{ + /* "_ab-cd" is 6 chars (≤ 9, accepted) and '-' is in the safe set → preserved */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_ab-cd", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_ab-cd"); +} + /* --------------- Test processIssueTypeInDynamicProfile() from rrdEventProcess --------------- */ class ProcessIssueTypeInDynamicProfileTest : public ::testing::Test { @@ -1989,11 +2226,93 @@ TEST(ProcessIssueTypeEvntTest, RBufIsNull){ } TEST(ProcessIssueTypeEvntTest, inDynamic_NoJson){ - data_buf rbuf; + data_buf rbuf = {}; rbuf.mdata = strdup("a"); rbuf.inDynamic = true; rbuf.jsonPath = nullptr; processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, IssueTypeWithSearchSuffix_inDynamic_NoJson){ + /* Issue type with a long suffix (> 9 chars): suffix is discarded; base "Device.DeviceTime" is used */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceTime_Search-b6877385-9463-45fc-b19d-a24d77fd0790"); + rbuf.inDynamic = true; + rbuf.jsonPath = nullptr; + /* Should not crash; long suffix is discarded, base is processed normally */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, IssueTypeWithLogSearchSuffix_inDynamic_NoJson){ + /* Issue type with a long suffix (> 9 chars): suffix is discarded; base "Device.DeviceInfo" is used */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceInfo_LogSearch-9abc1def-0000-1111-2222-3333aaaabbbb"); + rbuf.inDynamic = true; + rbuf.jsonPath = nullptr; + /* Should not crash; long suffix is discarded, base is processed normally */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, IssueTypeWithInvalidSuffixTreatedAsBase) +{ + /* "_Random-token" is 13 chars (> 9): suffix discarded; base = "Device.DeviceTime" */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceTime_Random-token"); + rbuf.inDynamic = false; + rbuf.jsonPath = nullptr; + /* Should not crash; long suffix is discarded, base is processed normally */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, MultipleIssueTypesWithAndWithoutSuffix){ + /* Comma-separated list: one plain type, one with long suffix (> 9, discarded), + * one with another long suffix (> 9, discarded) */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceTime,Device.DeviceInfo_Search-1234,Device.Net_BadSuffix"); + rbuf.inDynamic = true; + rbuf.jsonPath = nullptr; + /* Should not crash; all entries are processed without leaks */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, WhitespaceOnlyIssueTypeIsSkipped) +{ + /* When the IssueType value from RBUS is whitespace (e.g. a space), + * removeSpecialCharacterfromIssueTypeList() strips it to an empty string. + * processIssueTypeEvent() must detect the post-sanitization empty base + * and skip processing without crashing or invoking processIssueType. */ + data_buf rbuf = {}; + rbuf.mdata = strdup(" "); /* single space — all-special after split */ + rbuf.inDynamic = false; + rbuf.jsonPath = nullptr; + /* Must not crash and must not reach getIssueInfo with an empty mdata */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, EmptyStringIssueTypeIsSkipped) +{ + /* An empty-string mdata must be handled gracefully — issueTypeSplitter + * returns 1 token that is an empty string, which split_issue_type then + * maps to an empty base, causing the entry to be skipped. */ + data_buf rbuf = {}; + rbuf.mdata = strdup(""); + rbuf.inDynamic = false; + rbuf.jsonPath = nullptr; + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; } /* ======================== rrdExecuteScript ==============*/ @@ -2117,12 +2436,22 @@ TEST(RRDDataBuffInitTest, InitializeDataBuff) EXPECT_EQ(sbuf.dsEvent, deepSleepEvent); } +TEST(RRDDataBuffInitTest, SuffixInitializedToNull) +{ + /* Verify that the newly added suffix field is initialised to NULL */ + data_buf sbuf; + sbuf.suffix = reinterpret_cast(0xDEADBEEF); /* pre-fill with garbage */ + RRD_data_buff_init(&sbuf, EVENT_MSG, RRD_DEEPSLEEP_INVALID_DEFAULT); + EXPECT_EQ(sbuf.suffix, nullptr); +} + /* --------------- Test RRD_data_buff_deAlloc() from rrdIarm --------------- */ TEST(RRDDataBuffDeAllocTest, DeallocateDataBuff) { data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); sbuf->mdata = (char *)malloc(10 * sizeof(char)); sbuf->jsonPath = (char *)malloc(10 * sizeof(char)); + sbuf->suffix = nullptr; ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); } @@ -2134,6 +2463,28 @@ TEST(RRDDataBuffDeAllocTest, NullPointer) ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); } +TEST(RRDDataBuffDeAllocTest, DeallocateWithSuffixSet) +{ + /* Verify that suffix is freed without crash when it is non-NULL */ + data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); + sbuf->mdata = strdup("IssueType"); + sbuf->jsonPath = nullptr; + sbuf->suffix = strdup("_Search-b6877385-9463-45fc-b19d-a24d77fd0790"); + + ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); +} + +TEST(RRDDataBuffDeAllocTest, DeallocateWithAllFieldsNull) +{ + /* All pointer fields NULL: should not crash */ + data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); + sbuf->mdata = nullptr; + sbuf->jsonPath = nullptr; + sbuf->suffix = nullptr; + + ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); +} + /* --------------- Test RRD_unsubscribe() from rrdIarm --------------- */ class RRDUnsubscribeTest : public ::testing::Test @@ -3684,6 +4035,7 @@ TEST_F(RRDEventThreadFuncTest, MessageReceiveSuccessEventMsgType) { rbuf.mdata = strdup("Test"); rbuf.inDynamic = true; rbuf.jsonPath = nullptr; + rbuf.suffix = strdup("_ab12345"); msgRRDHdr msgHdr; msgHdr.mbody = malloc(sizeof(data_buf)); ASSERT_NE(msgHdr.mbody, nullptr); @@ -4444,12 +4796,22 @@ TEST_F(RRDUploadOrchestrationTest, SpecialCharactersInIssueType) { char sanitized[64]; int result = rrd_logproc_convert_issue_type("test-issue.sub@special!", sanitized, sizeof(sanitized)); EXPECT_EQ(result, 0); - // Should only contain alphanumeric and underscore + // Should only contain alphanumeric, underscore, and hyphen for (const char *p = sanitized; *p; ++p) { - EXPECT_TRUE(isalnum(*p) || *p == '_'); + EXPECT_TRUE(isalnum(*p) || *p == '_' || *p == '-'); } } +// Suffix with hyphens: hyphens must be preserved so portal can parse filename +TEST_F(RRDUploadOrchestrationTest, IssueTypeWithSuffixHyphensPreserved) { + char sanitized[128]; + // Simulates issue type after normalizeIssueName: dots→underscore, hyphens kept + int result = rrd_logproc_convert_issue_type("Device_DeviceIP_Search-67768-67", sanitized, sizeof(sanitized)); + EXPECT_EQ(result, 0); + // Hyphens in suffix must survive so the archive filename delimiter structure is intact + EXPECT_STREQ(sanitized, "DEVICE_DEVICEIP_SEARCH-67768-67"); +} + // Performance test: Large directory TEST_F(RRDUploadOrchestrationTest, LargeDirectoryHandling) { // Create multiple log files