Skip to content
Open
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
3 changes: 3 additions & 0 deletions run_l2.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,6 @@ pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rrd_ba
pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rrd_debug_report_upload.json test/functional-tests/tests/test_rrd_debug_report_upload.py
pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rrd_deepsleep_static.json test/functional-tests/tests/test_rrd_deepsleep_static_report.py
pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rrd_c_api_upload.json test/functional-tests/tests/test_rrd_c_api_upload.py

cp remote_debugger.json /etc/rrd/
pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/test.json test/functional-tests/tests/test.py
328 changes: 328 additions & 0 deletions src/rrdInterface.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,73 @@ key_t key = 1234;
uint32_t gWebCfgBloBVersion = 0;
rbusHandle_t rrdRbusHandle;

// Global storage for profile category
char RRDProfileCategory[256] = "all";
#define MAX_PROFILE_JSON_SIZE 32768

// Helper functions for profile category file-based storage
int load_profile_category(void) {
FILE *fp = fopen(RRD_PROFILE_CATEGORY_FILE, "r");
if (fp) {
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';
Comment on lines +36 to +55
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The PR title suggests only rrdUnitTestRunner.cpp is updated, but this change set also modifies core runtime code (src/rrdInterface.c/.h), unit test mocks, build flags, and functional test tooling. Please update the PR title/description to reflect the broader scope (RBUS profile data provider + tests) so reviewers know what to focus on.

Copilot uses AI. Check for mistakes.
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 +40 to +67
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

load_profile_category / save_profile_category appear to be internal helpers but are declared with external linkage. Consider making them static (or moving declarations to the header if they are intended to be part of the public interface) to keep the module surface area small.

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
},
{
RRD_GET_PROFILE_EVENT,
RBUS_ELEMENT_TYPE_PROPERTY,
DATA_HANDLER_GET_MACRO
}
};
Comment on lines +89 to +101
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

profileDataElements is a file-local implementation detail but is declared with external linkage. Consider making it static (and possibly const) to avoid exporting unnecessary globals from rrdInterface.c and to reduce the chance of symbol collisions.

Copilot uses AI. Check for mistakes.

