diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..72704dac --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# Autotools build artifacts +Makefile.in +aclocal.m4 +autom4te.cache/ +compile +config.guess +config.h.in +config.log +config.sub +configure +depcomp +install-sh +ltmain.sh +m4/libtool.m4 +m4/ltoptions.m4 +m4/ltsugar.m4 +m4/ltversion.m4 +m4/lt~obsolete.m4 +missing +config.status +Makefile +src/Makefile +src/unittest/Makefile + +# Build output files +*.o +*.a +*.so +*.lo +*.la +*.Po +*.Plo +*.gcda +*.gcno +*.gcov +.deps/ +.libs/ + +# Test binaries and coverage output +src/unittest/remotedebugger_gtest +src/unittest/COPYING +src/unittest/INSTALL +src/unittest/dummy_*/ + +# Generated test data files +src/unittest/UTJson/device.properties +dummy_*/ diff --git a/src/rrdCommon.h b/src/rrdCommon.h index b91ff6f7..8f5544b7 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 5164e783..62bf13be 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,11 @@ 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->jsonPath == NULL) + { + RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: jsonPath is NULL in GTEST mode, skipping installed package check... \n", __FUNCTION__, __LINE__); + return; + } int utjsonlen = strlen(rbuf->jsonPath); dynJSONPath = (char *)malloc(utjsonlen + 1); #endif @@ -639,7 +690,8 @@ 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[source] == '_'|| str[source] == '-') + if (isalnum(str[source]) || str[source] == ',' || str[source] == '.') { str[destination] = str[source]; ++destination; @@ -663,7 +715,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 b69dd893..30291cd8 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 e06d93ac..69854342 100644 --- a/src/rrdJsonParser.c +++ b/src/rrdJsonParser.c @@ -46,6 +46,88 @@ void removeSpecialChar(char *str) } } +/* Valid suffix prefixes: only "_Search-" and "_LogSearch-" are accepted */ +#define VALID_SUFFIX_SEARCH "_Search-" +#define VALID_SUFFIX_LOGSEARCH "_LogSearch-" + +/* + * @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 + * populated when it starts with "_Search-" or "_LogSearch-"; any + * other underscore-delimited token is discarded (suffix returns ""). + * If no underscore is present the full input is the base. + * Examples: + * "Device.DeviceTime_Search-b6877385" → base="Device.DeviceTime", + * suffix="_Search-b6877385" + * "Device.DeviceTime_LogSearch-1a2b" → base="Device.DeviceTime", + * suffix="_LogSearch-1a2b" + * "Device.DeviceTime_Other-token" → base="Device.DeviceTime", + * suffix="" + * "Device.DeviceTime" → base="Device.DeviceTime", + * suffix="" + * @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'; + + /* Only carry the suffix when it starts with an allowed prefix */ + if (strncmp(underscore, VALID_SUFFIX_SEARCH, sizeof(VALID_SUFFIX_SEARCH) - 1) == 0 || + strncmp(underscore, VALID_SUFFIX_LOGSEARCH, sizeof(VALID_SUFFIX_LOGSEARCH) - 1) == 0) + { + strncpy(suffix, underscore, suffix_len - 1); + suffix[suffix_len - 1] = '\0'; + } + else + { + RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Suffix after '%s' does not start with an allowed prefix; discarding suffix\n", __FUNCTION__, __LINE__, base); + 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 +597,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 +621,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 +666,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 +697,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 29943610..87c4a262 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/rrd_logproc.c b/src/rrd_logproc.c index 30c0cff1..a33aee46 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 7b8c6c83..f25d1c46 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,203 @@ 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) +{ + char base[64] = {0}; + char suffix[128] = {0}; + split_issue_type("Device.DeviceTime_Search-b6877385-9463-45fc-b19d-a24d77fd0790", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_Search-b6877385-9463-45fc-b19d-a24d77fd0790"); +} + +TEST(SplitIssueTypeTest, MultipleUnderscoresSplitsAtFirst) +{ + /* "abc_def_ghi": suffix "_def_ghi" does not start with "_Search-" or "_LogSearch-". + * Base is always the part before the first '_'; invalid suffix is discarded. */ + 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, ""); +} + +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": invalid suffix prefix, so suffix is discarded. + * 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, ""); +} + +TEST(SplitIssueTypeTest, SuffixTruncatedWhenTooSmall) +{ + /* "abc_LogSearch-longsuffix": valid suffix prefix "_LogSearch-". Suffix buffer + * is only 5 bytes so suffix is truncated to "_Log" + NUL. */ + char base[64] = {0}; + char suffix[5] = {0}; + split_issue_type("abc_LogSearch-longsuffix", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_Log"); + 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, invalid suffix 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": invalid suffix prefix, so suffix is discarded. + * 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, ""); +} + +TEST(SplitIssueTypeTest, OnlyUnderscoreInput) +{ + /* "_": split at '_' gives empty base, empty (invalid) suffix is discarded */ + 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, LogSearchSuffixIsValid) +{ + /* "_LogSearch-uuid" is an allowed suffix prefix → split */ + char base[64] = {0}; + char suffix[128] = {0}; + split_issue_type("Device.DeviceTime_LogSearch-b6877385-9463-45fc-b19d-a24d77fd0790", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_LogSearch-b6877385-9463-45fc-b19d-a24d77fd0790"); +} + +TEST(SplitIssueTypeTest, SearchSuffixIsValid) +{ + /* "_Search-uuid" is an allowed suffix prefix → split (mirrors UnderscoreSplitsBaseAndSuffix) */ + char base[64] = {0}; + char suffix[128] = {0}; + split_issue_type("Device.DeviceInfo_Search-1a2b3c4d", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceInfo"); + EXPECT_STREQ(suffix, "_Search-1a2b3c4d"); +} + +TEST(SplitIssueTypeTest, InvalidSuffixPrefixDiscarded) +{ + /* "_Random-token" is not an allowed prefix → base = "Device.DeviceTime", 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, SearchWithoutHyphenIsInvalid) +{ + /* "_Search_something" does not start with "_Search-" → split at first '_', 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, LogSearchWithoutHyphenIsInvalid) +{ + /* "_LogSearch_something" does not start with "_LogSearch-" → split at first '_', 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 processIssueTypeInDynamicProfile() from rrdEventProcess --------------- */ class ProcessIssueTypeInDynamicProfileTest : public ::testing::Test { @@ -1989,11 +2192,92 @@ 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 valid "_Search-" suffix: base is "Device.DeviceTime", suffix is preserved */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceTime_Search-b6877385-9463-45fc-b19d-a24d77fd0790"); + rbuf.inDynamic = true; + rbuf.jsonPath = nullptr; + /* Should not crash; suffix is parsed, stored, and freed internally */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, IssueTypeWithLogSearchSuffix_inDynamic_NoJson){ + /* Issue type with valid "_LogSearch-" suffix: base is "Device.DeviceInfo", suffix is preserved */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceInfo_LogSearch-9abc1def-0000-1111-2222-3333aaaabbbb"); + rbuf.inDynamic = true; + rbuf.jsonPath = nullptr; + /* Should not crash; suffix is parsed, stored, and freed internally */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, IssueTypeWithInvalidSuffixTreatedAsBase) +{ + /* "_Random-token" is not an allowed prefix; base = "Device.DeviceTime", suffix discarded */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceTime_Random-token"); + rbuf.inDynamic = false; + rbuf.jsonPath = nullptr; + /* Should not crash; base is split at '_', invalid suffix is discarded */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, MultipleIssueTypesWithAndWithoutSuffix){ + /* Comma-separated list: one plain type, one with valid suffix, one with invalid suffix */ + 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 +2401,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 +2428,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 +4000,7 @@ TEST_F(RRDEventThreadFuncTest, MessageReceiveSuccessEventMsgType) { rbuf.mdata = strdup("Test"); rbuf.inDynamic = true; rbuf.jsonPath = nullptr; + rbuf.suffix = strdup("_search_b675-657-556-656"); msgRRDHdr msgHdr; msgHdr.mbody = malloc(sizeof(data_buf)); ASSERT_NE(msgHdr.mbody, nullptr); @@ -4444,12 +4761,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 diff --git a/src/uploadRRDLogs.c b/src/uploadRRDLogs.c index 14e364ab..69625f40 100644 --- a/src/uploadRRDLogs.c +++ b/src/uploadRRDLogs.c @@ -82,7 +82,10 @@ int rrd_upload_orchestrate(const char *upload_dir, const char *issue_type) RDK_LOG(RDK_LOG_INFO, LOG_REMDEBUG, "%s: Log directory validated and prepared\n", __FUNCTION__); // 6. Convert/sanitize issue type - char issue_type_sanitized[64] = {0}; + /* Buffer must be large enough for a base issue type (~32 chars) plus a + * full UUID suffix (_Search-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx = 47 chars) + * plus the NUL terminator. 256 bytes matches the archive_filename convention. */ + char issue_type_sanitized[256] = {0}; if (rrd_logproc_convert_issue_type(issue_type, issue_type_sanitized, sizeof(issue_type_sanitized)) != 0) { RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "%s: Failed to sanitize issue type\n", __FUNCTION__); return 8;