Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 273 additions & 0 deletions src/rrdInterface.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,72 @@ key_t key = 1234;
uint32_t gWebCfgBloBVersion = 0;
rbusHandle_t rrdRbusHandle;

// Global storage for profile category
char RRDProfileCategory[256] = "all";

// Helper functions for profile category file-based storage
int load_profile_category(void) {
FILE *fp = fopen(RRD_PROFILE_CATEGORY_FILE, "r");
if (fp) {
Comment on lines +36 to +42
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RRDProfileCategory and the helper functions load_profile_category/save_profile_category are not file-local, so they export new global symbols from rrdInterface.c. Make these static (and consider making the buffer static too) to avoid symbol collisions and accidental external use.

Copilot uses AI. Check for mistakes.
if (fgets(RRDProfileCategory, sizeof(RRDProfileCategory), fp)) {
// Remove trailing newline
char *newline = strchr(RRDProfileCategory, '\n');
if (newline) *newline = '\0';
fclose(fp);
return 0;
}
fclose(fp);
}
// Default to "all" if file doesn't exist or read fails
strncpy(RRDProfileCategory, "all", sizeof(RRDProfileCategory) - 1);
RRDProfileCategory[sizeof(RRDProfileCategory) - 1] = '\0';
return -1;
}

int save_profile_category(void) {
FILE *fp = fopen(RRD_PROFILE_CATEGORY_FILE, "w");
if (fp) {
fprintf(fp, "%s\n", RRDProfileCategory);
fclose(fp);
return 0;
}
return -1;
Comment on lines +58 to +65
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save_profile_category writes to a predictable path under /tmp using plain fopen(...,"w"), which is vulnerable to symlink/hardlink attacks (can overwrite arbitrary files if the process has privileges). Prefer using open() with O_NOFOLLOW|O_CREAT|O_TRUNC and restrictive permissions (e.g., 0600), then fdopen(), or store this state in a non-world-writable directory.

Copilot uses AI. Check for mistakes.
}

#define DATA_HANDLER_SET_MACRO \
{ \
NULL, \
rrd_SetHandler, \
NULL, \
NULL, \
NULL, \
NULL \
}

#define DATA_HANDLER_GET_MACRO \
{ \
rrd_GetHandler, \
NULL, \
NULL, \
NULL, \
NULL, \
NULL \
}

// Data elements for profile data RBUS provider
rbusDataElement_t profileDataElements[2] = {
{
RRD_SET_PROFILE_EVENT,
RBUS_ELEMENT_TYPE_PROPERTY,
DATA_HANDLER_SET_MACRO
},
Comment on lines +88 to +94
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New RBUS provider globals (profileDataElements / handler macros) are compiled even when GTEST_ENABLE is defined, but the unit test build includes rrdInterface.c with -DGTEST_ENABLE and the mock RBUS headers don’t define rbusDataElement_t / rbus_regDataElements / rbusProperty_t APIs. This will break the gtest build; wrap the provider registration + handlers + data element definitions in #if !defined(GTEST_ENABLE) or extend the mocks to cover the required RBUS types/APIs.

Copilot uses AI. Check for mistakes.
{
RRD_GET_PROFILE_EVENT,
RBUS_ELEMENT_TYPE_PROPERTY,
DATA_HANDLER_GET_MACRO
}
};