/*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 +170,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 {
Comment on lines +180 to +184
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

RRD_subscribe reuses ret for rbus_regDataElements and then returns it, which can mask a prior failure from rbusEvent_SubscribeEx. Consider preserving the event-subscribe result (or returning early on failure) so callers get the correct error and initialization doesn’t proceed in a partially-initialized state.

Copilot uses AI. Check for mistakes.
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: SUCCESS: RBUS profile data elements registered\n", __FUNCTION__, __LINE__);
Comment on lines +181 to +185
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

RRD_subscribe overwrites the existing "ret" (from RBUS event subscribe / previous steps) with the result of rbus_regDataElements and then returns it. This can incorrectly report success if earlier subscription steps failed but regDataElements succeeds. Preserve the original failure (e.g., only call regDataElements when ret==RBUS_ERROR_SUCCESS, or combine errors without clobbering).

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 reg_ret = rbus_regDataElements(rrdRbusHandle, 2, profileDataElements);
if (reg_ret != RBUS_ERROR_SUCCESS) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: RBUS regDataElements failed with error: %d\n", __FUNCTION__, __LINE__, reg_ret);
if (ret == RBUS_ERROR_SUCCESS) {
ret = reg_ret;
}
} else {
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: SUCCESS: RBUS profile data elements registered\n", __FUNCTION__, __LINE__);
}

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 +510,14 @@ int RRD_unsubscribe()
return ret;
}

// Unregister RBUS data elements for profile data provider
ret = rbus_unregDataElements(rrdRbusHandle, 2, profileDataElements);
if (ret != 0) {
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

RRD_unsubscribe checks "if (ret != 0)" after rbus_unregDataElements. For readability and correctness if RBUS changes error codes, compare against RBUS_ERROR_SUCCESS instead (consistent with the subscribe-side check).

Suggested change
if (ret != 0) {
if (ret != RBUS_ERROR_SUCCESS) {

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

ret = rbus_close(rrdRbusHandle);
Comment on lines +513 to 521
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

RRD_unsubscribe logs failures from rbus_unregDataElements but then overwrites ret with the result of rbus_close and returns that. If unregistration fails but close succeeds, the function will still return success. Consider returning a failure code (or preserving the first error) when unregistration fails so shutdown errors are not silently ignored.

Copilot uses AI. Check for mistakes.
if (ret != 0)
{
Expand All @@ -443,3 +533,241 @@ 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);

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

Comment on lines +544 to +549
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

rrd_SetHandler dereferences/prints propertyName without validating prop or the returned name/value pointers. If RBUS ever invokes the handler with a null/invalid property or a null name/value, this can crash. Add defensive checks for prop, propertyName, value, and the returned string before using strcmp, strlen, or logging with %s.

Copilot uses AI. Check for mistakes.
if(strcmp(propertyName, RRD_SET_PROFILE_EVENT) == 0) {
if (type == RBUS_STRING) {
const char* str = rbusValue_GetString(value, NULL);
Comment on lines +539 to +552
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

rrd_SetHandler dereferences prop via rbusProperty_GetName/GetValue and logs propertyName without checking for null. If RBUS calls this with a null/invalid property (or if the mock passes nullptr), this can segfault before returning an error. Add early validation for prop, propertyName, value, and rbusValue_GetString result before using them, returning RBUS_ERROR_INVALID_INPUT (or BUS_ERROR) as appropriate.

Copilot uses AI. Check for mistakes.
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;
}
Comment on lines +544 to +556
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

rrd_SetHandler dereferences RBUS pointers without validation and assumes rbusValue_GetString returns a non-null string. If prop is NULL, propertyName is NULL, or str is NULL, strcmp/strlen will crash. Add null checks for prop, propertyName, value, and str before use and return RBUS_ERROR_INVALID_INPUT on invalid inputs.

Copilot uses AI. Check for mistakes.

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 Check if a category has direct commands (not nested structure)
*/
bool has_direct_commands(cJSON *category)
{
cJSON *item = NULL;
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

has_direct_commands dereferences "category" via cJSON_ArrayForEach without a NULL check. The unit test calls has_direct_commands(nullptr), which will currently segfault. Add an early return when category is NULL (and optionally when it’s not the expected container type).

Suggested change
cJSON *item = NULL;
cJSON *item = NULL;
if (category == NULL) {
return false;
}
if (!cJSON_IsArray(category) && !cJSON_IsObject(category)) {
return false;
}

Copilot uses AI. Check for mistakes.
cJSON_ArrayForEach(item, category) {
if (cJSON_IsObject(item)) {
cJSON *commands = cJSON_GetObjectItem(item, "Commands");
if (commands && cJSON_IsString(commands)) {
return true;
}
}
}
Comment on lines +582 to +592
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

has_direct_commands iterates assuming each child is an object containing a "Commands" string. But the existing /etc/rrd/remote_debugger.json schema used by rrdJsonParser.c stores issue types as arrays (e.g., category[issueType] is an array), so this will always return false and cause rrd_GetHandler to return an empty response even for valid JSON. Consider updating this helper to match the actual schema (and add a NULL guard for category).

Suggested change
bool has_direct_commands(cJSON *category)
{
cJSON *item = NULL;
cJSON_ArrayForEach(item, category) {
if (cJSON_IsObject(item)) {
cJSON *commands = cJSON_GetObjectItem(item, "Commands");
if (commands && cJSON_IsString(commands)) {
return true;
}
}
}
static bool has_commands_string(cJSON *item)
{
cJSON *commands = NULL;
if (!item || !cJSON_IsObject(item)) {
return false;
}
commands = cJSON_GetObjectItem(item, "Commands");
return (commands && cJSON_IsString(commands) && (commands->valuestring != NULL));
}
bool has_direct_commands(cJSON *category)
{
cJSON *item = NULL;
if (!category) {
return false;
}
if (cJSON_IsArray(category)) {
cJSON_ArrayForEach(item, category) {
if (has_commands_string(item)) {
return true;
}
}
return false;
}
cJSON_ArrayForEach(item, category) {
cJSON *issue = NULL;
if (has_commands_string(item)) {
return true;
}
if (cJSON_IsArray(item)) {
cJSON_ArrayForEach(issue, item) {
if (has_commands_string(issue)) {
return true;
}
}
}
}

Copilot uses AI. Check for mistakes.
return false;
Comment on lines +582 to +593
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

has_direct_commands will dereference category via cJSON_ArrayForEach even when category is NULL, which can segfault (the unit tests also call has_direct_commands(nullptr)). Add a NULL/type guard up front (e.g., return false when category == NULL or when it is not an object/array as expected).

Copilot uses AI. Check for mistakes.
}

/**
* @brief Read and validate JSON profile file
*/
char* read_profile_json_file(const char* filename, long* file_size)
{
FILE *fp = fopen(filename, "rb");
if (!fp) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Unable to read profile file from %s\n", __FUNCTION__, __LINE__, filename);
return NULL;
}
Comment on lines +599 to +605
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

read_profile_json_file calls fopen(filename, ...) unconditionally. Several unit tests call this with filename == nullptr, which is undefined behavior and can crash. Add an early guard (and set *file_size to 0 when provided) when filename (or file_size) is NULL.

Copilot uses AI. Check for mistakes.

Comment on lines +599 to +606
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

read_profile_json_file calls fopen(filename, ...) without validating filename or file_size. The unit tests in this PR call it with nullptr filename and expect a null result, but this implementation will likely crash. Add guards for null inputs and ensure *file_size is set to 0 on all failure paths.

Copilot uses AI. Check for mistakes.
fseek(fp, 0L, SEEK_END);
long fileSz = ftell(fp);
rewind(fp);

if (fileSz <= 0 || fileSz >= MAX_PROFILE_JSON_SIZE) {
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Invalid file size: %ld\n", __FUNCTION__, __LINE__, fileSz);
fclose(fp);
return NULL;
}

char *jsonBuffer = malloc(fileSz + 1);
if (!jsonBuffer) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Memory allocation failed for JSON buffer\n", __FUNCTION__, __LINE__);
fclose(fp);
return NULL;
}

size_t bytesRead = fread(jsonBuffer, 1U, (size_t)fileSz, fp);
jsonBuffer[bytesRead] = '\0';
fclose(fp);

Comment on lines +601 to +627
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

read_profile_json_file does not validate filename or file_size before using them (fopen/printf and *file_size assignment). Passing nullptr (as the tests do) can crash, and failures don’t consistently reset *file_size. Add NULL checks up front and ensure *file_size is set to 0 on all error paths.

Suggested change
FILE *fp = fopen(filename, "rb");
if (!fp) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Unable to read profile file from %s\n", __FUNCTION__, __LINE__, filename);
return NULL;
}
fseek(fp, 0L, SEEK_END);
long fileSz = ftell(fp);
rewind(fp);
if (fileSz <= 0 || fileSz >= MAX_PROFILE_JSON_SIZE) {
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Invalid file size: %ld\n", __FUNCTION__, __LINE__, fileSz);
fclose(fp);
return NULL;
}
char *jsonBuffer = malloc(fileSz + 1);
if (!jsonBuffer) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Memory allocation failed for JSON buffer\n", __FUNCTION__, __LINE__);
fclose(fp);
return NULL;
}
size_t bytesRead = fread(jsonBuffer, 1U, (size_t)fileSz, fp);
jsonBuffer[bytesRead] = '\0';
fclose(fp);
if (file_size == NULL) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: file_size is NULL\n", __FUNCTION__, __LINE__);
return NULL;
}
*file_size = 0;
if (filename == NULL) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: filename is NULL\n", __FUNCTION__, __LINE__);
return NULL;
}
FILE *fp = fopen(filename, "rb");
if (!fp) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Unable to read profile file from %s\n", __FUNCTION__, __LINE__, filename);
return NULL;
}
if (fseek(fp, 0L, SEEK_END) != 0) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Failed to seek profile file %s\n", __FUNCTION__, __LINE__, filename);
fclose(fp);
return NULL;
}
long fileSz = ftell(fp);
if (fileSz < 0) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Failed to determine file size for %s\n", __FUNCTION__, __LINE__, filename);
fclose(fp);
return NULL;
}
rewind(fp);
if (fileSz <= 0 || fileSz >= MAX_PROFILE_JSON_SIZE) {
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Invalid file size: %ld\n", __FUNCTION__, __LINE__, fileSz);
fclose(fp);
return NULL;
}
char *jsonBuffer = malloc(fileSz + 1);
if (!jsonBuffer) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Memory allocation failed for JSON buffer\n", __FUNCTION__, __LINE__);
fclose(fp);
return NULL;
}
size_t bytesRead = fread(jsonBuffer, 1U, (size_t)fileSz, fp);
if (bytesRead != (size_t)fileSz) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Failed to read complete profile file %s\n", __FUNCTION__, __LINE__, filename);
free(jsonBuffer);
fclose(fp);
return NULL;
}
jsonBuffer[fileSz] = '\0';
fclose(fp);

