diff --git a/CHANGELOG.md b/CHANGELOG.md index a22c171d..db51480c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,24 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [1.1.3](https://github.com/rdkcentral/rfc/compare/1.1.2...1.1.3) + +- RDK-57302 : Add support for URL Encoding [`#66`](https://github.com/rdkcentral/rfc/pull/66) +- RDKB-60919: Port Optimized RFCMGR source code to RDKB [`#64`](https://github.com/rdkcentral/rfc/pull/64) +- RDKB-60919: Port Optimized RFCMGR source code to RDKB [`#62`](https://github.com/rdkcentral/rfc/pull/62) +- RDK-57736 [ RFC ] : Increase L1 Test Coverage For RFC above 80% [`#45`](https://github.com/rdkcentral/rfc/pull/45) +- RDKB-60919: Review and cleanup RFC scripts [`13a9c0d`](https://github.com/rdkcentral/rfc/commit/13a9c0d7e4030501e64d2f8d6c8347e1dac6b3aa) +- Update rfc_xconf_handler.cpp [`43c3ccb`](https://github.com/rdkcentral/rfc/commit/43c3ccb8b058838bded67663de543d8c71191023) +- Update rfc_xconf_handler.cpp [`d6ca6b3`](https://github.com/rdkcentral/rfc/commit/d6ca6b328bb3aa38defa5db26dec0c07343bc634) + #### [1.1.2](https://github.com/rdkcentral/rfc/compare/1.1.1...1.1.2) +> 17 July 2025 + - RDKEMW-5971: Review and cleanup RFC scripts [`#55`](https://github.com/rdkcentral/rfc/pull/55) - RDKEMW-5675 [RDKE] onDeviceMgtUpdate sent slightly before RFC processing is complete [`#52`](https://github.com/rdkcentral/rfc/pull/52) - RDKEMW-5593 [RDKE] Excessive Logging in rfcscript.log curl response [`#51`](https://github.com/rdkcentral/rfc/pull/51) +- 1.1.2 release changelog updates [`5aff2b4`](https://github.com/rdkcentral/rfc/commit/5aff2b469f92859affaa670234017c6983d52516) - Merge tag '1.1.1' into develop [`77b8f1f`](https://github.com/rdkcentral/rfc/commit/77b8f1f00a13e231cb0e8a13f526809425a1e362) #### [1.1.1](https://github.com/rdkcentral/rfc/compare/1.1.0...1.1.1) diff --git a/rfcMgr/gtest/gtest_main.cpp b/rfcMgr/gtest/gtest_main.cpp index 72415886..c7a88d29 100644 --- a/rfcMgr/gtest/gtest_main.cpp +++ b/rfcMgr/gtest/gtest_main.cpp @@ -515,10 +515,10 @@ TEST(rfcMgrTest, GetValidPartnerId) { TEST(rfcMgrTest, CreateXconfHTTPUrl) { writeToTr181storeFile("WHOAMI_SUPPORT", "true", "/tmp/device.properties", Plain); - writeToTr181storeFile("Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Bootstrap.OsClass", "TestOsClass", "/opt/secure/RFC/tr181store.ini", Quoted); + writeToTr181storeFile("Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Bootstrap.OsClass", "TestOs Class", "/opt/secure/RFC/tr181store.ini", Quoted); writeToTr181storeFile("Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.AccountInfo.AccountID", "4123705941507160514", "/opt/secure/RFC/tr181store.ini", Quoted); RuntimeFeatureControlProcessor *rfcObj = new RuntimeFeatureControlProcessor(); - rfcObj->_RFCKeyAndValueMap[RFC_PARNER_ID_KEY_STR] = "sky"; + rfcObj->_RFCKeyAndValueMap[RFC_PARTNER_ID_KEY_STR] = "sky"; rfcObj->_RFCKeyAndValueMap[XCONF_URL_KEY_STR] = "https://xconf.xdp.eu-1.xcal.tv"; rfcObj->PreProcessJsonResponse(xconfResp); rfcObj->GetValidPartnerId(); @@ -526,7 +526,7 @@ TEST(rfcMgrTest, CreateXconfHTTPUrl) { rfcObj->GetAccountID(); rfcObj->GetXconfSelect(); std:stringstream url = rfcObj->CreateXconfHTTPUrl(); - EXPECT_EQ(url.str(), "https://xconf.xdp.eu-1.xcal.tv?estbMacAddress=&firmwareVersion=&env=&model=&manufacturer=&controllerId=2504&channelMapId=2345&VodId=15660&partnerId=sky&osClass=TestOsClass&accountId=4123705941507160514&Experience=&version=2"); + EXPECT_EQ(url.str(), "https://xconf.xdp.eu-1.xcal.tv?estbMacAddress=&firmwareVersion=&env=&model=&manufacturer=&controllerId=2504&channelMapId=2345&VodId=15660&partnerId=sky&osClass=TestOs%20Class&accountId=4123705941507160514&Experience=&version=2"); delete rfcObj; } diff --git a/rfcMgr/gtest/mocks/urlHelper.c b/rfcMgr/gtest/mocks/urlHelper.c index e6598d68..e9ac9fd4 100644 --- a/rfcMgr/gtest/mocks/urlHelper.c +++ b/rfcMgr/gtest/mocks/urlHelper.c @@ -930,6 +930,32 @@ bool checkDeviceInternetConnection(long timeout_ms) return status; } +char* urlEncodeString(const char* inputString) +{ + if (!inputString) { + SWLOG_ERROR("Input string is NULL in Function %s\n", __FUNCTION__); + return NULL; + } + char* encodedString = NULL; + CURL *curl = curl_easy_init(); + if (curl) + { + char *output = curl_easy_escape(curl, inputString, 0); + if (output) { + encodedString = strdup(output); + curl_free(output); + } else { + SWLOG_ERROR("curl_easy_escape failed in Function %s\n", __FUNCTION__); + } + curl_easy_cleanup(curl); + } + else + { + SWLOG_ERROR("Error in curl_easy_init in Function %s\n", __FUNCTION__); + } + return encodedString; +} + // Define your write callback function size_t writeFunction(void *contents, size_t size, size_t nmemb, void *userp) { diff --git a/rfcMgr/gtest/mocks/urlHelper.h b/rfcMgr/gtest/mocks/urlHelper.h index 90d4b6fd..01a475be 100644 --- a/rfcMgr/gtest/mocks/urlHelper.h +++ b/rfcMgr/gtest/mocks/urlHelper.h @@ -134,6 +134,7 @@ int allocDowndLoadDataMem( DownloadData *pDwnData, int szDataSize ); bool checkDeviceInternetConnection(long); size_t writeFunction(void *contents, size_t size, size_t nmemb, void *userp); void closeFile(DownloadData *data, struct curlprogress *prog, FILE *fp); +char* urlEncodeString(const char* inputString); #if CURL_DEBUG CURLcode setCurlDebugOpt(CURL *curl, DbgData_t *debug); #endif diff --git a/rfcMgr/rfc_common.h b/rfcMgr/rfc_common.h index 075cf3c6..06f8c721 100644 --- a/rfcMgr/rfc_common.h +++ b/rfcMgr/rfc_common.h @@ -59,6 +59,7 @@ #define RFC_MAX_LEN 64 #define RFC_TMP_PATH "/tmp/RFC/tmp" +#define SECURE_RFC_PATH "/opt/secure/RFC" #define DEFAULT_DL_ALLOC 1024 diff --git a/rfcMgr/rfc_main.cpp b/rfcMgr/rfc_main.cpp index 4b63b8ac..d105cea9 100644 --- a/rfcMgr/rfc_main.cpp +++ b/rfcMgr/rfc_main.cpp @@ -47,6 +47,28 @@ void signal_handler(int sig) exit(0); } +bool createDirectoryIfNotExists(const char* path) { + if (!path || *path == '\0') { + RDK_LOG(RDK_LOG_ERROR, LOG_RFCMGR, "[%s][%d] Invalid path\n", __FUNCTION__, __LINE__); + return false; + } + + if (mkdir(path, 0755) == 0) { + RDK_LOG(RDK_LOG_INFO, LOG_RFCMGR, "[%s][%d] Directory created: %s\n", __FUNCTION__, __LINE__, path); + return true; + } + + if (errno == EEXIST) { + struct stat st; + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + return true; + } + } + + RDK_LOG(RDK_LOG_ERROR, LOG_RFCMGR, "[%s][%d] Failed to create directory: %s (%s)\n", __FUNCTION__, __LINE__, path, strerror(errno)); + return false; +} + int main() { pid_t pid; @@ -68,6 +90,11 @@ int main() rfc::RFCManager* rfcMgr = new rfc::RFCManager(); + if (!createDirectoryIfNotExists(SECURE_RFC_PATH)) { + RDK_LOG(RDK_LOG_ERROR, LOG_RFCMGR, "[%s][%d] RFC: Failed to create RFC directory\n", __FUNCTION__, __LINE__); + exit(EXIT_FAILURE); + } + RDK_LOG(RDK_LOG_INFO, LOG_RFCMGR, "[%s][%d] RFC: Starting service, creating lock \n", __FUNCTION__, __LINE__); #if defined(RDKB_SUPPORT) diff --git a/rfcMgr/rfc_manager.cpp b/rfcMgr/rfc_manager.cpp index 3389ab94..0a23f19c 100644 --- a/rfcMgr/rfc_manager.cpp +++ b/rfcMgr/rfc_manager.cpp @@ -498,11 +498,16 @@ namespace rfc { cronValues[1] = (cronValues[1] == 0) ? 23 : cronValues[1] - 1; } - // Build adjusted cron string std::string adjustedCron; for (size_t i = 0; i < 5; i++) { if (i > 0) adjustedCron += " "; - adjustedCron += std::to_string(cronValues[i]); + + // Use original cronParts if it was a wildcard, otherwise use adjusted cronValues + if (cronParts[i] == "*") { + adjustedCron += "*"; + } else { + adjustedCron += std::to_string(cronValues[i]); + } } RDK_LOG(RDK_LOG_INFO, LOG_RFCMGR, "[%s][%d] Configuring cron job: %s\n", __FUNCTION__, __LINE__, adjustedCron.c_str()); diff --git a/rfcMgr/rfc_mgr_key.h b/rfcMgr/rfc_mgr_key.h index 9385a924..edcded77 100644 --- a/rfcMgr/rfc_mgr_key.h +++ b/rfcMgr/rfc_mgr_key.h @@ -28,7 +28,7 @@ #define BOOTSTRAP_XCONF_URL_KEY_STR "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Bootstrap.XconfUrl" #endif #define RFC_ACCOUNT_ID_KEY_STR "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.AccountInfo.AccountID" -#define RFC_PARNER_ID_KEY_STR "Device.DeviceInfo.X_RDKCENTRAL-COM_Syndication.PartnerId" +#define RFC_PARTNER_ID_KEY_STR "Device.DeviceInfo.X_RDKCENTRAL-COM_Syndication.PartnerId" #define RFC_OSCLASS_KEY_STR "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Bootstrap.OsClass" #define RFC_PARTNERNAME_KEY_STR "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Bootstrap.PartnerName" #define RFC_CONFIG_SET_HASH "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Control.ConfigSetHash" diff --git a/rfcMgr/rfc_xconf_handler.cpp b/rfcMgr/rfc_xconf_handler.cpp index 9102d672..f695ef21 100644 --- a/rfcMgr/rfc_xconf_handler.cpp +++ b/rfcMgr/rfc_xconf_handler.cpp @@ -41,7 +41,7 @@ extern "C" { #include #include #include - + int RuntimeFeatureControlProcessor:: InitializeRuntimeFeatureControlProcessor(void) { std::string rfc_file; @@ -705,7 +705,7 @@ void RuntimeFeatureControlProcessor::GetAccountID() RDK_LOG(RDK_LOG_DEBUG, LOG_RFCMGR, "GetAccountID: AccountID = %s\n", tempbuf); _accountId = tempbuf; #ifdef RDKB_SUPPORT - if (access("/tmp/.timeValue", F_OK) != 0) + if (access("/tmp/RFC/.timeValue", F_OK) != 0) { // Time file doesn't exist, set AccountID to Unknown _accountId = "Unknown"; @@ -1480,6 +1480,28 @@ int RuntimeFeatureControlProcessor::ProcessRuntimeFeatureControlReq() return result; } +bool urlencodingInterface(const std::string& input, std::stringstream& output) { + char* temp = urlEncodeString(input.c_str()); + if (temp) { + output << temp; + free(temp); + return true; + } + return false; +} + +void EncodeString(const std::string& key, const std::string& value, std::stringstream& output, const std::string& appender = "") { + if (!key.empty()) { + output << key; + if (!value.empty()) { + urlencodingInterface(value, output); + } + if (!appender.empty()) { + output << appender; + } + } +} + std::stringstream RuntimeFeatureControlProcessor::CreateXconfHTTPUrl() { std::stringstream url; @@ -1497,8 +1519,33 @@ std::stringstream RuntimeFeatureControlProcessor::CreateXconfHTTPUrl() url << "accountId=" << _accountId << "&"; url << "Experience=" << _experience << "&"; url << "version=" << 2; - + + #ifndef URLENCODING_DISABLED + std::stringstream encodedUrl; + encodedUrl << _xconf_server_url << "?"; + + EncodeString("estbMacAddress=", _estb_mac_address, encodedUrl, "&"); + EncodeString("firmwareVersion=", _firmware_version, encodedUrl, "&"); + EncodeString("env=", _build_type_str, encodedUrl, "&"); + EncodeString("model=", _model_number, encodedUrl, "&"); + EncodeString("manufacturer=", _manufacturer, encodedUrl, "&"); + + encodedUrl << "controllerId=" << RFC_VIDEO_CONTROL_ID << "&"; + encodedUrl << "channelMapId=" << RFC_CHANNEL_MAP_ID << "&"; + encodedUrl << "VodId=" << RFC_VIDEO_VOD_ID << "&"; + + EncodeString("partnerId=", _partner_id, encodedUrl, "&"); + EncodeString("osClass=", _osclass, encodedUrl, "&"); + EncodeString("accountId=", _accountId, encodedUrl, "&"); + EncodeString("Experience=", _experience, encodedUrl, "&"); + encodedUrl << "version=2"; + + RDK_LOG(RDK_LOG_INFO, LOG_RFCMGR, "[%s][%d] Encoding is enabled plain URL: %s\n", __FUNCTION__, __LINE__, url.str().c_str()); + + return encodedUrl; // Use encoded URL + #else return url; + #endif } void RuntimeFeatureControlProcessor::GetStoredHashAndTime( std ::string &valueHash, std::string &valueTime ) @@ -1873,7 +1920,7 @@ void RuntimeFeatureControlProcessor::GetValidPartnerId() { /* Get Valid Partner ID*/ - std::string value = _RFCKeyAndValueMap[RFC_PARNER_ID_KEY_STR]; + std::string value = _RFCKeyAndValueMap[RFC_PARTNER_ID_KEY_STR]; if(value.empty()) { diff --git a/test/functional-tests/features/rfc_xconf_communication.feature b/test/functional-tests/features/rfc_xconf_communication.feature index 88eb449e..71030ec0 100644 --- a/test/functional-tests/features/rfc_xconf_communication.feature +++ b/test/functional-tests/features/rfc_xconf_communication.feature @@ -30,4 +30,7 @@ Feature: RFC Manager XCONF Communication and Error Handling When the RFC manager binary is run Then an error message "cURL Return : 0 HTTP Code : 404" should be logged - + Scenario: URL Encoding + Given all the properties to run RFC manager is available and running + When RFC manager binary is communicating with XCONF server + Then the URL should be percentage encoded \ No newline at end of file diff --git a/test/functional-tests/tests/rfc_test_helper.py b/test/functional-tests/tests/rfc_test_helper.py index 3b359d29..6e5348a0 100644 --- a/test/functional-tests/tests/rfc_test_helper.py +++ b/test/functional-tests/tests/rfc_test_helper.py @@ -149,6 +149,19 @@ def grep_log_file(log_file: str, search_string: str) -> bool: print(f"An error occurred while running grep: {e}") return False +def search_log_file(log_file: str, search_string: str) -> str: + result = subprocess.run( + f'grep -r "{search_string}" {log_file}* | tail -n 1', + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + if result.returncode == 0: + return result.stdout + else : + return result.stderr + def rfc_run_binary() -> None: """ diff --git a/test/functional-tests/tests/test_rfc_xconf_communication.py b/test/functional-tests/tests/test_rfc_xconf_communication.py index af9808d0..d660ebd4 100644 --- a/test/functional-tests/tests/test_rfc_xconf_communication.py +++ b/test/functional-tests/tests/test_rfc_xconf_communication.py @@ -19,6 +19,8 @@ import os from rfc_test_helper import * +import urllib.parse + def get_rfc_old_FW() -> str | None: @@ -165,12 +167,28 @@ def test_rfcMgr_xconf_communication() -> None: post_rfc_run_rfc_version = get_rfc_old_FW() rfc_key1, rfc_value1 = get_tr181_file_key_value() ERROR_MSG1 = f"COMPLETED RFC PASS" - + assert grep_log_file(RFC_LOG_FILE, ERROR_MSG1), f"Expected '{ERROR_MSG1}' in log file." assert (post_rfc_run_tr181_File != pre_rfc_run_tr181_File), f"'{TR181_INI_FILE}' creation success on rfc run." assert (pre_rfc_run_rfc_version != post_rfc_run_rfc_version) and (device_fw_version == post_rfc_run_rfc_version), f"'{device_fw_version}' updated in '{TR181_INI_FILE}'" assert (rfc_key1 == TEST_RFC_PARAM_KEY1) and (rfc_value1 == TEST_RFC_PARAM_VAL1), f"'{TEST_RFC_PARAM_KEY1}' key is set with '{TEST_RFC_PARAM_VAL1} in {TR181_INI_FILE}'" + SEARCH_MSG = f"Encoding is enabled plain URL: " + plain_url = search_log_file(RFC_LOG_FILE,SEARCH_MSG) + SEARCH_MSG_ENCODED = f"Xconf Request : " + encoded_url = search_log_file(RFC_LOG_FILE,SEARCH_MSG_ENCODED) + try: + url = plain_url.split(SEARCH_MSG,1)[1].strip().strip("[]") + print(f"Plain url : {url}") + coded_url = encoded_url.split(SEARCH_MSG_ENCODED,1)[1].strip().strip("[]") + print(f"Encoded url : {coded_url}") + decoded_url = urllib.parse.unquote(coded_url) + print(f"Decoded url : {decoded_url}") + assert url == decoded_url, f"Decoded URL does not match plain URL!\nDecoded: {decoded_url}\nPlain: {url}" + except Exception as e: + print(f"Exception during URL encoding check: {e}") + assert False, f"Exception during URL encoding check: {e}" + finally: if os.path.exists(RFC_OLD_FW_FILE + "_bak"): rename_file(RFC_OLD_FW_FILE + "_bak", RFC_OLD_FW_FILE)