/*Function: RRD_subscribe
*Details: This helps to perform Bus init/connect and event handler registration for receiving
*events from the TR181 parameter.
Expand Down Expand Up @@ -103,6 +169,21 @@ int RRD_subscribe()
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: SUCCESS: RBUS Event Subscribe for RRD done! \n", __FUNCTION__, __LINE__);
}

// Load profile category from file
if (load_profile_category() == 0) {
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Loaded profile category: %s\n", __FUNCTION__, __LINE__, RRDProfileCategory);
} else {
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: No stored profile category, defaulting to 'all'\n", __FUNCTION__, __LINE__);
}

// Register RBUS data elements for profile data provider
ret = rbus_regDataElements(rrdRbusHandle, 2, profileDataElements);
if (ret != RBUS_ERROR_SUCCESS) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: RBUS regDataElements failed with error: %d\n", __FUNCTION__, __LINE__, ret);
} else {
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: SUCCESS: RBUS profile data elements registered\n", __FUNCTION__, __LINE__);
Comment on lines +180 to +184
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RRD_subscribe now overwrites ret with the result of rbus_regDataElements, which can mask a prior failure from rbusEvent_SubscribeEx (function may return success even though event subscription failed). Preserve the earlier error (e.g., only register data elements when ret == RBUS_ERROR_SUCCESS, or use a separate variable).

Suggested change
ret = rbus_regDataElements(rrdRbusHandle, 2, profileDataElements);
if (ret != RBUS_ERROR_SUCCESS) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: RBUS regDataElements failed with error: %d\n", __FUNCTION__, __LINE__, ret);
} else {
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: SUCCESS: RBUS profile data elements registered\n", __FUNCTION__, __LINE__);
{
rbusError_t regRet = rbus_regDataElements(rrdRbusHandle, 2, profileDataElements);
if (regRet != RBUS_ERROR_SUCCESS) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: RBUS regDataElements failed with error: %d\n", __FUNCTION__, __LINE__, regRet);
} else {
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: SUCCESS: RBUS profile data elements registered\n", __FUNCTION__, __LINE__);
}
if (ret == RBUS_ERROR_SUCCESS) {
ret = regRet;
}

Copilot uses AI. Check for mistakes.
}

webconfigFrameworkInit();
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: ...Exiting.. \n", __FUNCTION__, __LINE__);
return ret;
Expand Down Expand Up @@ -428,6 +509,14 @@ int RRD_unsubscribe()
return ret;
}

// Unregister RBUS data elements for profile data provider
ret = rbus_unregDataElements(rrdRbusHandle, 2, profileDataElements);
if (ret != 0) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: RBUS unregDataElements failed with error: %d\n", __FUNCTION__, __LINE__, ret);
} else {
Comment on lines +512 to +516
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RRD_unsubscribe assigns ret from rbus_unregDataElements and then immediately overwrites it with rbus_close’s result, so unregistration failures can be lost (function may return success even though unreg failed). Consider preserving the first failure and/or returning a combined error status.

Copilot uses AI. Check for mistakes.
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: SUCCESS: RBUS profile data elements unregistered\n", __FUNCTION__, __LINE__);
}

ret = rbus_close(rrdRbusHandle);
if (ret != 0)
{
Expand All @@ -443,3 +532,187 @@ int RRD_unsubscribe()
#endif
return ret;
}
/**
* @brief Set handler for RDK Remote Debugger profile category selection
*/
rbusError_t rrd_SetHandler(rbusHandle_t handle, rbusProperty_t prop, rbusSetHandlerOptions_t* opts)
{
(void)handle;
(void)opts;

char const* propertyName = rbusProperty_GetName(prop);
rbusValue_t value = rbusProperty_GetValue(prop);
rbusValueType_t type = rbusValue_GetType(value);
Comment on lines +538 to +545
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new RBUS set/get handler behavior (category persistence and JSON response construction) isn’t covered by the existing gtest suite that includes rrdInterface.c. Add unit tests for rrd_SetHandler/rrd_GetHandler covering valid category set, invalid type/oversize input, missing/unreadable JSON file, and category-not-found/nested-category cases.

Copilot uses AI. Check for mistakes.

RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Set handler called for [%s]\n", __FUNCTION__, __LINE__, propertyName);

if(strcmp(propertyName, RRD_SET_PROFILE_EVENT) == 0) {
if (type == RBUS_STRING) {
const char* str = rbusValue_GetString(value, NULL);
if(strlen(str) > 255) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: String too long for setProfileData\n", __FUNCTION__, __LINE__);
return RBUS_ERROR_INVALID_INPUT;
}

strncpy(RRDProfileCategory, str, sizeof(RRDProfileCategory)-1);
RRDProfileCategory[sizeof(RRDProfileCategory)-1] = '\0';
RDK_LOG(RDK_LOG_INFO, LOG_REMDEBUG, "[%s:%d]: setProfileData value: %s\n", __FUNCTION__, __LINE__, RRDProfileCategory);

// Store the category selection to file
if(save_profile_category() != 0) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Failed to store profile category\n", __FUNCTION__, __LINE__);
return RBUS_ERROR_BUS_ERROR;
}

RDK_LOG(RDK_LOG_INFO, LOG_REMDEBUG, "[%s:%d]: Successfully set profile category to: %s\n", __FUNCTION__, __LINE__, RRDProfileCategory);
return RBUS_ERROR_SUCCESS;
} else {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Invalid type for setProfileData\n", __FUNCTION__, __LINE__);
return RBUS_ERROR_INVALID_INPUT;
}
}