Copilot uses AI. Check for mistakes.
*file_size = fileSz;
Comment on lines +625 to +628
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

read_profile_json_file doesn’t verify that fread read the expected number of bytes and sets *file_size to the ftell() size rather than the actual bytesRead. This can return truncated data while reporting a larger size. Check bytesRead == fileSz (or handle partial reads) and set *file_size consistently.

Suggested change
jsonBuffer[bytesRead] = '\0';
fclose(fp);
*file_size = fileSz;
if (bytesRead != (size_t)fileSz) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Failed to read complete profile file from %s. Expected %ld bytes, read %zu bytes\n", __FUNCTION__, __LINE__, filename, fileSz, bytesRead);
free(jsonBuffer);
fclose(fp);
return NULL;
}
jsonBuffer[bytesRead] = '\0';
fclose(fp);
*file_size = (long)bytesRead;

Copilot uses AI. Check for mistakes.
return jsonBuffer;
Comment on lines +624 to +629
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

read_profile_json_file sets *file_size to fileSz even if fread reads fewer bytes (or fails). This can lead to inconsistent size reporting and silently truncated JSON. Check bytesRead == (size_t)fileSz and treat mismatches as an error (free buffer, return NULL, set *file_size = 0).

Copilot uses AI. Check for mistakes.
}

