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/rrdExecuteScript.c b/src/rrdExecuteScript.c index 0c5c2f11..10521c07 100644 --- a/src/rrdExecuteScript.c +++ b/src/rrdExecuteScript.c @@ -32,7 +32,7 @@ static void normalizeIssueName(char *str); * @param char *issuename - Issue type from RFC. * @return int - Returns 0 for success and non-zero for failure. */ -int uploadDebugoutput(char *outdir, char *issuename) +int uploadDebugoutput(char *outdir, char *issuename, const char *suffix) { int ret = 0; @@ -42,7 +42,7 @@ int uploadDebugoutput(char *outdir, char *issuename) #ifdef IARMBUS_SUPPORT RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Starting Upload Debug output via API... \n",__FUNCTION__,__LINE__); - ret = rrd_upload_orchestrate(outdir, issuename); + ret = rrd_upload_orchestrate(outdir, issuename, suffix); if(ret != 0) { RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Upload orchestration failed with code: %d\n",__FUNCTION__,__LINE__, ret); @@ -52,11 +52,24 @@ int uploadDebugoutput(char *outdir, char *issuename) RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Upload orchestration completed successfully\n",__FUNCTION__,__LINE__); } #else - RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Starting Upload Debug output Script: %s... \n",__FUNCTION__,__LINE__,RRD_SCRIPT); - if(v_secure_system("%s %s %s",RRD_SCRIPT,outdir,issuename) != 0) - { - ret = 1; - } + /* Shell-script path: suffix is appended to issuename so the script + * retains the old combined-name behaviour. + * Buffer: issuename is at most ~64 chars, suffix is at most 9 chars + * (RRD_MAX_SUFFIX_LEN), so 256 bytes gives ample headroom. */ + if (suffix && suffix[0] != '\0') { + char issue_with_suffix[256]; + int n = snprintf(issue_with_suffix, sizeof(issue_with_suffix), "%s%s", issuename, suffix); + if (n < 0 || (size_t)n >= sizeof(issue_with_suffix)) { + ret = 1; + } else if (v_secure_system("%s %s %s", RRD_SCRIPT, outdir, issue_with_suffix) != 0) { + ret = 1; + } + } else { + if(v_secure_system("%s %s %s",RRD_SCRIPT,outdir,issuename) != 0) + { + ret = 1; + } + } #endif } diff --git a/src/rrdExecuteScript.h b/src/rrdExecuteScript.h index 02705448..dbd2ae2f 100644 --- a/src/rrdExecuteScript.h +++ b/src/rrdExecuteScript.h @@ -28,7 +28,7 @@ extern "C" #include "rrdCommon.h" #include "rrd_upload.h" -int uploadDebugoutput(char *outdir, char *issuename); +int uploadDebugoutput(char *outdir, char *issuename, const char *suffix); #ifdef __cplusplus } 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..2541417d 100644 --- a/src/rrdJsonParser.c +++ b/src/rrdJsonParser.c @@ -46,6 +46,93 @@ void removeSpecialChar(char *str) } } +/* Maximum allowed suffix length (including the leading '_'). + * The analytics portal parses archive filenames by splitting on '_'. A suffix + * longer than this limit would introduce extra '_' delimiters that shift the + * timestamp field to a wrong position, causing download failures. + * 9 characters = '_' + up to 8 alphanumeric/short-token chars: long UUID-based + * suffixes (e.g. "_Search-b6877385-...") are always > 9 and are therefore + * discarded before the archive name is built. */ +#define RRD_MAX_SUFFIX_LEN 9 + +/* + * @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. + * 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="" + * @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) + { + strncpy(suffix, underscore, suffix_len - 1); + suffix[suffix_len - 1] = '\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 +602,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 +626,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 +671,21 @@ 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); + /* Pass only the base issue type as tarName; the suffix is threaded + * separately so it can be placed AFTER the timestamp in the archive + * filename: {MAC}_{BASE}_{TIMESTAMP}_{SUFFIX}_RRD_DEBUG_LOGS.tgz */ + char tarName[512] = {0}; + int 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 + { + const char *issueSuffix = (buff->suffix && buff->suffix[0] != '\0') ? buff->suffix : NULL; + status = uploadDebugoutput(outdir, tarName, issueSuffix); + } 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_archive.c b/src/rrd_archive.c index 9cf31103..af9996d2 100644 --- a/src/rrd_archive.c +++ b/src/rrd_archive.c @@ -373,13 +373,20 @@ int rrd_archive_create(const char *source_dir, const char *working_dir, const ch } } -// Generate archive filename: ___RRD_DEBUG_LOGS.tgz -int rrd_archive_generate_filename(const char *mac, const char *issue_type, const char *timestamp, char *filename, size_t size) { +// Generate archive filename. +// Without suffix: ___RRD_DEBUG_LOGS.tgz +// With suffix: ____RRD_DEBUG_LOGS.tgz +int rrd_archive_generate_filename(const char *mac, const char *issue_type, const char *timestamp, const char *suffix, char *filename, size_t size) { if (!mac || !issue_type || !timestamp || !filename || size < 128) { RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s] Invalid parameters\n", __FUNCTION__); return -1; } - int ret = snprintf(filename, size, "%s_%s_%s_RRD_DEBUG_LOGS.tgz", mac, issue_type, timestamp); + int ret; + if (suffix && suffix[0] != '\0') { + ret = snprintf(filename, size, "%s_%s_%s_%s_RRD_DEBUG_LOGS.tgz", mac, issue_type, timestamp, suffix); + } else { + ret = snprintf(filename, size, "%s_%s_%s_RRD_DEBUG_LOGS.tgz", mac, issue_type, timestamp); + } if (ret < 0 || (size_t)ret >= size) { RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s] Filename truncated\n", __FUNCTION__); return -1; diff --git a/src/rrd_archive.h b/src/rrd_archive.h index da42adc4..6d54c761 100644 --- a/src/rrd_archive.h +++ b/src/rrd_archive.h @@ -23,7 +23,9 @@ #include int rrd_archive_create(const char *source_dir, const char *working_dir, const char *archive_filename); -int rrd_archive_generate_filename(const char *mac, const char *issue_type, const char *timestamp, char *filename, size_t size); +/* suffix is optional (NULL or ""); when non-empty it is appended AFTER the + * timestamp field: {MAC}_{ISSUE_TYPE}_{TIMESTAMP}_{SUFFIX}_RRD_DEBUG_LOGS.tgz */ +int rrd_archive_generate_filename(const char *mac, const char *issue_type, const char *timestamp, const char *suffix, char *filename, size_t size); int rrd_archive_cleanup(const char *archive_path); int rrd_archive_check_cpu_usage(float *cpu_usage); int rrd_archive_adjust_priority(float cpu_usage); 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/rrd_upload.h b/src/rrd_upload.h index 6eebfcbe..6b1d6832 100644 --- a/src/rrd_upload.h +++ b/src/rrd_upload.h @@ -38,7 +38,7 @@ int rrd_upload_execute(const char *log_server, const char *protocol, const char int rrd_upload_check_lock(bool *is_locked); int rrd_upload_wait_for_lock(int max_attempts, int wait_seconds); int rrd_upload_invoke_logupload_api(const char *log_server, const char *protocol, const char *http_link, const char *archive_filename); -int rrd_upload_orchestrate(const char *upload_dir, const char *issue_type); +int rrd_upload_orchestrate(const char *upload_dir, const char *issue_type, const char *suffix); int rrd_upload_cleanup_files(const char *archive_path, const char *source_dir); int rrd_upload_cleanup_source_dir(const char *dir_path); void rrd_upload_cleanup(void); diff --git a/src/unittest/rrdUnitTestRunner.cpp b/src/unittest/rrdUnitTestRunner.cpp index 7b8c6c83..781467b6 100644 --- a/src/unittest/rrdUnitTestRunner.cpp +++ b/src/unittest/rrdUnitTestRunner.cpp @@ -1287,13 +1287,13 @@ class UploadDebugoutputTest : public ::testing::Test TEST_F(UploadDebugoutputTest, HandlesNullParameters) { - result = uploadDebugoutput(NULL, NULL); + result = uploadDebugoutput(NULL, NULL, NULL); ASSERT_EQ(result, 0); } TEST_F(UploadDebugoutputTest, HandlesGoodPath) { - result = uploadDebugoutput("/sample/good_path", "issuename"); + result = uploadDebugoutput("/sample/good_path", "issuename", NULL); ASSERT_NE(result, 0); } @@ -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,205 @@ 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 processIssueTypeInDynamicProfile() from rrdEventProcess --------------- */ class ProcessIssueTypeInDynamicProfileTest : public ::testing::Test { @@ -1989,11 +2194,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 +2404,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 +2431,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 +4003,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); @@ -4033,19 +4353,19 @@ class RRDUploadOrchestrationTest : public ::testing::Test { // Test: Invalid parameters TEST_F(RRDUploadOrchestrationTest, InvalidParametersNull) { - int result = rrd_upload_orchestrate(NULL, "issue_type"); + int result = rrd_upload_orchestrate(NULL, "issue_type", NULL); EXPECT_NE(result, 0); - result = rrd_upload_orchestrate(test_dir, NULL); + result = rrd_upload_orchestrate(test_dir, NULL, NULL); EXPECT_NE(result, 0); - result = rrd_upload_orchestrate(NULL, NULL); + result = rrd_upload_orchestrate(NULL, NULL, NULL); EXPECT_NE(result, 0); } // Test: Valid orchestration flow TEST_F(RRDUploadOrchestrationTest, ValidOrchestrationFlow) { - int result = rrd_upload_orchestrate(test_dir, test_issue_type); + int result = rrd_upload_orchestrate(test_dir, test_issue_type, NULL); // Expected: 0 (success) or reasonable error code EXPECT_GE(result, -1); // At minimum, should not crash } @@ -4139,7 +4459,7 @@ TEST_F(RRDUploadOrchestrationTest, ArchiveFilenameGeneration) { const char *issue = "CPU_HIGH"; const char *timestamp = "2024-12-17-14-30-45PM"; - int result = rrd_archive_generate_filename(mac, issue, timestamp, filename, sizeof(filename)); + int result = rrd_archive_generate_filename(mac, issue, timestamp, NULL, filename, sizeof(filename)); EXPECT_EQ(result, 0); EXPECT_STRNE(filename, ""); @@ -4154,6 +4474,55 @@ TEST_F(RRDUploadOrchestrationTest, ArchiveFilenameGeneration) { EXPECT_STREQ(ext, ".tgz"); } +// Test: Archive filename generation WITH suffix — suffix appears AFTER timestamp +TEST_F(RRDUploadOrchestrationTest, ArchiveFilenameGenerationWithSuffix) { + char filename[256]; + const char *mac = "04B86A12F9F8"; + const char *issue = "DEVICE_DEVICEIP"; + const char *timestamp = "2026-05-06-05-42-35AM"; + const char *suffix = "AB12345"; /* already sanitized/uppercased token */ + + int result = rrd_archive_generate_filename(mac, issue, timestamp, suffix, filename, sizeof(filename)); + EXPECT_EQ(result, 0); + // Expected: 04B86A12F9F8_DEVICE_DEVICEIP_2026-05-06-05-42-35AM_AB12345_RRD_DEBUG_LOGS.tgz + char expected[256]; + snprintf(expected, sizeof(expected), "%s_%s_%s_%s_RRD_DEBUG_LOGS.tgz", mac, issue, timestamp, suffix); + EXPECT_STREQ(filename, expected); + // suffix must come AFTER timestamp in the filename + const char *ts_pos = strstr(filename, timestamp); + const char *sfx_pos = strstr(filename, suffix); + ASSERT_NE(ts_pos, nullptr); + ASSERT_NE(sfx_pos, nullptr); + EXPECT_GT(sfx_pos, ts_pos); +} + +// Test: Empty/NULL suffix produces the same filename as no suffix +TEST_F(RRDUploadOrchestrationTest, ArchiveFilenameGenerationNullOrEmptySuffix) { + char fname_null[256] = {0}; + char fname_empty[256] = {0}; + const char *mac = "AABBCCDDEEFF"; + const char *issue = "CPU_HIGH"; + const char *ts = "2026-01-01-00-00-00AM"; + + EXPECT_EQ(rrd_archive_generate_filename(mac, issue, ts, NULL, fname_null, sizeof(fname_null)), 0); + EXPECT_EQ(rrd_archive_generate_filename(mac, issue, ts, "", fname_empty, sizeof(fname_empty)), 0); + EXPECT_STREQ(fname_null, fname_empty); + // Neither should contain an extra '_' segment between timestamp and _RRD + char expected[256]; + snprintf(expected, sizeof(expected), "%s_%s_%s_RRD_DEBUG_LOGS.tgz", mac, issue, ts); + EXPECT_STREQ(fname_null, expected); +} + +// Test: Suffix with leading '_' and lowercase letters is sanitized before reaching +// rrd_archive_generate_filename (exercised via rrd_upload_orchestrate plumbing) +TEST_F(RRDUploadOrchestrationTest, SuffixSanitizationUppercasesToken) { + char suffix_sanitized[32] = {0}; + // rrd_logproc_convert_issue_type uppercases and passes through alphanumeric/- + int result = rrd_logproc_convert_issue_type("ab12-cd", suffix_sanitized, sizeof(suffix_sanitized)); + EXPECT_EQ(result, 0); + EXPECT_STREQ(suffix_sanitized, "AB12-CD"); +} + // Test: Archive creation in /tmp/rrd/ TEST_F(RRDUploadOrchestrationTest, ArchiveCreation) { char archive_filename[256]; @@ -4310,7 +4679,7 @@ TEST_F(RRDUploadOrchestrationTest, UploadLockCheck) { // Integration test: End-to-end orchestration TEST_F(RRDUploadOrchestrationTest, EndToEndOrchestration) { // This test verifies the entire flow works together - int result = rrd_upload_orchestrate(test_dir, "test.issue"); + int result = rrd_upload_orchestrate(test_dir, "test.issue", NULL); // Result should be a valid return code (0 for success, or specific error code) EXPECT_GE(result, -11); // Within expected error range @@ -4319,7 +4688,7 @@ TEST_F(RRDUploadOrchestrationTest, EndToEndOrchestration) { // Edge case: Invalid directory path TEST_F(RRDUploadOrchestrationTest, InvalidDirectoryPath) { - int result = rrd_upload_orchestrate("/invalid/path/to/logs", "issue"); + int result = rrd_upload_orchestrate("/invalid/path/to/logs", "issue", NULL); EXPECT_NE(result, 0); // Should fail } @@ -4328,7 +4697,7 @@ TEST_F(RRDUploadOrchestrationTest, EmptyDirectoryFailure) { const char *empty_dir = "/tmp/rrd_empty_test"; mkdir(empty_dir, 0755); - int result = rrd_upload_orchestrate(empty_dir, "test_issue"); + int result = rrd_upload_orchestrate(empty_dir, "test_issue", NULL); EXPECT_EQ(result, 6); // Should fail with error code 6 (empty directory) rmdir(empty_dir); @@ -4337,11 +4706,11 @@ TEST_F(RRDUploadOrchestrationTest, EmptyDirectoryFailure) { // Failure case: NULL parameters TEST_F(RRDUploadOrchestrationTest, NullParametersFailure) { // NULL upload_dir - int result = rrd_upload_orchestrate(NULL, "issue"); + int result = rrd_upload_orchestrate(NULL, "issue", NULL); EXPECT_EQ(result, 1); // NULL issue_type - result = rrd_upload_orchestrate(test_dir, NULL); + result = rrd_upload_orchestrate(test_dir, NULL, NULL); EXPECT_EQ(result, 1); } @@ -4390,7 +4759,7 @@ TEST_F(RRDUploadOrchestrationTest, LogUploadEnableHandling) { f.close(); // Test with LOGUPLOAD_ENABLE issue type - int result = rrd_upload_orchestrate(test_dir, "logupload_enable"); + int result = rrd_upload_orchestrate(test_dir, "logupload_enable", NULL); // Should process without error (even if upload fails in test environment) // The important thing is it doesn't crash and handles the live logs @@ -4444,12 +4813,23 @@ 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]; + /* Verify rrd_logproc_convert_issue_type preserves hyphens. + * The suffix is now passed separately (after timestamp), but the + * conversion must still keep hyphens so the suffix token is intact. */ + int result = rrd_logproc_convert_issue_type("Device_DeviceIP_Search-67768-67", sanitized, sizeof(sanitized)); + EXPECT_EQ(result, 0); + EXPECT_STREQ(sanitized, "DEVICE_DEVICEIP_SEARCH-67768-67"); +} + // Performance test: Large directory TEST_F(RRDUploadOrchestrationTest, LargeDirectoryHandling) { // Create multiple log files @@ -4477,7 +4857,7 @@ TEST_F(RRDUploadOrchestrationTest, ConfigurationLoadFailure) { unlink("/etc/dcm.properties"); unlink("/opt/dcm.properties"); - int result = rrd_upload_orchestrate(test_dir, test_issue_type); + int result = rrd_upload_orchestrate(test_dir, test_issue_type, NULL); EXPECT_EQ(result, 3); // Expected error code for config load failure } @@ -4548,23 +4928,23 @@ TEST_F(RRDUploadOrchestrationTest, ArchiveFilenameGenerationFailure) { char filename[256]; // Test with NULL MAC address - int result = rrd_archive_generate_filename(NULL, "ISSUE", "timestamp", filename, sizeof(filename)); + int result = rrd_archive_generate_filename(NULL, "ISSUE", "timestamp", NULL, filename, sizeof(filename)); EXPECT_NE(result, 0); // Test with NULL issue type - result = rrd_archive_generate_filename("00:11:22:33:44:55", NULL, "timestamp", filename, sizeof(filename)); + result = rrd_archive_generate_filename("00:11:22:33:44:55", NULL, "timestamp", NULL, filename, sizeof(filename)); EXPECT_NE(result, 0); // Test with NULL timestamp - result = rrd_archive_generate_filename("00:11:22:33:44:55", "ISSUE", NULL, filename, sizeof(filename)); + result = rrd_archive_generate_filename("00:11:22:33:44:55", "ISSUE", NULL, NULL, filename, sizeof(filename)); EXPECT_NE(result, 0); // Test with NULL output buffer - result = rrd_archive_generate_filename("00:11:22:33:44:55", "ISSUE", "timestamp", NULL, 256); + result = rrd_archive_generate_filename("00:11:22:33:44:55", "ISSUE", "timestamp", NULL, NULL, 256); EXPECT_NE(result, 0); // Test with insufficient buffer size - result = rrd_archive_generate_filename("00:11:22:33:44:55", "ISSUE", "timestamp", filename, 10); + result = rrd_archive_generate_filename("00:11:22:33:44:55", "ISSUE", "timestamp", NULL, filename, 10); EXPECT_NE(result, 0); } diff --git a/src/uploadRRDLogs.c b/src/uploadRRDLogs.c index 14e364ab..8de124a1 100644 --- a/src/uploadRRDLogs.c +++ b/src/uploadRRDLogs.c @@ -34,7 +34,7 @@ // --- Main Orchestration Layer --- -int rrd_upload_orchestrate(const char *upload_dir, const char *issue_type) +int rrd_upload_orchestrate(const char *upload_dir, const char *issue_type, const char *suffix) { // Validate input parameters if (!upload_dir || !issue_type) { @@ -81,15 +81,29 @@ 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}; + // 6. Convert/sanitize issue type (base only — no suffix) + 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; } RDK_LOG(RDK_LOG_INFO, LOG_REMDEBUG, "%s: Issue type sanitized: %s\n", __FUNCTION__, issue_type_sanitized); - // 6.5. Handle LOGUPLOAD_ENABLE special case (matching shell script lines 128-131) + // 6.5. Sanitize suffix (strip leading '_', uppercase); empty when no suffix provided. + /* Convention (enforced by split_issue_type / checkIssueNodeInfo): + * suffix == NULL or suffix[0] == '\0' → no suffix + * suffix starts with '_', then 1-8 data chars (total <= RRD_MAX_SUFFIX_LEN=9) + * After stripping the leading '_', the data portion is at most 8 chars, so + * a 32-byte buffer is more than sufficient (8 data + NUL + headroom). */ + char suffix_sanitized[32] = {0}; + if (suffix && suffix[0] == '_' && suffix[1] != '\0') { + if (rrd_logproc_convert_issue_type(suffix + 1, suffix_sanitized, sizeof(suffix_sanitized)) != 0) { + RDK_LOG(RDK_LOG_WARN, LOG_REMDEBUG, "%s: Failed to sanitize suffix '%s', ignoring\n", __FUNCTION__, suffix); + suffix_sanitized[0] = '\0'; + } + } + + // 6.6. Handle LOGUPLOAD_ENABLE special case (matching shell script lines 128-131) if (strcmp(issue_type_sanitized, "LOGUPLOAD_ENABLE") == 0) { RDK_LOG(RDK_LOG_INFO, LOG_REMDEBUG, "%s: Check and upload live device logs for the issuetype\n", __FUNCTION__); if (rrd_logproc_handle_live_logs(upload_dir) != 0) { @@ -98,8 +112,12 @@ int rrd_upload_orchestrate(const char *upload_dir, const char *issue_type) } // 7. Generate archive filename + // Format: {MAC}_{ISSUE_TYPE}_{TIMESTAMP}_RRD_DEBUG_LOGS.tgz + // With suffix: {MAC}_{ISSUE_TYPE}_{TIMESTAMP}_{SUFFIX}_RRD_DEBUG_LOGS.tgz char archive_filename[256] = {0}; - if (rrd_archive_generate_filename(mac_addr, issue_type_sanitized, timestamp, archive_filename, sizeof(archive_filename)) != 0) { + if (rrd_archive_generate_filename(mac_addr, issue_type_sanitized, timestamp, + suffix_sanitized[0] != '\0' ? suffix_sanitized : NULL, + archive_filename, sizeof(archive_filename)) != 0) { RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "%s: Failed to generate archive filename\n", __FUNCTION__); return 9; }