return RBUS_ERROR_INVALID_INPUT;
}

/**
* @brief Get handler for RDK Remote Debugger profile data retrieval
*/
rbusError_t rrd_GetHandler(rbusHandle_t handle, rbusProperty_t prop, rbusGetHandlerOptions_t* opts)
{
(void)handle;
(void)opts;

char const* propertyName = rbusProperty_GetName(prop);
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Get handler called for [%s]\n", __FUNCTION__, __LINE__, propertyName);

if(strcmp(propertyName, RRD_GET_PROFILE_EVENT) == 0) {
const char *filename = "/etc/rrd/remote_debugger.json";

FILE *fp = fopen(filename, "rb");
if (fp) {
fseek(fp, 0L, SEEK_END);
long fileSz = ftell(fp);
rewind(fp);

if (fileSz > 0 && fileSz < 32768) {
char *jsonBuffer = malloc(fileSz + 1);
if (jsonBuffer) {
Comment on lines +592 to +600
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rrd_GetHandler returns RBUS_ERROR_SUCCESS even when file size is out of bounds, malloc fails, fread is short, or JSON parsing fails (and may leave the property unset). Return an appropriate RBUS error and/or set a deterministic empty JSON value on these failure paths.

Copilot uses AI. Check for mistakes.
size_t bytesRead = fread(jsonBuffer, 1U, (size_t)fileSz, fp);
jsonBuffer[bytesRead] = '\0';

cJSON *json = cJSON_Parse(jsonBuffer);
if (json) {
char *result_str = NULL;
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: JSON parsed successfully, processing categories\n", __FUNCTION__, __LINE__);

if (strlen(RRDProfileCategory) == 0 || strcmp(RRDProfileCategory, "all") == 0) {
// Return all issue types grouped by categories (exclude nested structures like DeepSleep)
cJSON *response = cJSON_CreateObject();

cJSON *category = NULL;
cJSON_ArrayForEach(category, json) {
if (cJSON_IsObject(category) && category->string) {
// Skip categories with nested subcategories (no direct Commands/Timeout)
bool hasDirectCommands = false;
cJSON *item = NULL;
cJSON_ArrayForEach(item, category) {
if (cJSON_IsObject(item)) {
cJSON *commands = cJSON_GetObjectItem(item, "Commands");
if (commands && cJSON_IsString(commands)) {
hasDirectCommands = true;
break;
}
}
}

if (hasDirectCommands) {
// Create array for this category's issue types
cJSON *issueTypesArray = cJSON_CreateArray();
cJSON *issueType = NULL;
cJSON_ArrayForEach(issueType, category) {
if (cJSON_IsObject(issueType) && issueType->string) {
cJSON_AddItemToArray(issueTypesArray, cJSON_CreateString(issueType->string));
}
}

// Add this category and its issue types to response
if (cJSON_GetArraySize(issueTypesArray) > 0) {
cJSON_AddItemToObject(response, category->string, issueTypesArray);
} else {
cJSON_Delete(issueTypesArray);
}
}
}
}

result_str = cJSON_Print(response);
cJSON_Delete(response);
} else {
// Return specific category issue types
cJSON *category = cJSON_GetObjectItem(json, RRDProfileCategory);
if (category && cJSON_IsObject(category)) {
// Check if this category has direct commands (not nested like DeepSleep)
bool hasDirectCommands = false;
cJSON *item = NULL;
cJSON_ArrayForEach(item, category) {
if (cJSON_IsObject(item)) {
cJSON *commands = cJSON_GetObjectItem(item, "Commands");
if (commands && cJSON_IsString(commands)) {
hasDirectCommands = true;
break;
}
}
}

if (hasDirectCommands) {
cJSON *issueTypes = cJSON_CreateArray();
cJSON *issueType = NULL;
cJSON_ArrayForEach(issueType, category) {
if (cJSON_IsObject(issueType) && issueType->string) {
cJSON_AddItemToArray(issueTypes, cJSON_CreateString(issueType->string));
}
}
result_str = cJSON_Print(issueTypes);
cJSON_Delete(issueTypes);
} else {
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Category %s has nested structure, returning empty\n",
__FUNCTION__, __LINE__, RRDProfileCategory);
result_str = cJSON_Print(cJSON_CreateArray());
}
} else {
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Category %s not found\n",
__FUNCTION__, __LINE__, RRDProfileCategory);
result_str = cJSON_Print(cJSON_CreateArray());
Comment on lines +679 to +686
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memory leak: cJSON_Print(cJSON_CreateArray()) allocates a cJSON array that is never freed (same pattern appears in both the nested-structure and not-found branches). Create the array in a variable, print it, then cJSON_Delete it after printing.

Suggested change
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Category %s has nested structure, returning empty\n",
__FUNCTION__, __LINE__, RRDProfileCategory);
result_str = cJSON_Print(cJSON_CreateArray());
}
} else {
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Category %s not found\n",
__FUNCTION__, __LINE__, RRDProfileCategory);
result_str = cJSON_Print(cJSON_CreateArray());
cJSON *emptyArray = NULL;
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Category %s has nested structure, returning empty\n",
__FUNCTION__, __LINE__, RRDProfileCategory);
emptyArray = cJSON_CreateArray();
result_str = cJSON_Print(emptyArray);
cJSON_Delete(emptyArray);
}
} else {
cJSON *emptyArray = NULL;
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Category %s not found\n",
__FUNCTION__, __LINE__, RRDProfileCategory);
emptyArray = cJSON_CreateArray();
result_str = cJSON_Print(emptyArray);
cJSON_Delete(emptyArray);