/**
* @brief Generate JSON for all categories
*/
char* get_all_categories_json(cJSON* json)
{
cJSON *response = cJSON_CreateObject();

cJSON *category = NULL;
cJSON_ArrayForEach(category, json) {
if (cJSON_IsObject(category) && category->string) {
Comment on lines +635 to +641
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

get_all_categories_json assumes json is non-null and will crash if called with null (the cJSON_ArrayForEach macro dereferences the input). Add a null check up front and return a valid empty JSON object (or nullptr, consistently) for null input.

Copilot uses AI. Check for mistakes.
if (has_direct_commands(category)) {
// Create array for this category's issue types
Comment on lines +639 to +643
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

get_all_categories_json only processes categories whose value is a JSON object (cJSON_IsObject(category)). However, the new profileTestValid.json test data models categories as arrays, so this function will return an empty object for that input. Either update the helper to support the array-based schema, or adjust the test JSON to match the schema the production code expects.

Copilot uses AI. Check for mistakes.
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);
}
Comment on lines +638 to +657
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

get_all_categories_json iterates with cJSON_ArrayForEach(category, json) without handling json == NULL, which can crash if callers pass NULL (there’s a unit test doing this). Add an explicit NULL check and return a sensible default (e.g., "{}" or NULL) consistently.

Suggested change
cJSON *category = NULL;
cJSON_ArrayForEach(category, json) {
if (cJSON_IsObject(category) && category->string) {
if (has_direct_commands(category)) {
// 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);
}
if (response == NULL) {
return NULL;
}
if (json != NULL) {
cJSON *category = NULL;
cJSON_ArrayForEach(category, json) {
if (cJSON_IsObject(category) && category->string) {
if (has_direct_commands(category)) {
// 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);
}
}

Copilot uses AI. Check for mistakes.
}
}
}

Comment on lines +638 to +661
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

get_all_categories_json assumes "json" is non-NULL and will crash if called with nullptr (there is a unit test that does exactly that). Either guard against NULL (e.g., return an empty object string or NULL) or remove/replace the NULL-input test.

Suggested change
cJSON *category = NULL;
cJSON_ArrayForEach(category, json) {
if (cJSON_IsObject(category) && category->string) {
if (has_direct_commands(category)) {
// 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);
}
}
}
}
if (response == NULL) {
return NULL;
}
if (json != NULL) {
cJSON *category = NULL;
cJSON_ArrayForEach(category, json) {
if (cJSON_IsObject(category) && category->string) {
if (has_direct_commands(category)) {
// 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);
}
}
}
}
}

Copilot uses AI. Check for mistakes.
char *result_str = cJSON_Print(response);
cJSON_Delete(response);
return result_str;
}

/**
* @brief Generate JSON for specific category
*/
char* get_specific_category_json(cJSON* json, const char* category_name)
{
cJSON *category = cJSON_GetObjectItem(json, category_name);
if (!category || !cJSON_IsObject(category)) {
Comment on lines +670 to +673
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

get_specific_category_json calls cJSON_GetObjectItem(json, category_name) without checking json / category_name for NULL. The unit tests include a NULL-JSON call and will currently segfault. Add input validation (e.g., return an empty object/array or NULL) before calling into cJSON.

Copilot uses AI. Check for mistakes.
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Category %s not found \n", __FUNCTION__, __LINE__, category_name);
return get_all_categories_json(json);
}
Comment on lines +670 to +676
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

get_specific_category_json calls cJSON_GetObjectItem(json, ...) without guarding json/category_name. Unit tests call get_specific_category_json(nullptr, "Video"), which will currently crash. Add NULL checks and define a deterministic return for NULL input (empty array/object or NULL).

Copilot uses AI. Check for mistakes.

if (!has_direct_commands(category)) {
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Category %s has nested structure, returning empty\n",
__FUNCTION__, __LINE__, category_name);
return cJSON_Print(cJSON_CreateArray());
Comment on lines +679 to +681
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

get_specific_category_json returns cJSON_Print(cJSON_CreateArray()) without deleting the created array object, leaking memory. Create the array into a variable, print it, then cJSON_Delete it before returning the printed string.

Suggested change
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Category %s has nested structure, returning empty\n",
__FUNCTION__, __LINE__, category_name);
return cJSON_Print(cJSON_CreateArray());
cJSON *empty_array = NULL;
char *result_str = NULL;
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Category %s has nested structure, returning empty\n",
__FUNCTION__, __LINE__, category_name);
empty_array = cJSON_CreateArray();
result_str = cJSON_Print(empty_array);
cJSON_Delete(empty_array);
return result_str;

Copilot uses AI. Check for mistakes.
Comment on lines +674 to +681
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

When the requested category is missing (or not an object), get_specific_category_json currently falls back to get_all_categories_json. The unit tests added in this PR expect a missing category to return an empty array, and returning all categories is also surprising for API consumers. Consider returning an empty JSON array ("[]") for unknown categories instead of returning all categories.

Suggested change
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Category %s not found \n", __FUNCTION__, __LINE__, category_name);
return get_all_categories_json(json);
}
if (!has_direct_commands(category)) {
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Category %s has nested structure, returning empty\n",
__FUNCTION__, __LINE__, category_name);
return cJSON_Print(cJSON_CreateArray());
cJSON *emptyArray = NULL;
char *result_str = NULL;
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Category %s not found \n", __FUNCTION__, __LINE__, category_name);
emptyArray = cJSON_CreateArray();
if (!emptyArray) {
return NULL;
}
result_str = cJSON_Print(emptyArray);
cJSON_Delete(emptyArray);
return result_str;
}
if (!has_direct_commands(category)) {
cJSON *emptyArray = NULL;
char *result_str = NULL;
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Category %s has nested structure, returning empty\n",
__FUNCTION__, __LINE__, category_name);
emptyArray = cJSON_CreateArray();
if (!emptyArray) {
return NULL;
}
result_str = cJSON_Print(emptyArray);
cJSON_Delete(emptyArray);
return result_str;

Copilot uses AI. Check for mistakes.
}

cJSON *issueTypes = cJSON_CreateArray();
cJSON *issueType = NULL;
cJSON_ArrayForEach(issueType, category) {
if (cJSON_IsObject(issueType) && issueType->string) {
cJSON_AddItemToArray(issueTypes, cJSON_CreateString(issueType->string));
}
}

char *result_str = cJSON_Print(issueTypes);
cJSON_Delete(issueTypes);
return result_str;
}