Copilot uses AI. Check for mistakes.
}
}

if (result_str) {
rbusValue_t rbusValue;
rbusValue_Init(&rbusValue);
rbusValue_SetString(rbusValue, result_str);
rbusProperty_SetValue(prop, rbusValue);
rbusValue_Release(rbusValue);
free(result_str);
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Successfully returned profile data\n", __FUNCTION__, __LINE__);
}

cJSON_Delete(json);
} else {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Failed to parse JSON from %s\n", __FUNCTION__, __LINE__, filename);
}

free(jsonBuffer);
}
}
fclose(fp);
} else {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Unable to read profile file from %s\n", __FUNCTION__, __LINE__, filename);
return RBUS_ERROR_BUS_ERROR;
}

return RBUS_ERROR_SUCCESS;
}

return RBUS_ERROR_INVALID_INPUT;
}
8 changes: 8 additions & 0 deletions src/rrdInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ extern "C"
#include "rrdCommon.h"
#if !defined(GTEST_ENABLE)
#include "rbus.h"
#include <cjson/cJSON.h>
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#include <cjson/cJSON.h> in this public header is inconsistent with the rest of the codebase (other headers include "cJSON.h") and may not match the project’s include paths (often -I.../cjson expects "cJSON.h"). Since this header doesn’t expose cJSON types, move the include to the .c file (or switch to the project’s standard include form).

Suggested change
#include <cjson/cJSON.h>
#include "cJSON.h"

Copilot uses AI. Check for mistakes.
#ifdef IARMBUS_SUPPORT
#include "libIARM.h"
#include "libIBus.h"
Expand All @@ -45,6 +46,11 @@ extern "C"
#define RRD_PROCESS_NAME "remotedebugger"
#define RRD_RBUS_TIMEOUT 60

// RDK Remote Debugger profile data parameter definitions
#define RRD_SET_PROFILE_EVENT "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.setProfileData"
#define RRD_GET_PROFILE_EVENT "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RDKRemoteDebugger.getProfileData"
#define RRD_PROFILE_CATEGORY_FILE "/tmp/rrd_profile_category"

/*Enum for IARM Events*/
typedef enum _RemoteDebugger_EventId_t {
IARM_BUS_RDK_REMOTE_DEBUGGER_ISSUETYPE = 0,
Expand All @@ -57,6 +63,8 @@ typedef enum _RemoteDebugger_EventId_t {
void _remoteDebuggerEventHandler(rbusHandle_t handle, rbusEvent_t const* event, rbusEventSubscription_t* subscription);
void _remoteDebuggerWebCfgDataEventHandler(rbusHandle_t handle, rbusEvent_t const* event, rbusEventSubscription_t* subscription);
void _rdmDownloadEventHandler(rbusHandle_t handle, rbusEvent_t const* event, rbusEventSubscription_t* subscription);
rbusError_t rrd_SetHandler(rbusHandle_t handle, rbusProperty_t property, rbusSetHandlerOptions_t* opts);
rbusError_t rrd_GetHandler(rbusHandle_t handle, rbusProperty_t prop, rbusGetHandlerOptions_t* opts);
#endif
#if defined(IARMBUS_SUPPORT) || defined(GTEST_ENABLE)
int RRD_IARM_subscribe(void);
Expand Down
Loading