/**
* @brief Set RBUS property response with JSON string
*/
rbusError_t set_rbus_response(rbusProperty_t prop, const char* json_str)
{
if (!json_str) {
return RBUS_ERROR_BUS_ERROR;
}

rbusValue_t rbusValue;
rbusValue_Init(&rbusValue);
rbusValue_SetString(rbusValue, json_str);
rbusProperty_SetValue(prop, rbusValue);
rbusValue_Release(rbusValue);

Comment on lines +702 to +711
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

set_rbus_response should validate prop and check return codes from rbusValue_Init / rbusValue_SetString. As written, a NULL prop (or init failure) will still call into rbusProperty_SetValue, risking a crash and hiding RBUS failures. Return an appropriate RBUS error when any step fails.

Suggested change
if (!json_str) {
return RBUS_ERROR_BUS_ERROR;
}
rbusValue_t rbusValue;
rbusValue_Init(&rbusValue);
rbusValue_SetString(rbusValue, json_str);
rbusProperty_SetValue(prop, rbusValue);
rbusValue_Release(rbusValue);
rbusError_t error;
rbusValue_t rbusValue = NULL;
if (!prop || !json_str) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Invalid input: prop=%p json_str=%p\n",
__FUNCTION__, __LINE__, (void*)prop, (void*)json_str);
return RBUS_ERROR_INVALID_INPUT;
}
error = rbusValue_Init(&rbusValue);
if (error != RBUS_ERROR_SUCCESS) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: rbusValue_Init failed with error %d\n",
__FUNCTION__, __LINE__, error);
return error;
}
error = rbusValue_SetString(rbusValue, json_str);
if (error != RBUS_ERROR_SUCCESS) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: rbusValue_SetString failed with error %d\n",
__FUNCTION__, __LINE__, error);
rbusValue_Release(rbusValue);
return error;
}
rbusProperty_SetValue(prop, rbusValue);
rbusValue_Release(rbusValue);

Copilot uses AI. Check for mistakes.
RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Successfully returned profile data\n", __FUNCTION__, __LINE__);
return RBUS_ERROR_SUCCESS;
Comment on lines +706 to +713
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

set_rbus_response ignores the return values from rbusValue_Init and rbusValue_SetString and always returns RBUS_ERROR_SUCCESS for non-null json_str. If either RBUS call fails, this will report success while not actually setting a valid response. Capture and check those return codes and return an error if any step fails.

Copilot uses AI. Check for mistakes.
}

/**
* @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) {
return RBUS_ERROR_INVALID_INPUT;
}

const char *filename = "/etc/rrd/remote_debugger.json";
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

rrd_GetHandler hardcodes the profile filename as "/etc/rrd/remote_debugger.json" even though the codebase already defines RRD_JSON_FILE (see src/rrdCommon.h:49). Using the existing macro avoids duplication and keeps file-path changes centralized.

Suggested change
const char *filename = "/etc/rrd/remote_debugger.json";
const char *filename = RRD_JSON_FILE;

Copilot uses AI. Check for mistakes.
long file_size;

// Read JSON file
char *jsonBuffer = read_profile_json_file(filename, &file_size);
Comment on lines +731 to +735
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

rrd_GetHandler hardcodes the profile JSON path as /etc/rrd/remote_debugger.json, but the codebase already defines RRD_JSON_FILE in rrdCommon.h. Using the shared constant avoids path drift between components/tests.

Copilot uses AI. Check for mistakes.
if (!jsonBuffer) {
return RBUS_ERROR_BUS_ERROR;
}

// Parse JSON
cJSON *json = cJSON_Parse(jsonBuffer);
if (!json) {
RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Failed to parse JSON from %s\n", __FUNCTION__, __LINE__, filename);
free(jsonBuffer);
return RBUS_ERROR_BUS_ERROR;
}

RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: JSON parsed successfully, processing categories\n", __FUNCTION__, __LINE__);

// Generate appropriate JSON response
char *result_str = NULL;
if (strlen(RRDProfileCategory) == 0 || strcmp(RRDProfileCategory, "all") == 0) {
result_str = get_all_categories_json(json);
} else {
result_str = get_specific_category_json(json, RRDProfileCategory);
}

// Set RBUS response
rbusError_t error = set_rbus_response(prop, result_str);

// Log success if getHandler completed successfully
if (error == RBUS_ERROR_SUCCESS) {
RDK_LOG(RDK_LOG_INFO, LOG_REMDEBUG, "[%s:%d]: getHandler completed successfully for property [%s] with category [%s]\n",
__FUNCTION__, __LINE__, propertyName, RRDProfileCategory);
}

// Cleanup
cJSON_Delete(json);
free(jsonBuffer);
free(result_str);
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

result_str is produced by cJSON_Print, which should be released with cJSON_free (the repo already uses cJSON_free for cJSON_Print results). Using plain free can be incorrect when cJSON is built with custom allocators. Switch to cJSON_free(result_str) here (and keep allocation/freeing conventions consistent).

Suggested change
free(result_str);
cJSON_free(result_str);

Copilot uses AI. Check for mistakes.

return error;
}
Loading
Loading