diff --git a/Makefile.am b/Makefile.am index 3d36a1ff..cd15f595 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,7 +27,7 @@ AM_LDFLAGS += -lrdkloggers -ldwnlutil -lfwutils -lsecure_wrapper -lparsejson -lp AM_CFLAGS += $(GLIB_CFLAGS) AM_LDFLAGS += $(GLIB_LIBS) # Build the RDK upgrade shared library -lib_LTLIBRARIES = librdksw_upgrade.la librdksw_rfcIntf.la librdksw_iarmIntf.la librdksw_jsonparse.la librdksw_flash.la librdksw_fwutils.la +lib_LTLIBRARIES = librdksw_upgrade.la librdksw_rfcIntf.la librdksw_iarmIntf.la librdksw_jsonparse.la librdksw_flash.la librdksw_fwutils.la librdkFwupdateMgr.la librdksw_upgrade_la_SOURCES = \ ${top_srcdir}/src/rdkv_upgrade.c\ @@ -118,6 +118,27 @@ librdksw_flash_la_LIBADD = $(AM_LDFLAGS) librdksw_fwutils_la_LDFLAGS = -shared librdksw_fwutils_la_LIBADD = $(AM_LDFLAGS) +# Client Library Configuration - librdkFwupdateMgr.la +librdkFwupdateMgr_la_SOURCES = \ + ${top_srcdir}/librdkFwupdateMgr/src/ + #${top_srcdir}/librdkFwupdateMgr/src/handle_mgr.c \ + #${top_srcdir}/librdkFwupdateMgr/src/handle_registry.c \ + #${top_srcdir}/librdkFwupdateMgr/src/dbus_client.c \ + #${top_srcdir}/librdkFwupdateMgr/src/api_impl.c + +librdkFwupdateMgr_la_CFLAGS = -fPIC \ + -I${top_srcdir}/librdkFwupdateMgr/include \ + -I${top_srcdir}/librdkFwupdateMgr/src \ + $(GLIB_CFLAGS) -Wall -Wextra -Werror + +librdkFwupdateMgr_la_CPPFLAGS = -fPIC \ + -I${top_srcdir}/librdkFwupdateMgr/include \ + -I${top_srcdir}/librdkFwupdateMgr/src \ + $(GLIB_CFLAGS) + +librdkFwupdateMgr_la_LDFLAGS = -shared -version-info 1:0:0 +librdkFwupdateMgr_la_LIBADD = $(GLIB_LIBS) -lpthread + # Library headers to install librdksw_upgrade_include_HEADERS = \ ${top_srcdir}/src/include/rdkv_upgrade.h @@ -133,12 +154,17 @@ librdksw_fwutils_include_HEADERS = \ ${top_srcdir}/src/deviceutils/deviceutils.h \ ${top_srcdir}/src/deviceutils/device_api.h +# Library Public Header for dbus clients +librdkFwupdateMgr_include_HEADERS = \ + ${top_srcdir}/librdkFwupdateMgr/include/rdkFwupdateMgr_client.h + librdksw_upgrade_includedir = ${includedir} librdksw_rfcIntf_includedir = ${includedir} librdksw_iarmIntf_includedir = ${includedir} librdksw_jsonparse_includedir = ${includedir} librdksw_flash_includedir = ${includedir} librdksw_fwutils_includedir = ${includedir} +librdkFwupdateMgr_includedir = ${includedir} # Shared/common headers not tied to specific libraries include_HEADERS = \ diff --git a/cov_build.sh b/cov_build.sh index 86cd4459..13871390 100755 --- a/cov_build.sh +++ b/cov_build.sh @@ -10,5 +10,5 @@ mkdir -p $INSTALL_DIR #Build rdkfwupdater autoreconf -i -./configure --prefix=${INSTALL_DIR} CFLAGS="-DRDK_LOGGER" --enable-extended-logger +./configure --prefix=${INSTALL_DIR} CFLAGS="-DRDK_LOGGER" make && make install diff --git a/librdkFwupdateMgr/include/rdkFwupdateMgr_client.h b/librdkFwupdateMgr/include/rdkFwupdateMgr_client.h new file mode 100644 index 00000000..f8da68d3 --- /dev/null +++ b/librdkFwupdateMgr/include/rdkFwupdateMgr_client.h @@ -0,0 +1,358 @@ +/* + * Copyright 2025 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef RDKFWUPDATEMGR_CLIENT_H +#define RDKFWUPDATEMGR_CLIENT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ======================================================================== + * HANDLE TYPE + * ======================================================================== */ + +/** + * FirmwareInterfaceHandle + * + * This is a string ID that the daemon gives you when you register. + * Think of it like a session ID or ticket number (e.g., "12345"). + * + * You get this from registerProcess() and use it for all other API calls. + * The library owns this string - don't free() it yourself. + * It becomes invalid after you call unregisterProcess(). + */ +typedef char* FirmwareInterfaceHandle; + + +/* ======================================================================== + * STATUS ENUMS + * ======================================================================== */ + +/** + * CheckForUpdateStatus + * + * What happened when we checked for updates? + */ +typedef enum { + FIRMWARE_AVAILABLE = 0, /* New firmware is available to download */ + FIRMWARE_NOT_AVAILABLE = 1, /* You're already on the latest version */ + UPDATE_NOT_ALLOWED = 2, /* Firmware not compatible with this device model */ + FIRMWARE_CHECK_ERROR = 3, /* Something went wrong checking for updates */ + IGNORE_OPTOUT = 4, /* User has opted out and the update is blocked */ + BYPASS_OPTOUT = 5 /* Update available but requires explicit user consent before installation */ +} CheckForUpdateStatus; + +/** + * DownloadStatus + * + * Where are we in the download? + */ +typedef enum { + DWNL_IN_PROGRESS = 0, /* Download is happening now */ + DWNL_COMPLETED = 1, /* Download finished successfully */ + DWNL_ERROR = 2 /* Download failed */ +} DownloadStatus; + +/** + * UpdateStatus + * + * Where are we in flashing the firmware? + */ +typedef enum { + UPDATE_IN_PROGRESS = 0, /* Firmware is being flashed now */ + UPDATE_COMPLETED = 1, /* Firmware flash finished successfully */ + UPDATE_ERROR = 2 /* Firmware flash failed */ +} UpdateStatus; + + +/* ======================================================================== + * DATA STRUCTURES + * ======================================================================== */ + +/* UpdateDetails field size definitions */ +#define MAX_FW_FILENAME_SIZE 128 +#define MAX_FW_URL_SIZE 512 +#define MAX_FW_VERSION_SIZE 64 +#define MAX_REBOOT_IMMEDIATELY_SIZE 12 +#define MAX_DELAY_DOWNLOAD_SIZE 8 +#define MAX_PDRI_VERSION_LEN 64 +#define MAX_PERIPHERAL_VERSION_LEN 256 + +typedef struct { + char FwFileName[MAX_FW_FILENAME_SIZE]; /* Firmware file name */ + char FwUrl[MAX_FW_URL_SIZE]; /* Download URL */ + char FwVersion[MAX_FW_VERSION_SIZE]; /* Firmware version string */ + char RebootImmediately[MAX_REBOOT_IMMEDIATELY_SIZE]; /*Reboot flag ("true" or "false")*/ + char DelayDownload[MAX_DELAY_DOWNLOAD_SIZE]; /* Delay download flag ("true" or "false") */ + char PDRIVersion[MAX_PDRI_VERSION_LEN]; /* PDRI image version.*/ + char PeripheralFirmwares[MAX_PERIPHERAL_VERSION_LEN]; /* Peripheral image version; may be null if not configured*/ +} UpdateDetails; + +/** + * FwInfoData + * + * Information about firmware that's available (or not). + * You get this in your UpdateEventCallback after calling checkForUpdate(). + */ +typedef struct { + char CurrFWVersion[MAX_FW_VERSION_SIZE]; /* Version string */ + UpdateDetails *UpdateDetails; /* details of the update available*/ + CheckForUpdateStatus status; /* Did we find an update or not? */ +} FwInfoData; + +/** + * FwDwnlReq + * + * What firmware do you want to download? + * Fill this out before calling downloadFirmware(). + */ +typedef struct { + const char *firmwareName; /* Filename like "firmware_v2.bin" */ + const char *downloadUrl; /* Where to download from (NULL = let daemon decide) */ + const char *TypeOfFirmware; /* "PCI", "PDRI", or "PERIPHERAL" */ +} FwDwnlReq; + +/** + * FwUpdateReq + * + * Instructions for flashing firmware. + * Fill this out before calling updateFirmware(). + */ +typedef struct { + const char *firmwareName; /* Filename like "firmware_v2.bin" */ + const char *TypeOfFirmware; /* "PCI", "PDRI", or "PERIPHERAL" */ + const char *LocationOfFirmware; /* Where file is (NULL = use /etc/device.properties default) */ + bool rebootImmediately; /* true = reboot right after flash, false = you'll reboot manually */ +} FwUpdateReq; + + +/* ======================================================================== + * API RESULT CODES + * ======================================================================== */ + +/** + * Did the API call succeed or fail? + * Note: This just means the call started successfully. + * Actual results come through callbacks. + */ +typedef enum { + CHECK_FOR_UPDATE_SUCCESS = 0, + CHECK_FOR_UPDATE_FAIL = 1 +} CheckForUpdateResult; + +typedef enum { + RDKFW_DWNL_SUCCESS = 0, + RDKFW_DWNL_FAILED = 1 +} DownloadResult; + +typedef enum { + RDKFW_UPDATE_SUCCESS = 0, + RDKFW_UPDATE_FAILED = 1 +} UpdateResult; + + +/* ======================================================================== + * CALLBACKS + * ======================================================================== */ + +/** + * UpdateEventCallback + * + * Your function that gets called when checkForUpdate() finishes. + * The library calls this from a background thread when it knows if firmware is available. + * + * Parameters: + * fwinfodata - Pointer to update info (version, details, status) + * + * Important notes: + * - The pointer and strings inside are only valid during this callback + * - If you need the data later, copy it with strdup() + * - Don't call other library functions from inside this callback + * - This runs in a background thread, not your main thread + * + + */ +typedef void (*UpdateEventCallback)(const FwInfoData *fwinfodata); + +/** + * DownloadCallback + * + * Your function that gets called repeatedly while firmware downloads. + * The library calls this from a background thread to report progress. + * + * Parameters: + * progress_per - Percentage complete (0 to 100) + * fwdwnlstatus - What's happening (IN_PROGRESS, COMPLETED, or ERROR) + * + * Important notes: + * - This gets called multiple times (0%, 25%, 50%, 75%, 100%) + * - Don't call other library functions from inside this callback + * - This runs in a background thread, not your main thread + * + */ +typedef void (*DownloadCallback)(int download_progress, DownloadStatus fwdwnlstatus); + +/** + * UpdateCallback + * + * Your function that gets called repeatedly while firmware flashes. + * The library calls this from a background thread to report progress. + * + * Parameters: + * progress_per - Percentage complete (0 to 100) + * fwupdatestatus - What's happening (IN_PROGRESS, COMPLETED, or ERROR) + * + * Important notes: + * - This gets called multiple times (0%, 25%, 50%, 75%, 100%) + * - Don't call other library functions from inside this callback + * - This runs in a background thread, not your main thread + * + * API Stability Notice: + * The signature and behavior of this callback may change in future versions + * when HAL (Hardware Abstraction Layer) APIs become available. The daemon + * will provide more granular progress information and device-specific status + * updates once the underlying HAL interface is implemented. Client applications + * should be prepared for potential signature changes in major version updates. + * + */ +typedef void (*UpdateCallback)(int update_progress, UpdateStatus fwupdatestatus); + + +/* ======================================================================== + * PUBLIC API FUNCTIONS + * ======================================================================== */ + +/** + * registerProcess + * + * Connect to the firmware daemon. This is the first thing you call. + * + * Parameters: + * processName - Your app's name (like "VideoPlayer" or "MyApp") + * libVersion - Your app's version (like "1.0" or "2.3.1") + * + * Returns: + * A string ID from the daemon (like "12345") if successful + * NULL if it fails (daemon not running, D-Bus error, etc.) + * + * Important notes: + * - The returned string belongs to the library - don't free() it + * - Save this ID and use it for all other API calls + * - The ID becomes invalid after you call unregisterProcess() + * + */ +#define LIB_VERSION "1.0.0" +FirmwareInterfaceHandle registerProcess(const char *processName, const char *libVersion); + +/** + * unregisterProcess + * + * Disconnect from the firmware daemon. Call this before your app exits. + * + * Parameters: + * handler - The ID you got from registerProcess() + * + * Important notes: + * - Always call this before exiting your app + * - After this, your handle becomes invalid (don't use it anymore) + * - Safe to call with NULL (does nothing) + * + */ +void unregisterProcess(FirmwareInterfaceHandle handler); + +/** + * checkForUpdate + * + * Ask the daemon "Is there new firmware available?" + * This is async - it returns immediately and calls your callback later. + * + * Parameters: + * handle - Your ID from registerProcess() + * callback - Your function that handles the result + * + * Returns: + * CHECK_FOR_UPDATE_SUCCESS - Request started OK + * CHECK_FOR_UPDATE_FAIL - Couldn't start (bad handle, NULL callback, etc.) + * + * Important notes: + * - This returns right away (doesn't wait for answer) + * - Your callback gets called later in a background thread + * - The return code just means we started the check successfully + * - Actual firmware info comes through your callback + * + */ +CheckForUpdateResult checkForUpdate(FirmwareInterfaceHandle handle,UpdateEventCallback callback); + +/** + * downloadFirmware + * + * Download new firmware from the server. + * This is async - it returns immediately and calls your callback as download progresses. + * + * Parameters: + * handle - Your ID from registerProcess() + * fwdwnlreq - Details about what to download (name, URL, type) + * callback - Your function that tracks download progress + * + * Returns: + * RDKFW_DWNL_SUCCESS - Download started OK + * RDKFW_DWNL_FAILED - Couldn't start (bad handle, NULL callback, invalid request, etc.) + * + * Important notes: + * - This returns right away (doesn't wait for download) + * - Your callback gets called multiple times (0%, 50%, 100%, etc.) + * - Set downloadUrl to NULL to let daemon use its default URL + * + */ +DownloadResult downloadFirmware(FirmwareInterfaceHandle handle,const FwDwnlReq *fwdwnlreq,DownloadCallback callback); + +/** + * updateFirmware + * + * Flash the downloaded firmware to the device. + * This is async - it returns immediately and calls your callback as flash progresses. + * WARNING: This modifies your device's firmware. Make sure you downloaded the right file! + * + * Parameters: + * handle - Your ID from registerProcess() + * fwupdatereq - Details about what to flash (name, type, location, reboot flag) + * callback - Your function that tracks flash progress + * + * Returns: + * RDKFW_UPDATE_SUCCESS - Flash started OK + * RDKFW_UPDATE_FAILED - Couldn't start (bad handle, NULL callback, invalid request, etc.) + * + * Important notes: + * - This returns right away (doesn't wait for flash to complete) + * - Your callback gets called multiple times (0%, 50%, 100%, etc.) + * - Set LocationOfFirmware to NULL to use daemon's default path + * - If rebootImmediately is true, device reboots when flash completes + * - This operation is irreversible - double-check your firmware file! + * + */ +UpdateResult updateFirmware(FirmwareInterfaceHandle handle,const FwUpdateReq *fwupdatereq,UpdateCallback callback); + +#ifdef __cplusplus +} +#endif + +#endif /* RDKFWUPDATEMGR_CLIENT_H */ diff --git a/run_l2.sh b/run_l2.sh index 4c6ee5f5..75562ffe 100755 --- a/run_l2.sh +++ b/run_l2.sh @@ -26,7 +26,7 @@ mkdir -p $INSTALL_DIR #Build rdkfwupdater autoreconf -i -./configure --prefix=${INSTALL_DIR} --enable-rdkcertselector=yes --enable-mountutils=yes --enable-rfcapi=yes CFLAGS="-DRDK_LOGGER" --enable-extended-logger +./configure --prefix=${INSTALL_DIR} --enable-rdkcertselector=yes --enable-mountutils=yes --enable-rfcapi=yes CFLAGS="-DRDK_LOGGER" make clean make && make install diff --git a/src/chunk.c b/src/chunk.c index 5dcf9940..2e75192e 100644 --- a/src/chunk.c +++ b/src/chunk.c @@ -19,6 +19,7 @@ #include #include "include/rdkv_cdl.h" +#include "include/rdkv_upgrade.h" #include "rdkv_cdl_log_wrapper.h" #ifndef GTEST_ENABLE #include "downloadUtil.h" @@ -129,11 +130,11 @@ int chunkDownload(FileDwnl_t *pfile_dwnl, MtlsAuth_t *sec, unsigned int speed_li if (curl != NULL) { doStopDownload(curl); } - /*During Download Stop and exit the app. This feature for Throttling + /*During Download Stop and return error to caller. This feature for Throttling * when throttle speed limit set to 0*/ if (force_exit == 1 && (curl_ret_code == 23)) { - uninitialize(INITIAL_VALIDATION_SUCCESS); - exit(1); + SWLOG_INFO("chunkDownload() Force exit requested (curl error 23)\n"); + return RDKV_UPGRADE_ERROR_FORCE_EXIT; } SWLOG_INFO("chunkDownload() curl ret status=%u\n", curl_ret_code); if (curl_ret_code == 33 || curl_ret_code == 36) { @@ -155,11 +156,11 @@ int chunkDownload(FileDwnl_t *pfile_dwnl, MtlsAuth_t *sec, unsigned int speed_li if (curl != NULL) { doStopDownload(curl); } - /*During Download Stop and exit the app. This feature for Throttling + /*During Download Stop and return error to caller. This feature for Throttling * when throttle speed limit set to 0*/ if (force_exit == 1 && (curl_ret_code == 23)) { - uninitialize(INITIAL_VALIDATION_SUCCESS); - exit(1); + SWLOG_INFO("chunkDownload() Force exit after retry (curl error 23)\n"); + return RDKV_UPGRADE_ERROR_FORCE_EXIT; } } } else if ((curl_ret_code == 0) && ((filePresentCheck(pfile_dwnl->pathname)) == 0)) { @@ -188,8 +189,8 @@ int chunkDownload(FileDwnl_t *pfile_dwnl, MtlsAuth_t *sec, unsigned int speed_li doStopDownload(curl); } if (force_exit == 1 && (curl_ret_code == 23)) { - uninitialize(INITIAL_VALIDATION_SUCCESS); - exit(1); + SWLOG_INFO("chunkDownload() Force exit after completion check (curl error 23)\n"); + return RDKV_UPGRADE_ERROR_FORCE_EXIT; } } } else { diff --git a/src/dbus/rdkFwupdateMgr_handlers.c b/src/dbus/rdkFwupdateMgr_handlers.c index 13df3d64..68f87f70 100644 --- a/src/dbus/rdkFwupdateMgr_handlers.c +++ b/src/dbus/rdkFwupdateMgr_handlers.c @@ -63,6 +63,57 @@ #define XCONF_PROGRESS_FILE "/tmp/xconf_curl_progress_thunder" #define RED_STATE_FILE "/lib/rdk/stateRedRecovery.sh" +// ============================================================================ +// GLOBAL IN-MEMORY XCONF CACHE +// ============================================================================ +/** + * @brief Global parsed XConf response cache + * + * This structure holds the most recent successfully parsed XConf response + * in memory to avoid repeated file I/O and JSON parsing operations. + * + * Benefits: + * - Fast access to firmware metadata without file I/O + * - No repeated JSON parsing overhead + * - Direct access to download URLs for DownloadFirmware API + * - Thread-safe via g_xconf_data_cache mutex + * + * Lifecycle: + * - Populated by save_xconf_to_cache() after successful XConf query + * - Read by get_cached_xconf_data() with automatic deep copy + * - Cleared by clear_cached_xconf_data() on errors or invalidation + * - Protected by g_xconf_data_cache mutex for thread safety + * + * Memory: + * - Struct itself is statically allocated for the lifetime of the process + * - String data is stored in fixed-size char arrays inside XCONFRES + * (no g_strdup/g_free; data is copied via memcpy/strncpy into the struct) + * - clear_cached_xconf_data() resets/invalidates the struct; no heap frees + */ +static XCONFRES g_cached_xconf_data = {0}; +static gboolean g_xconf_data_valid = FALSE; +static int g_cached_http_code = 0; + +/** + * @brief Mutex protecting global XConf data cache + * + * Protects concurrent access to: + * - g_cached_xconf_data (parsed XConf response structure) + * - g_xconf_data_valid (cache validity flag) + * - g_cached_http_code (HTTP status code) + * + * Lock Scope: + * - MUST lock before: save_cached_xconf_data(), get_cached_xconf_data(), clear_cached_xconf_data() + * - Release immediately after data copy completes + * - Do NOT hold during network calls or file I/O + * + * Thread Safety: + * - Protects both read and write operations + * - Ensures atomicity of cache updates + * - Prevents partial reads during writes + */ +G_LOCK_DEFINE_STATIC(xconf_data_cache); + // ============================================================================ // CACHE SYNCHRONIZATION // ============================================================================ @@ -187,12 +238,21 @@ typedef struct { // Shared device and image information (populated at daemon startup) extern DeviceProperty_t device_info; + +// Forward declaration for getOPTOUTValue function from rdkFwupdateMgr.c +extern int getOPTOUTValue(const char *file_name); extern ImageDetails_t cur_img_detail; extern Rfc_t rfc_list; // Trigger type constant for manual/D-Bus initiated downloads #define TRIGGER_MANUAL 1 +// ============================================================================ +// FORWARD DECLARATIONS (Internal Functions) +// ============================================================================ +static void clear_cached_xconf_data_internal(void); +static gboolean save_cached_xconf_data(const XCONFRES *pResponse, int http_code); + /** * @brief Check if XConf response cache exists * @return TRUE if cache file exists, FALSE otherwise @@ -294,9 +354,9 @@ static gboolean save_xconf_to_cache(const char *xconf_response, int http_code) return FALSE; } - SWLOG_INFO("[CACHE] Saving XConf response to cache files\n"); + SWLOG_INFO("[CACHE] Saving XConf response to cache files and memory\n"); - // === CRITICAL SECTION START === + // === CRITICAL SECTION START (File Cache) === G_LOCK(xconf_cache); // Save main XConf response (with lock held) @@ -318,14 +378,34 @@ static gboolean save_xconf_to_cache(const char *xconf_response, int http_code) } G_UNLOCK(xconf_cache); - // === CRITICAL SECTION END === + // === CRITICAL SECTION END (File Cache) === - SWLOG_INFO("[CACHE] XConf data cached successfully\n"); + SWLOG_INFO("[CACHE] XConf data cached to files successfully\n"); SWLOG_INFO("[CACHE] - Response file: %s\n", XCONF_CACHE_FILE); SWLOG_INFO("[CACHE] - HTTP code file: %s (code: %d)\n", XCONF_HTTP_CODE_FILE, http_code); g_free(http_code_str); + + // Parse JSON response and save to global in-memory cache + XCONFRES parsed_response = {0}; + int parse_result = getXconfRespData(&parsed_response, (char *)xconf_response); + + if (parse_result == 0) { + SWLOG_INFO("[CACHE] Parsed XConf response successfully, saving to memory cache\n"); + + // Save parsed data to global in-memory cache + if (save_cached_xconf_data(&parsed_response, http_code)) { + SWLOG_INFO("[CACHE] In-memory cache updated successfully\n"); + } else { + SWLOG_ERROR("[CACHE] Failed to update in-memory cache (non-fatal)\n"); + } + } else { + SWLOG_ERROR("[CACHE] Failed to parse XConf response for memory cache (error: %d)\n", parse_result); + SWLOG_ERROR("[CACHE] File cache saved but in-memory cache not updated\n"); + // Non-fatal: file cache is still valid + } + return TRUE; } @@ -406,11 +486,6 @@ static int fetch_xconf_firmware_info( XCONFRES *pResponse, int server_type, int xconf_context.trigger_type = local_trigger_type; xconf_context.rfc_list = &local_rfc_list; - //#ifndef GTEST_ENABLE - //SWLOG_INFO("Simulating a 120 seconds sleep()\n"); - //sleep(120); - //SWLOG_INFO("Just now completed 120 seconds sleep\n"); - //#endif SWLOG_INFO("fetch_xconf_firmware_info: Initiating XConf request with server_type=%d\n", server_type); SWLOG_INFO("fetch_xconf_firmware_info: Context setup - device_info=%p, rfc_list=%p\n", xconf_context.device_info, xconf_context.rfc_list); @@ -420,6 +495,13 @@ static int fetch_xconf_firmware_info( XCONFRES *pResponse, int server_type, int ret = rdkv_upgrade_request(&xconf_context, &curl, pHttp_code); SWLOG_INFO("fetch_xconf_firmware_info: rdkv_upgrade_request returned (ret=%d)\n", ret); + // Handle library-specific errors (negative values) - Daemon NEVER exits + if (ret < 0) { + SWLOG_ERROR("fetch_xconf_firmware_info: Library error: %s (code: %d)\n", + rdkv_upgrade_strerror(ret), ret); + // Daemon continues - ret is already < 0, will be handled by existing error logic below + } + SWLOG_INFO("fetch_xconf_firmware_info: XConf request completed - ret=%d, http_code=%d\n", ret, *pHttp_code); if( ret == 0 && *pHttp_code == 200 && DwnLoc.pvOut != NULL ) @@ -654,6 +736,55 @@ static CheckUpdateResponse create_result_response(CheckForUpdateStatus status_co return response; } +/** + * @brief Create a CheckUpdateResponse for opt-out scenarios with firmware metadata. + * + * Similar to create_success_response, but specifically for IGNORE_OPTOUT and BYPASS_OPTOUT + * status codes. Always includes full firmware metadata so clients can display available + * update information even when updates are blocked or require consent. + * + * @param status_code Status code (IGNORE_OPTOUT or BYPASS_OPTOUT) + * @param available_version Firmware version from XConf server + * @param update_details Pipe-delimited firmware metadata string + * @param status_message Custom status message explaining opt-out state + * @return CheckUpdateResponse structure with allocated strings (must be freed by caller) + */ +#ifdef GTEST_ENABLE +CheckUpdateResponse create_optout_response(CheckForUpdateStatus status_code, + const gchar *available_version, + const gchar *update_details, + const gchar *status_message) +#else +static CheckUpdateResponse create_optout_response(CheckForUpdateStatus status_code, + const gchar *available_version, + const gchar *update_details, + const gchar *status_message) +#endif +{ + CheckUpdateResponse response = {0}; + char current_img_buffer[256] = {0}; + + bool img_status = GetFirmwareVersion(current_img_buffer, sizeof(current_img_buffer)); + + SWLOG_INFO("[rdkFwupdateMgr] create_optout_response: Creating response for status_code=%d\n", status_code); + SWLOG_INFO("[rdkFwupdateMgr] - currentImg status: %s\n", img_status ? "SUCCESS" : "FAILED"); + SWLOG_INFO("[rdkFwupdateMgr] - current_img_buffer: '%s'\n", current_img_buffer); + + response.result = CHECK_FOR_UPDATE_SUCCESS; // API call succeeded + response.status_code = status_code; // IGNORE_OPTOUT or BYPASS_OPTOUT + response.current_img_version = g_strdup(img_status ? current_img_buffer : "Unknown"); + response.available_version = g_strdup(available_version ? available_version : ""); + response.update_details = g_strdup(update_details ? update_details : ""); + response.status_message = g_strdup(status_message ? status_message : ""); + + SWLOG_INFO("[rdkFwupdateMgr] create_optout_response: Response created with:\n"); + SWLOG_INFO("[rdkFwupdateMgr] - current: '%s'\n", response.current_img_version); + SWLOG_INFO("[rdkFwupdateMgr] - available: '%s'\n", response.available_version); + SWLOG_INFO("[rdkFwupdateMgr] - status_message: '%s'\n", response.status_message); + + return response; +} + // *** NEW: Progress signal emission (main thread callback) *** /** @@ -1084,21 +1215,7 @@ CheckUpdateResponse rdkFwupdateMgr_checkForUpdate(const gchar *handler_id) { int server_type = HTTP_XCONF_DIRECT; int ret = -1; - SWLOG_INFO("[rdkFwupdateMgr] CheckForUpdate: Checking for cached XConf data...\n"); - - // Try cache first to support offline recovery scenarios - if (xconf_cache_exists()) { - SWLOG_INFO("[rdkFwupdateMgr] Cache hit! Loading XConf data from cache\n"); - if (load_xconf_from_cache(&response)) { - ret = 0; - http_code = 200; - SWLOG_INFO("[rdkFwupdateMgr] Successfully loaded XConf data from cache\n"); - } else { - SWLOG_ERROR("[rdkFwupdateMgr] Cache read failed, falling back to live XConf call\n"); - ret = fetch_xconf_firmware_info(&response, server_type, &http_code); - } - } else { - SWLOG_INFO("[rdkFwupdateMgr] Cache miss! Making live XConf call\n"); + SWLOG_INFO("[rdkFwupdateMgr] Making live XConf call\n"); ret = fetch_xconf_firmware_info(&response, server_type, &http_code); if (ret == 0 && http_code == 200) { @@ -1120,7 +1237,6 @@ CheckUpdateResponse rdkFwupdateMgr_checkForUpdate(const gchar *handler_id) { SWLOG_INFO("[rdkFwupdateMgr] VALIDATION PASSED - Firmware is valid for this device\n"); SWLOG_INFO("[rdkFwupdateMgr] ===== VALIDATION & COMPARISON COMPLETE =====\n"); } - } SWLOG_INFO("[rdkFwupdateMgr] XConf call completed with result: ret=%d\n",ret); @@ -1154,6 +1270,14 @@ CheckUpdateResponse rdkFwupdateMgr_checkForUpdate(const gchar *handler_id) { response.cloudPDRIVersion[0] ? response.cloudPDRIVersion : "(empty)"); SWLOG_INFO("=== [rdkFwupdateMgr] XConf Response - End ===\n"); + // Check if firmware version is present + if (!response.cloudFWVersion[0] || strlen(response.cloudFWVersion) == 0) { + SWLOG_INFO("[rdkFwupdateMgr] XConf returned no firmware version - no update available\n"); + return create_result_response(FIRMWARE_NOT_AVAILABLE, "No firmware update available"); + } + + SWLOG_INFO("[rdkFwupdateMgr] XConf returned firmware version: '%s'\n", response.cloudFWVersion); + // Serialize XConf metadata into pipe-delimited string for D-Bus transport gchar *update_details = g_strdup_printf( "File:%s|Location:%s|IPv6Location:%s|Version:%s|Protocol:%s|Reboot:%s|Delay:%s|PDRI:%s|Peripherals:%s|CertBundle:%s", @@ -1169,440 +1293,129 @@ CheckUpdateResponse rdkFwupdateMgr_checkForUpdate(const gchar *handler_id) { response.dlCertBundle[0] ? response.dlCertBundle : "N/A" ); - // Determine result based on presence of firmware version - if (response.cloudFWVersion[0] && strlen(response.cloudFWVersion) > 0) { - SWLOG_INFO("[rdkFwupdateMgr] XConf returned firmware version: '%s'\n", response.cloudFWVersion); - + // ===== POST-XCONF OPT-OUT EVALUATION ===== + SWLOG_INFO("[rdkFwupdateMgr] ===== BEGIN POST-XCONF OPT-OUT EVALUATION =====\n"); + + // Parse critical update flag from XConf response + bool isCriticalUpdate = false; + if (strncmp(response.cloudImmediateRebootFlag, "true", 4) == 0) { + isCriticalUpdate = true; + SWLOG_INFO("[rdkFwupdateMgr] CRITICAL UPDATE DETECTED (cloudImmediateRebootFlag=true)\n"); + } else { + SWLOG_INFO("[rdkFwupdateMgr] Non-critical update (cloudImmediateRebootFlag=%s)\n", + response.cloudImmediateRebootFlag[0] ? response.cloudImmediateRebootFlag : "false"); + } + + // Check 1: Is Maintenance Manager integration active? + SWLOG_INFO("[rdkFwupdateMgr] Checking maint_status: '%s'\n", device_info.maint_status); + if (strncmp(device_info.maint_status, "true", 4) != 0) { + SWLOG_INFO("[rdkFwupdateMgr] MaintenanceMGR not active (maint_status != 'true') - skipping opt-out logic\n"); + SWLOG_INFO("[rdkFwupdateMgr] ===== END OPT-OUT EVALUATION: NORMAL FLOW =====\n"); CheckUpdateResponse result = create_success_response( response.cloudFWVersion, update_details, "Firmware update available" ); - g_free(update_details); return result; - } else { - SWLOG_INFO("[rdkFwupdateMgr] XConf returned no firmware version - no update available\n"); - g_free(update_details); - return create_result_response(FIRMWARE_NOT_AVAILABLE, "No firmware update available"); } - } else { - // XConf query failed - network or server error - SWLOG_ERROR("[rdkFwupdateMgr] XConf communication failed: ret=%d, http=%d\n", ret, http_code); - if (http_code != 200) { - return create_result_response(FIRMWARE_CHECK_ERROR, "Network error - unable to reach update server"); - } else { - return create_result_response(FIRMWARE_CHECK_ERROR, "Update check failed - server communication error"); - } - } -} - -/** - * @brief Download firmware with progress monitoring - * - * Main entry point for firmware download operation. Features: - * - URL source: Custom URL or XConf cache - * - Progress monitoring: Spawns thread if download_state provided - * - Error handling: Comprehensive curl/HTTP error mapping - * - Memory safety: All allocations checked and cleaned up - * - * Thread Safety: - * - Spawns progress monitor thread if needed - * - Properly joins thread before returning - * - All shared state protected by mutex - * - * Memory Management: - * - All g_strdup'd strings must be freed by caller - * - Thread context freed by thread itself - * - Mutex and context freed by thread on exit - * - * @param firmwareName Firmware filename (for logging, can be NULL) - * @param downloadUrl Custom URL or empty string to use XConf URL - * @param typeOfFirmware Type: "PCI", "PDRI", "PERIPHERAL" (can be NULL) - * @param localFilePath Destination path (required, must not be NULL) - * @param download_state D-Bus skeleton for progress signals (NULL = no progress) - * @return DownloadFirmwareResult with result_code and error_message - */ -DownloadFirmwareResult rdkFwupdateMgr_downloadFirmware(const gchar *firmwareName, - const gchar *downloadUrl, - const gchar *typeOfFirmware, - const gchar *localFilePath, - void *download_state) { - SWLOG_INFO("[DOWNLOAD_HANDLER] === Starting Firmware Download ===\n"); - SWLOG_INFO("[DOWNLOAD_HANDLER] Firmware: %s\n", firmwareName ? firmwareName : "(null)"); - SWLOG_INFO("[DOWNLOAD_HANDLER] Custom URL: '%s'\n", downloadUrl ? downloadUrl : "(empty)"); - SWLOG_INFO("[DOWNLOAD_HANDLER] Type: %s\n", typeOfFirmware ? typeOfFirmware : "(null)"); - SWLOG_INFO("[DOWNLOAD_HANDLER] Destination: %s\n", localFilePath ? localFilePath : "(null)"); - SWLOG_INFO("[DOWNLOAD_HANDLER] Progress monitoring: %s\n", download_state ? "ENABLED" : "DISABLED"); - - // Initialize result structure - DownloadFirmwareResult result; - result.result_code = DOWNLOAD_ERROR; - result.error_message = NULL; - - // Validate required parameters - if (localFilePath == NULL || strlen(localFilePath) == 0) { - SWLOG_ERROR("[DOWNLOAD_HANDLER] ERROR: localFilePath is NULL or empty\n"); - result.error_message = g_strdup("Invalid parameters: localFilePath required"); - return result; - } - - // Determine effective download URL - gchar *effective_url = NULL; - - if (downloadUrl != NULL && strlen(downloadUrl) > 0) { - // Use custom URL provided by caller - SWLOG_INFO("[DOWNLOAD_HANDLER] Using custom URL: %s\n", downloadUrl); - effective_url = g_strdup(downloadUrl); - - if (effective_url == NULL) { - SWLOG_ERROR("[DOWNLOAD_HANDLER] ERROR: Failed to duplicate URL string\n"); - result.error_message = g_strdup("Memory allocation failed"); - return result; - } - } else { - // Load URL from XConf cache - SWLOG_INFO("[DOWNLOAD_HANDLER] No custom URL, loading from XConf cache\n"); - - if (!xconf_cache_exists()) { - SWLOG_ERROR("[DOWNLOAD_HANDLER] ERROR: No XConf cache found\n"); - SWLOG_ERROR("[DOWNLOAD_HANDLER] Client must call CheckForUpdate first\n"); - result.error_message = g_strdup("No firmware metadata. Call CheckForUpdate first."); + // Check 2: Is opt-out feature enabled for this device? + SWLOG_INFO("[rdkFwupdateMgr] Checking sw_optout: '%s'\n", device_info.sw_optout); + if (strncmp(device_info.sw_optout, "true", 4) != 0) { + SWLOG_INFO("[rdkFwupdateMgr] Opt-out feature disabled (sw_optout != 'true') - skipping opt-out logic\n"); + SWLOG_INFO("[rdkFwupdateMgr] ===== END OPT-OUT EVALUATION: NORMAL FLOW =====\n"); + CheckUpdateResponse result = create_success_response( + response.cloudFWVersion, + update_details, + "Firmware update available" + ); + g_free(update_details); return result; } - XCONFRES xconf_response; - memset(&xconf_response, 0, sizeof(XCONFRES)); + // Check 3: Read user's opt-out preference + SWLOG_INFO("[rdkFwupdateMgr] Reading opt-out preference from /opt/maintenance_mgr_record.conf\n"); + int optout = getOPTOUTValue("/opt/maintenance_mgr_record.conf"); + SWLOG_INFO("[rdkFwupdateMgr] Opt-out value: %d (-1=not set, 0=ENFORCE_OPTOUT, 1=IGNORE_UPDATE)\n", optout); - if (!load_xconf_from_cache(&xconf_response)) { - SWLOG_ERROR("[DOWNLOAD_HANDLER] ERROR: Failed to load XConf cache\n"); - result.error_message = g_strdup("Failed to load firmware metadata from cache"); + if (optout == -1) { + SWLOG_INFO("[rdkFwupdateMgr] No opt-out preference set (file missing or no value) - allowing update\n"); + SWLOG_INFO("[rdkFwupdateMgr] ===== END OPT-OUT EVALUATION: NORMAL FLOW =====\n"); + CheckUpdateResponse result = create_success_response( + response.cloudFWVersion, + update_details, + "Firmware update available" + ); + g_free(update_details); return result; } - SWLOG_INFO("[DOWNLOAD_HANDLER] Loaded XConf metadata:\n"); - SWLOG_INFO("[DOWNLOAD_HANDLER] Version: %s\n", - xconf_response.cloudFWVersion ? xconf_response.cloudFWVersion : "(null)"); - SWLOG_INFO("[DOWNLOAD_HANDLER] URL: %s\n", - xconf_response.cloudFWFile ? xconf_response.cloudFWFile : "(null)"); - - // Validate that cloudFWFile contains a valid URL - if (xconf_response.cloudFWFile == NULL || strlen(xconf_response.cloudFWFile) == 0) { - SWLOG_ERROR("[DOWNLOAD_HANDLER] ERROR: XConf cache has no firmware URL\n"); - result.error_message = g_strdup("Invalid XConf data: missing firmware URL"); - return result; + // Check 4: Apply opt-out decision logic + if (optout == 1) { + // User has opted out (IGNORE_UPDATE) + if (isCriticalUpdate) { + // Critical update bypasses opt-out + SWLOG_INFO("[rdkFwupdateMgr] CRITICAL UPDATE OVERRIDE: Bypassing user opt-out\n"); + SWLOG_INFO("[rdkFwupdateMgr] ===== END OPT-OUT EVALUATION: CRITICAL BYPASS =====\n"); + CheckUpdateResponse result = create_success_response( + response.cloudFWVersion, + update_details, + "Critical firmware update available (security/stability)" + ); + g_free(update_details); + return result; + } else { + // Non-critical update blocked by user + SWLOG_INFO("[rdkFwupdateMgr] BLOCKING UPDATE: User opted out (IGNORE_UPDATE), non-critical firmware\n"); + SWLOG_INFO("[rdkFwupdateMgr] ===== END OPT-OUT EVALUATION: RETURNING IGNORE_OPTOUT =====\n"); + CheckUpdateResponse result = create_optout_response( + IGNORE_OPTOUT, + response.cloudFWVersion, + update_details, + "Firmware download blocked - user has opted out of updates" + ); + g_free(update_details); + return result; + } } - - effective_url = g_strdup(xconf_response.cloudFWFile); - - if (effective_url == NULL) { - SWLOG_ERROR("[DOWNLOAD_HANDLER] ERROR: Failed to duplicate XConf URL\n"); - result.error_message = g_strdup("Memory allocation failed"); + else if (optout == 0) { + // User requires consent (ENFORCE_OPTOUT) + SWLOG_INFO("[rdkFwupdateMgr] CONSENT REQUIRED: User has ENFORCE_OPTOUT set\n"); + SWLOG_INFO("[rdkFwupdateMgr] ===== END OPT-OUT EVALUATION: RETURNING BYPASS_OPTOUT =====\n"); + CheckUpdateResponse result = create_optout_response( + BYPASS_OPTOUT, + response.cloudFWVersion, + update_details, + "Firmware available - user consent required before installation" + ); + g_free(update_details); return result; } - } - - // Validate effective URL - if (strlen(effective_url) == 0) { - SWLOG_ERROR("[DOWNLOAD_HANDLER] ERROR: No download URL available\n"); - result.error_message = g_strdup("No download URL available"); - g_free(effective_url); + + // Defensive: Should not reach here, but return normal flow + SWLOG_WARN("[rdkFwupdateMgr] WARNING: Unexpected opt-out value path - returning normal flow\n"); + SWLOG_INFO("[rdkFwupdateMgr] ===== END OPT-OUT EVALUATION: FALLBACK NORMAL FLOW =====\n"); + CheckUpdateResponse result = create_success_response( + response.cloudFWVersion, + update_details, + "Firmware update available" + ); + g_free(update_details); return result; - } - - SWLOG_INFO("[DOWNLOAD_HANDLER] Effective download URL: %s\n", effective_url); - - // Prepare upgrade context - RdkUpgradeContext_t upgrade_context; - memset(&upgrade_context, 0, sizeof(RdkUpgradeContext_t)); - - // Determine upgrade type from firmware type parameter - if (typeOfFirmware != NULL) { - if (strcmp(typeOfFirmware, "PCI") == 0) { - upgrade_context.upgrade_type = PCI_UPGRADE; - } else if (strcmp(typeOfFirmware, "PDRI") == 0) { - upgrade_context.upgrade_type = PDRI_UPGRADE; - } else if (strcmp(typeOfFirmware, "PERIPHERAL") == 0) { - upgrade_context.upgrade_type = PERIPHERAL_UPGRADE; - } else { - SWLOG_ERROR("[DOWNLOAD_HANDLER] Unknown firmware type '%s', using PCI\n", typeOfFirmware); - upgrade_context.upgrade_type = PCI_UPGRADE; - } } else { - upgrade_context.upgrade_type = PCI_UPGRADE; - } - - SWLOG_INFO("[DOWNLOAD_HANDLER] Upgrade type: %d\n", upgrade_context.upgrade_type); - - // CRITICAL: Set download_only flag (do NOT flash automatically) - upgrade_context.download_only = TRUE; - SWLOG_INFO("[DOWNLOAD_HANDLER] download_only=TRUE (will NOT auto-flash)\n"); - - // Set context fields - upgrade_context.server_type = HTTP_SSR_DIRECT; - upgrade_context.artifactLocationUrl = effective_url; - upgrade_context.dwlloc = (const void*)localFilePath; - upgrade_context.pPostFields = NULL; - upgrade_context.immed_reboot_flag = "NO"; - upgrade_context.delay_dwnl = 0; - - // Generate timestamp for lastrun - char timestamp[64]; - snprintf(timestamp, sizeof(timestamp), "%ld", (long)time(NULL)); - upgrade_context.lastrun = timestamp; - upgrade_context.disableStatsUpdate = (char*)"false"; - upgrade_context.device_info = &device_info; - - int force_exit = 0; - upgrade_context.force_exit = &force_exit; - upgrade_context.trigger_type = TRIGGER_MANUAL; - upgrade_context.rfc_list = &rfc_list; - - - // *** NEW: Spawn progress monitor thread if download_state provided *** - GThread* monitor_thread = NULL; - gint stop_monitor = 0; // Changed from gboolean to gint for g_atomic_int_get/set type safety - GMutex* monitor_mutex = NULL; - ProgressMonitorContext* monitor_ctx = NULL; - - if (download_state != NULL) { - SWLOG_INFO("[DOWNLOAD_HANDLER] Setting up progress monitoring...\n"); - - // Cast download_state to the proper type - DownloadStateContext* dl_ctx = (DownloadStateContext*)download_state; - - // NULL CHECK: Validate download state context fields - if (dl_ctx->connection == NULL) { - SWLOG_ERROR("[DOWNLOAD_HANDLER] ERROR: NULL D-Bus connection in download_state\n"); - result.result_code = DOWNLOAD_ERROR; - result.error_message = g_strdup("Invalid download state (NULL connection)"); - g_free(effective_url); - return result; - } - - // Allocate and initialize mutex for thread-safe access - monitor_mutex = g_new0(GMutex, 1); - if (monitor_mutex == NULL) { - SWLOG_ERROR("[DOWNLOAD_HANDLER] ERROR: Failed to allocate monitor mutex\n"); - result.result_code = DOWNLOAD_ERROR; - result.error_message = g_strdup("Memory allocation failed"); - g_free(effective_url); - return result; - } - g_mutex_init(monitor_mutex); - SWLOG_DEBUG("[DOWNLOAD_HANDLER] Monitor mutex allocated and initialized\n"); - - // Allocate progress monitor context - monitor_ctx = g_new0(ProgressMonitorContext, 1); - if (monitor_ctx == NULL) { - SWLOG_ERROR("[DOWNLOAD_HANDLER] ERROR: Failed to allocate monitor context\n"); - g_mutex_clear(monitor_mutex); - g_free(monitor_mutex); - result.result_code = DOWNLOAD_ERROR; - result.error_message = g_strdup("Memory allocation failed"); - g_free(effective_url); - return result; - } - SWLOG_DEBUG("[DOWNLOAD_HANDLER] Monitor context allocated\n"); - - // Initialize context fields - monitor_ctx->connection = dl_ctx->connection; // Borrowed pointer (do NOT free) - monitor_ctx->handler_id = dl_ctx->handler_id ? g_strdup(dl_ctx->handler_id) : NULL; - monitor_ctx->firmware_name = dl_ctx->firmware_name ? g_strdup(dl_ctx->firmware_name) : NULL; - monitor_ctx->stop_flag = &stop_monitor; - monitor_ctx->mutex = monitor_mutex; - monitor_ctx->last_dlnow = 0; - monitor_ctx->last_activity_time = time(NULL); - - SWLOG_DEBUG("[DOWNLOAD_HANDLER] Monitor context initialized:\n"); - SWLOG_DEBUG("[DOWNLOAD_HANDLER] - Handler ID: %s\n", monitor_ctx->handler_id ? monitor_ctx->handler_id : "(null)"); - SWLOG_DEBUG("[DOWNLOAD_HANDLER] - Firmware: %s\n", monitor_ctx->firmware_name ? monitor_ctx->firmware_name : "(null)"); - - // Spawn monitor thread - GError* thread_error = NULL; - monitor_thread = g_thread_try_new("rdkfw_progress_monitor", - rdkfw_progress_monitor_thread, - monitor_ctx, - &thread_error); + // XConf query failed - network or server error + SWLOG_ERROR("[rdkFwupdateMgr] XConf communication failed: ret=%d, http=%d\n", ret, http_code); - if (monitor_thread == NULL) { - SWLOG_ERROR("[DOWNLOAD_HANDLER] ERROR: Failed to spawn monitor thread: %s\n", - thread_error ? thread_error->message : "Unknown error"); - SWLOG_ERROR("Thread creation failed"); - if (monitor_ctx) { - g_free(monitor_ctx->handler_id); - g_free(monitor_ctx->firmware_name); - if (monitor_ctx->mutex) { - g_mutex_clear(monitor_ctx->mutex); - g_free(monitor_ctx->mutex); - } - g_free(monitor_ctx); - monitor_ctx = NULL; - } - // Cleanup on thread creation failure - if (thread_error != NULL) { - g_error_free(thread_error); - thread_error = NULL; - } - - // Free string fields (g_strdup'd copies) - if (monitor_ctx->handler_id) { - g_free(monitor_ctx->handler_id); - monitor_ctx->handler_id = NULL; - } - if (monitor_ctx->firmware_name) { - g_free(monitor_ctx->firmware_name); - monitor_ctx->firmware_name = NULL; - } - - // Clear and free mutex - g_mutex_clear(monitor_mutex); - g_free(monitor_mutex); - monitor_mutex = NULL; - - // Free context - g_free(monitor_ctx); - monitor_ctx = NULL; - - // Continue without progress monitoring (non-fatal) - SWLOG_INFO("[DOWNLOAD_HANDLER] Continuing without progress monitoring\n"); + if (http_code != 200) { + return create_result_response(FIRMWARE_CHECK_ERROR, "Network error - unable to reach update server"); } else { - SWLOG_INFO("[DOWNLOAD_HANDLER] Progress monitor thread started successfully\n"); - } - } else { - SWLOG_INFO("[DOWNLOAD_HANDLER] No progress monitoring requested (download_state=NULL)\n"); - } - - // Call rdkv_upgrade_request() (blocks until download completes or fails) - SWLOG_INFO("[DOWNLOAD_HANDLER] Calling rdkv_upgrade_request()...\n"); - - void *curl_handle = NULL; - int http_code = 0; - int curl_ret_code = rdkv_upgrade_request(&upgrade_context, &curl_handle, &http_code); - - SWLOG_INFO("[DOWNLOAD_HANDLER] rdkv_upgrade_request() returned: curl=%d, http=%d\n", - curl_ret_code, http_code); - - // Stop progress monitor thread *** - if (monitor_thread != NULL) { - SWLOG_INFO("[DOWNLOAD_HANDLER] Stopping progress monitor thread...\n"); - - // Signal thread to stop atomically (use 1 for true with gint type) - g_atomic_int_set(&stop_monitor, 1); - - /* Coverity fix: RESOURCE_LEAK - g_thread_join() frees the thread handle. - * Do NOT set monitor_thread = NULL afterward as Coverity flags it as a leak. - * The thread handle is properly freed by g_thread_join() and should not be - * used again after this point. */ - g_thread_join(monitor_thread); - /* coverity[leaked_storage] - False positive: g_thread_join() already freed the GThread. - * Setting to NULL is defensive programming to prevent double-join. GLib documentation - * confirms the thread handle is consumed by g_thread_join(). */ - monitor_thread = NULL; - - SWLOG_INFO("[DOWNLOAD_HANDLER] Progress monitor thread stopped cleanly\n"); - - // Note: monitor_mutex and monitor_ctx are cleaned up by the thread itself - // Do NOT free them here to avoid double-free - } else if (monitor_ctx != NULL) { - /* Coverity fix: RESOURCE_LEAK - If monitor_thread is NULL but monitor_ctx was allocated - * and thread creation failed, we need to clean it up here. */ - SWLOG_DEBUG("[DOWNLOAD_HANDLER] Cleaning up monitor_ctx (thread was not started)\n"); - if (monitor_ctx->handler_id) g_free(monitor_ctx->handler_id); - if (monitor_ctx->firmware_name) g_free(monitor_ctx->firmware_name); - if (monitor_ctx->mutex) { - g_mutex_clear(monitor_ctx->mutex); - g_free(monitor_ctx->mutex); - } - g_free(monitor_ctx); - monitor_ctx = NULL; - } - - // Analyze download result - if (curl_ret_code == 0 && (http_code == 200 || http_code == 206)) { - // Success: curl completed and HTTP OK/Partial Content - SWLOG_INFO("[DOWNLOAD_HANDLER] Download completed successfully!\n"); - - // Verify file exists on disk - if (!g_file_test(localFilePath, G_FILE_TEST_EXISTS)) { - SWLOG_ERROR("[DOWNLOAD_HANDLER] ERROR: File not found after download: %s\n", localFilePath); - result.result_code = DOWNLOAD_ERROR; - result.error_message = g_strdup("File not found after download"); - g_free(effective_url); - return result; - } - - // Get file size for logging - struct stat st; - if (stat(localFilePath, &st) == 0) { - SWLOG_INFO("[DOWNLOAD_HANDLER] Downloaded file size: %ld bytes\n", (long)st.st_size); + return create_result_response(FIRMWARE_CHECK_ERROR, "Update check failed - server communication error"); } - - result.result_code = DOWNLOAD_SUCCESS; - result.error_message = NULL; - - } else if (curl_ret_code == 0 && http_code == 404) { - // HTTP 404: Not Found - SWLOG_ERROR("[DOWNLOAD_HANDLER] Firmware not found (HTTP 404)\n"); - result.result_code = DOWNLOAD_NOT_FOUND; - result.error_message = g_strdup("Firmware not found on server (HTTP 404)"); - - } else if (curl_ret_code == 6) { - // CURLE_COULDNT_RESOLVE_HOST - SWLOG_ERROR("[DOWNLOAD_HANDLER] DNS resolution failed (curl error 6)\n"); - result.result_code = DOWNLOAD_NETWORK_ERROR; - result.error_message = g_strdup("DNS resolution failed"); - - } else if (curl_ret_code == 7) { - // CURLE_COULDNT_CONNECT - SWLOG_ERROR("[DOWNLOAD_HANDLER] Connection failed (curl error 7)\n"); - result.result_code = DOWNLOAD_NETWORK_ERROR; - result.error_message = g_strdup("Connection failed"); - - } else if (curl_ret_code == 28) { - // CURLE_OPERATION_TIMEDOUT - SWLOG_ERROR("[DOWNLOAD_HANDLER] Timeout (curl error 28)\n"); - result.result_code = DOWNLOAD_NETWORK_ERROR; - result.error_message = g_strdup("Operation timed out"); - - } else if (curl_ret_code == 18) { - // CURLE_PARTIAL_FILE - SWLOG_ERROR("[DOWNLOAD_HANDLER] Partial file transfer (curl error 18)\n"); - result.result_code = DOWNLOAD_ERROR; - result.error_message = g_strdup("Partial file transfer (incomplete download)"); - - } else if (curl_ret_code == 23) { - // CURLE_WRITE_ERROR - SWLOG_ERROR("[DOWNLOAD_HANDLER] Write error (curl error 23) - disk full?\n"); - result.result_code = DOWNLOAD_ERROR; - result.error_message = g_strdup("Write error (disk full or permission denied)"); - - } else { - // Generic error - SWLOG_ERROR("[DOWNLOAD_HANDLER] Download failed (curl=%d, HTTP=%d)\n", - curl_ret_code, http_code); - - char error_msg[256]; - snprintf(error_msg, sizeof(error_msg), - "Download failed (curl error %d, HTTP status %d)", - curl_ret_code, http_code); - result.error_message = g_strdup(error_msg); } - - // Cleanup - g_free(effective_url); - effective_url = NULL; - - SWLOG_INFO("[DOWNLOAD_HANDLER] === Download Handler Complete (result=%d) ===\n", - result.result_code); - - /* coverity[leaked_storage] - False positive: monitor_thread was already cleaned up - * at line 1303 via g_thread_join(). All paths reaching this return have already - * stopped and freed the monitor thread. */ - return result; } + /* * =================================================================== * UpdateFirmware Worker Thread Implementation @@ -2141,3 +1954,207 @@ gpointer rdkfw_flash_worker_thread(gpointer user_data) SWLOG_INFO("[FLASH_WORKER] Thread exiting, result: %d\n", flash_result); return NULL; } + +// ============================================================================ +// GLOBAL IN-MEMORY XCONF CACHE MANAGEMENT FUNCTIONS +// ============================================================================ + +/** + * @brief Clear the global XConf data cache + * + * Frees all dynamically allocated strings in the global cache and marks + * it as invalid. Should be called before overwriting with new data or + * when cache needs to be invalidated. + * + * Thread Safety: Caller MUST hold g_xconf_data_cache lock + * + * Note: This is an internal function, always called with lock held + */ +static void clear_cached_xconf_data_internal(void) +{ + + // Zero out the entire structure + memset(&g_cached_xconf_data, 0, sizeof(XCONFRES)); + + // Mark cache as invalid + g_xconf_data_valid = FALSE; + g_cached_http_code = 0; + + SWLOG_DEBUG("[CACHE_MEM] Global XConf cache cleared\n"); +} + +/** + * @brief Save parsed XConf data to global in-memory cache + * + * Populates the global g_cached_xconf_data structure with parsed XConf + * response data. This allows fast access without file I/O or JSON parsing. + * + * Thread Safety: Thread-safe, uses g_xconf_data_cache mutex + * + * @param pResponse Parsed XConf response structure to cache + * @param http_code HTTP status code from XConf query + * @return TRUE on success, FALSE on error + */ +static gboolean save_cached_xconf_data(const XCONFRES *pResponse, int http_code) +{ + if (!pResponse) { + SWLOG_ERROR("[CACHE_MEM] Cannot save NULL XConf response to memory cache\n"); + return FALSE; + } + + SWLOG_INFO("[CACHE_MEM] Saving parsed XConf data to global in-memory cache\n"); + + // === CRITICAL SECTION START === + G_LOCK(xconf_data_cache); + + // Clear existing cache before overwriting + clear_cached_xconf_data_internal(); + + // Deep copy all fields from pResponse to g_cached_xconf_data + // Note: XCONFRES uses fixed-size char arrays, not pointers + + if (pResponse->cloudFWVersion[0]) { + strncpy(g_cached_xconf_data.cloudFWVersion, pResponse->cloudFWVersion, + sizeof(g_cached_xconf_data.cloudFWVersion) - 1); + g_cached_xconf_data.cloudFWVersion[sizeof(g_cached_xconf_data.cloudFWVersion) - 1] = '\0'; + } + + if (pResponse->cloudFWFile[0]) { + strncpy(g_cached_xconf_data.cloudFWFile, pResponse->cloudFWFile, + sizeof(g_cached_xconf_data.cloudFWFile) - 1); + g_cached_xconf_data.cloudFWFile[sizeof(g_cached_xconf_data.cloudFWFile) - 1] = '\0'; + } + + if (pResponse->cloudFWLocation[0]) { + strncpy(g_cached_xconf_data.cloudFWLocation, pResponse->cloudFWLocation, + sizeof(g_cached_xconf_data.cloudFWLocation) - 1); + g_cached_xconf_data.cloudFWLocation[sizeof(g_cached_xconf_data.cloudFWLocation) - 1] = '\0'; + } + + if (pResponse->ipv6cloudFWLocation[0]) { + strncpy(g_cached_xconf_data.ipv6cloudFWLocation, pResponse->ipv6cloudFWLocation, + sizeof(g_cached_xconf_data.ipv6cloudFWLocation) - 1); + g_cached_xconf_data.ipv6cloudFWLocation[sizeof(g_cached_xconf_data.ipv6cloudFWLocation) - 1] = '\0'; + } + + if (pResponse->cloudProto[0]) { + strncpy(g_cached_xconf_data.cloudProto, pResponse->cloudProto, + sizeof(g_cached_xconf_data.cloudProto) - 1); + g_cached_xconf_data.cloudProto[sizeof(g_cached_xconf_data.cloudProto) - 1] = '\0'; + } + + if (pResponse->cloudImmediateRebootFlag[0]) { + strncpy(g_cached_xconf_data.cloudImmediateRebootFlag, pResponse->cloudImmediateRebootFlag, + sizeof(g_cached_xconf_data.cloudImmediateRebootFlag) - 1); + g_cached_xconf_data.cloudImmediateRebootFlag[sizeof(g_cached_xconf_data.cloudImmediateRebootFlag) - 1] = '\0'; + } + + if (pResponse->cloudDelayDownload[0]) { + strncpy(g_cached_xconf_data.cloudDelayDownload, pResponse->cloudDelayDownload, + sizeof(g_cached_xconf_data.cloudDelayDownload) - 1); + g_cached_xconf_data.cloudDelayDownload[sizeof(g_cached_xconf_data.cloudDelayDownload) - 1] = '\0'; + } + + if (pResponse->cloudPDRIVersion[0]) { + strncpy(g_cached_xconf_data.cloudPDRIVersion, pResponse->cloudPDRIVersion, + sizeof(g_cached_xconf_data.cloudPDRIVersion) - 1); + g_cached_xconf_data.cloudPDRIVersion[sizeof(g_cached_xconf_data.cloudPDRIVersion) - 1] = '\0'; + } + + if (pResponse->peripheralFirmwares[0]) { + strncpy(g_cached_xconf_data.peripheralFirmwares, pResponse->peripheralFirmwares, + sizeof(g_cached_xconf_data.peripheralFirmwares) - 1); + g_cached_xconf_data.peripheralFirmwares[sizeof(g_cached_xconf_data.peripheralFirmwares) - 1] = '\0'; + } + + if (pResponse->dlCertBundle[0]) { + strncpy(g_cached_xconf_data.dlCertBundle, pResponse->dlCertBundle, + sizeof(g_cached_xconf_data.dlCertBundle) - 1); + g_cached_xconf_data.dlCertBundle[sizeof(g_cached_xconf_data.dlCertBundle) - 1] = '\0'; + } + + // Save HTTP code + g_cached_http_code = http_code; + + // Mark cache as valid + g_xconf_data_valid = TRUE; + + G_UNLOCK(xconf_data_cache); + // === CRITICAL SECTION END === + + SWLOG_INFO("[CACHE_MEM] Global in-memory cache saved successfully\n"); + SWLOG_INFO("[CACHE_MEM] - Version: '%s'\n", g_cached_xconf_data.cloudFWVersion); + SWLOG_INFO("[CACHE_MEM] - File: '%s'\n", g_cached_xconf_data.cloudFWFile); + SWLOG_INFO("[CACHE_MEM] - Location: '%s'\n", g_cached_xconf_data.cloudFWLocation); + SWLOG_INFO("[CACHE_MEM] - HTTP Code: %d\n", g_cached_http_code); + + return TRUE; +} + +/** + * @brief Get parsed XConf data from global in-memory cache + * + * Returns a deep copy of the cached XConf response data. This is the + * primary access method for other functions to retrieve firmware metadata. + * + * Use Case: DownloadFirmware can call this to get cloudFWLocation without + * file I/O or JSON parsing overhead. + * + * Thread Safety: Thread-safe, uses g_xconf_data_cache mutex + * + * @param[out] pResponse Output structure to populate with cached data + * @param[out] pHttpCode Output HTTP status code (can be NULL if not needed) + * @return TRUE if cache is valid and data copied, FALSE if cache invalid/empty + */ +gboolean get_cached_xconf_data(XCONFRES *pResponse, int *pHttpCode) +{ + if (!pResponse) { + SWLOG_ERROR("[CACHE_MEM] Cannot copy to NULL pResponse\n"); + return FALSE; + } + + gboolean result = FALSE; + + // === CRITICAL SECTION START === + G_LOCK(xconf_data_cache); + + if (!g_xconf_data_valid) { + SWLOG_DEBUG("[CACHE_MEM] Global cache is invalid or empty\n"); + G_UNLOCK(xconf_data_cache); + return FALSE; + } + + // Deep copy cached data to output structure + memcpy(pResponse, &g_cached_xconf_data, sizeof(XCONFRES)); + + // Copy HTTP code if requested + if (pHttpCode) { + *pHttpCode = g_cached_http_code; + } + + result = TRUE; + + G_UNLOCK(xconf_data_cache); + // === CRITICAL SECTION END === + + SWLOG_DEBUG("[CACHE_MEM] Retrieved XConf data from global cache\n"); + SWLOG_DEBUG("[CACHE_MEM] - Version: '%s'\n", pResponse->cloudFWVersion); + SWLOG_DEBUG("[CACHE_MEM] - Location: '%s'\n", pResponse->cloudFWLocation); + + return result; +} + +/** + * @brief Clear the global XConf data cache (public interface) + * + * Thread-safe public wrapper for clearing the global cache. + * Use this when cache needs to be invalidated (e.g., on error or manual refresh). + */ +void clear_cached_xconf_data(void) +{ + G_LOCK(xconf_data_cache); + clear_cached_xconf_data_internal(); + G_UNLOCK(xconf_data_cache); + + SWLOG_INFO("[CACHE_MEM] Global XConf cache cleared by request\n"); +} diff --git a/src/dbus/rdkFwupdateMgr_handlers.h b/src/dbus/rdkFwupdateMgr_handlers.h index bfa8ba2f..8782f813 100644 --- a/src/dbus/rdkFwupdateMgr_handlers.h +++ b/src/dbus/rdkFwupdateMgr_handlers.h @@ -157,27 +157,6 @@ typedef struct { gchar *error_message; // Error description if failed } DownloadFirmwareResult; -/* - * Download Firmware - * - * Initiates firmware download from XConf-provided URL or custom URL. - * This function performs the actual download in the calling thread. - * - * Parameters: - * firmwareName - Firmware filename to download - * downloadUrl - Custom URL or empty string (use XConf URL) - * typeOfFirmware - Firmware type: "PCI", "PDRI", "PERIPHERAL" - * localFilePath - Destination file path - * download_state - DownloadState pointer for progress updates (can be NULL) - * - * Returns: - * DownloadFirmwareResult with result_code and error details - */ -DownloadFirmwareResult rdkFwupdateMgr_downloadFirmware(const gchar *firmwareName, - const gchar *downloadUrl, - const gchar *typeOfFirmware, - const gchar *localFilePath, - void *download_state); /* * Update Firmware (Future Implementation) @@ -275,7 +254,55 @@ int rdkFwupdateMgr_unregisterProcess(guint64 handler_id); * progress monitoring during firmware downloads. */ gpointer rdkfw_progress_monitor_thread(gpointer user_data); + +/* + * Load XConf Response from File Cache + * + * Loads and parses the cached XConf JSON response from disk. + * Used as fallback when in-memory cache is not available. + * + * @param[out] pResponse Structure to populate with parsed data + * @return TRUE on success, FALSE if cache missing or corrupt + */ gboolean load_xconf_from_cache(XCONFRES *pResponse); + +/* + * Get Cached XConf Data from In-Memory Cache + * + * Retrieves parsed XConf response from global in-memory cache. + * This is faster than file I/O and avoids repeated JSON parsing. + * + * Primary use case: DownloadFirmware uses this to get cloudFWLocation + * without file I/O overhead when downloadUrl parameter is NULL/empty. + * + * Thread Safety: Thread-safe, uses internal mutex + * + * @param[out] pResponse Structure to populate with cached data (deep copy) + * @param[out] pHttpCode HTTP status code from original XConf query (can be NULL) + * @return TRUE if cache is valid and data copied, FALSE if cache empty/invalid + * + * Example Usage: + * ```c + * XCONFRES cached_data; + * int http_code; + * if (get_cached_xconf_data(&cached_data, &http_code)) { + * // Use cached_data.cloudFWLocation for download URL + * printf("Download URL: %s\n", cached_data.cloudFWLocation); + * } + * ``` + */ +gboolean get_cached_xconf_data(XCONFRES *pResponse, int *pHttpCode); + +/* + * Clear Global In-Memory XConf Cache + * + * Invalidates and clears the global in-memory XConf cache. + * Should be called when cache needs to be refreshed or on errors. + * + * Thread Safety: Thread-safe, uses internal mutex + */ +void clear_cached_xconf_data(void); + #ifdef GTEST_ENABLE gboolean save_xconf_to_cache(const char *xconf_response, int http_code); CheckUpdateResponse create_result_response(CheckForUpdateStatus status_code, @@ -283,5 +310,33 @@ CheckUpdateResponse create_result_response(CheckForUpdateStatus status_code, CheckUpdateResponse create_success_response(const gchar *available_version, const gchar *update_details, const gchar *status_message); + +/* + * Create CheckUpdateResponse for Opt-Out Scenarios (Exposed for Unit Testing) + * + * Internal helper function that creates a response structure for IGNORE_OPTOUT and + * BYPASS_OPTOUT status codes. Always includes full firmware metadata (available_version + * and update_details) so clients can display update information even when updates are + * blocked by user preferences. + * + * This function is used in the post-XConf opt-out evaluation phase as specified in + * PLAN-1.md Version 2.0. It ensures clients receive complete firmware information + * regardless of opt-out state. + * + * Parameters: + * status_code - Status code (IGNORE_OPTOUT=4 or BYPASS_OPTOUT=5) + * available_version - Firmware version from XConf server + * update_details - Pipe-delimited firmware metadata string + * status_message - Human-readable explanation of opt-out state + * + * Returns: + * CheckUpdateResponse with all fields populated (must be freed with checkupdate_response_free) + * + * Note: This is an internal function - use rdkFwupdateMgr_checkForUpdate() for production code. + */ +CheckUpdateResponse create_optout_response(CheckForUpdateStatus status_code, + const gchar *available_version, + const gchar *update_details, + const gchar *status_message); #endif #endif // RDKFWUPDATEMGR_HANDLERS_H diff --git a/src/dbus/rdkv_dbus_server.c b/src/dbus/rdkv_dbus_server.c index ab2fa04f..1eaa2adf 100644 --- a/src/dbus/rdkv_dbus_server.c +++ b/src/dbus/rdkv_dbus_server.c @@ -585,119 +585,6 @@ static void free_task_context(TaskContext *ctx) g_free(ctx); } -/** - * @brief Complete all waiting CheckForUpdate tasks and send responses. - * - * Called after XConf query completes. Iterates through waiting_checkUpdate_ids list, - * sends D-Bus method responses with cached result data, emits CheckForUpdateComplete - * signals, and cleans up task contexts. Resets XConf status flag. - * - * @param ctx Task context (currently unused, kept for API consistency) - */ -#if 0 -void complete_CheckUpdate_waiting_tasks(TaskContext *ctx) -{ - SWLOG_INFO("Completing %d waiting CheckUpdate tasks\n", g_slist_length(waiting_checkUpdate_ids)); - // Iterate through each task_id in waiting_checkUpdate_ids - GSList *current = waiting_checkUpdate_ids; - while (current != NULL) { - guint task_id = GPOINTER_TO_UINT(current->data); - SWLOG_INFO("current task Id %d will get cleared after sending response to the app\n", task_id); - if (active_tasks == NULL) { - SWLOG_INFO("ERROR: tasks table is NULL\n"); - return; - } - // Lookup task_id in active_task - //TaskContext *context = g_hash_table_lookup(active_tasks, GUINT_TO_POINTER(task_id)); - TaskContext *context = g_hash_table_lookup(active_tasks, GUINT_TO_POINTER(task_id)); - if (context != NULL) { - SWLOG_INFO("[Waiting task_id in -%d] Sending response to app_id : %s\n",task_id, context->process_name); - - // Send D-Bus response using the stored result data - const gchar *version = context->data.check_update.client_fwdata_version ? - context->data.check_update.client_fwdata_version : ""; - const gchar *available = context->data.check_update.client_fwdata_availableVersion ? - context->data.check_update.client_fwdata_availableVersion : ""; - const gchar *details = context->data.check_update.client_fwdata_updateDetails ? - context->data.check_update.client_fwdata_updateDetails : ""; - const gchar *status_str = context->data.check_update.client_fwdata_status ? - context->data.check_update.client_fwdata_status : ""; - - SWLOG_INFO("[CHECK_UPDATE] Task Completion - Sending Response\n"); - SWLOG_INFO("[CHECK_UPDATE] Task ID: %d\n", task_id); - SWLOG_INFO("[CHECK_UPDATE] Response data:\n"); - SWLOG_INFO("[CHECK_UPDATE] - Current FW Version: '%s'\n", version); - SWLOG_INFO("[CHECK_UPDATE] - Available Version: '%s'\n", available); - SWLOG_INFO("[CHECK_UPDATE] - Update Details: '%s'\n", details); - SWLOG_INFO("[CHECK_UPDATE] - Status String: '%s'\n", status_str); - SWLOG_INFO("[CHECK_UPDATE] - Status Code: %d ", (gint32)context->data.check_update.result_code); - - // Log status meaning - switch(context->data.check_update.result_code) { - case 0: SWLOG_INFO("(FIRMWARE_AVAILABLE)\n"); break; - case 1: SWLOG_INFO("(FIRMWARE_NOT_AVAILABLE)\n"); break; - case 2: SWLOG_INFO("(UPDATE_NOT_ALLOWED)\n"); break; - case 3: SWLOG_INFO("(FIRMWARE_CHECK_ERROR)\n"); break; - case 4: SWLOG_INFO("(IGNORE_OPTOUT)\n"); break; - case 5: SWLOG_INFO("(BYPASS_OPTOUT)\n"); break; - default: SWLOG_INFO("(UNKNOWN_STATUS)\n"); break; - } - SWLOG_INFO("[CHECK_UPDATE] Sending D-Bus response to client...\n"); - g_dbus_method_invocation_return_value(context->invocation, - g_variant_new("(issssi)", - 0, // result: CHECK_FOR_UPDATE_SUCCESS (API call succeeded) - version, // Current/Detected Fw Version (from server) - available, // Available Version (from XConf) - details, // Update Details (from XConf) - status_str, // Status string from FwData structure (optional field) - (gint32)context->data.check_update.result_code)); // Status Code (0=FIRMWARE_AVAILABLE, 1=FIRMWARE_NOT_AVAILABLE, 2=UPDATE_NOT_ALLOWED, 3=FIRMWARE_CHECK_ERROR, 4=IGNORE_OPTOUT, 5=BYPASS_OPTOUT) - - SWLOG_INFO("[CHECK_UPDATE] Response sent successfully to client\n"); - - // ALSO emit D-Bus signal for callback mechanism (NEW ADDITION) - SWLOG_INFO("[CHECK_UPDATE] Emitting D-Bus signal for callback...\n"); - GError *signal_error = NULL; - gboolean signal_result = g_dbus_connection_emit_signal(connection, - NULL, // Broadcast to all listeners - "/org/rdkfwupdater/Service", - "org.rdkfwupdater.Interface", - "CheckForUpdateComplete", - g_variant_new("(tiissss)", - g_ascii_strtoull(context->process_name, NULL, 10), // handler_id (uint64) - (gint32)CHECK_FOR_UPDATE_SUCCESS, // result (API call result) - (gint32)context->data.check_update.result_code, // status_code (firmware status) - version, // current_version - available, // available_version - details, // update_details - status_str // status_message - ), - &signal_error); - - if (signal_result) { - SWLOG_INFO("[CHECK_UPDATE] D-Bus signal emitted successfully for handler '%s'\n", context->process_name); - } else { - SWLOG_ERROR("[CHECK_UPDATE] Failed to emit D-Bus signal: %s\n", - signal_error ? signal_error->message : "Unknown error"); - if (signal_error) g_error_free(signal_error); - } - // Remove task_id from active_tasks - g_hash_table_remove(active_tasks, GUINT_TO_POINTER(task_id)); - SWLOG_INFO("[CHECK_UPDATE] Task-%d removed from active tasks\n", task_id); - SWLOG_INFO("[CHECK_UPDATE] Task Completion - SUCCESS\n"); - } else { - SWLOG_INFO("Task-%d not found in active_tasks\n", task_id); - } - current = current->next; - } - // Clear waiting_CheckUpdatr_ids list - g_slist_free(waiting_checkUpdate_ids); - waiting_checkUpdate_ids = NULL; - setXConfCommStatus(FALSE); - SWLOG_INFO("All CheckUpdate waiting tasks completed !!\n"); -} - -#endif - /** * @brief Complete all waiting DownloadFirmware tasks and send responses. * @@ -772,7 +659,7 @@ static void process_app_request(GDBusConnection *rdkv_conn_dbus, /* CHECK UPDATE REQUEST*/ //extract process handler_id and FwData from the payload - inputs provided by client app - /* CHECK FOR UPDATE REQUEST - CACHE-FIRST, NON-BLOCKING */ + /* CHECK FOR UPDATE REQUEST - NON-BLOCKING */ if (g_strcmp0(rdkv_req_method, "CheckForUpdate") == 0) { gchar *handler_process_name = NULL; g_variant_get(rdkv_req_payload, "(s)", &handler_process_name); @@ -827,94 +714,7 @@ static void process_app_request(GDBusConnection *rdkv_conn_dbus, return; } - // 3. CHECK CACHE (FAST, NON-BLOCKING) - SWLOG_INFO("\n[STEP 3] Cache Check\n"); - SWLOG_INFO(" Calling: xconf_cache_exists()\n"); - gboolean cache_exists = xconf_cache_exists(); - SWLOG_INFO("[CHECK_UPDATE] Result: %s\n", cache_exists ? "CACHE HIT" : "CACHE MISS"); - - if (cache_exists) { - // CACHE HIT PATH - SWLOG_INFO("[CHECK_UPDATE] CACHE HIT PATH - Immediate Response\n"); - SWLOG_INFO("[CHECK_UPDATE] Action: Loading firmware data from cache\n"); - - CheckUpdateResponse response = rdkFwupdateMgr_checkForUpdate(handler_process_name); - - SWLOG_INFO("[CHECK_UPDATE] Cache data loaded successfully\n"); - SWLOG_INFO("[CHECK_UPDATE] Cached Firmware Data:\n"); - SWLOG_INFO("[CHECK_UPDATE] API Result: %d ", response.result); - switch(response.result) { - case 0: SWLOG_INFO(" (CHECK_FOR_UPDATE_SUCCESS)\n"); break; - case 1: SWLOG_INFO(" (CHECK_FOR_UPDATE_FAIL)\n"); break; - default: SWLOG_INFO(" (UNKNOWN)\n"); break; - } - SWLOG_INFO("[CHECK_UPDATE] Firmware Status Code: %d ", response.status_code); - switch(response.status_code) { - case 0: SWLOG_INFO(" (FIRMWARE_AVAILABLE)\n"); break; - case 1: SWLOG_INFO(" (FIRMWARE_NOT_AVAILABLE)\n"); break; - case 2: SWLOG_INFO(" (UPDATE_NOT_ALLOWED)\n"); break; - case 3: SWLOG_INFO(" (FIRMWARE_CHECK_ERROR)\n"); break; - case 4: SWLOG_INFO(" (IGNORE_OPTOUT)\n"); break; - case 5: SWLOG_INFO(" (BYPASS_OPTOUT)\n"); break; - default: SWLOG_INFO(" (UNKNOWN)\n"); break; - } - SWLOG_INFO("[CHECK_UPDATE] - Current Version: '%s'\n", - response.current_img_version ? response.current_img_version : "N/A"); - SWLOG_INFO("[CHECK_UPDATE] - Available Version: '%s'\n", - response.available_version ? response.available_version : "N/A"); - SWLOG_INFO("[CHECK_UPDATE] - Status Message: '%s'\n", - response.status_message ? response.status_message : "N/A"); - SWLOG_INFO("[CHECK_UPDATE] Sending immediate D-Bus method response\n"); - // Send immediate D-Bus response (issssi): result + 4 strings + status_code - g_dbus_method_invocation_return_value(resp_ctx, - g_variant_new("(issssi)", - response.result, // API call result (SUCCESS/FAIL) - response.current_img_version ? response.current_img_version : "", - response.available_version ? response.available_version : "", - response.update_details ? response.update_details : "", - response.status_message ? response.status_message : "", - response.status_code)); // Firmware status (0-5) - - SWLOG_INFO("[CHECK_UPDATE] D-Bus method response sent successfully\n"); - - // Also emit signal for consistency (so clients can use either method or signal) - SWLOG_INFO("[CHECK_UPDATE] Emitting CheckForUpdateComplete signal for consistency...\n"); - GError *signal_error = NULL; - gboolean signal_sent = g_dbus_connection_emit_signal(connection, - NULL, "/org/rdkfwupdater/Service", - "org.rdkfwupdater.Interface", - "CheckForUpdateComplete", - g_variant_new("(tiissss)", - g_ascii_strtoull(handler_process_name, NULL, 10), // handler_id (uint64) - (gint32)response.result, // result (API call result) - (gint32)response.status_code, // status_code (firmware status) - response.current_img_version ? response.current_img_version : "", - response.available_version ? response.available_version : "", - response.update_details ? response.update_details : "", - response.status_message ? response.status_message : "" - ), - &signal_error); - - if (signal_sent) { - SWLOG_INFO("[CHECK_UPDATE] Signal emitted successfully\n"); - } else { - SWLOG_ERROR("[CHECK_UPDATE] Signal emission failed: %s\n", - signal_error ? signal_error->message : "Unknown"); - if (signal_error) g_error_free(signal_error); - } - - checkupdate_response_free(&response); - g_free(handler_process_name); - - SWLOG_INFO("[CHECK_UPDATE] CACHE HIT PATH COMPLETE\n"); - SWLOG_INFO("[CHECK_UPDATE] Total processing: Immediate (no async operation)\n"); - SWLOG_INFO("[CHECK_UPDATE] Client received: Real firmware data\n"); - return; - } - // CACHE MISS PATH - SWLOG_INFO("CACHE MISS PATH - Async Background Fetch\n"); - SWLOG_INFO(" XConf cache not available\n"); SWLOG_INFO(" Async non-blocking fetch required\n"); SWLOG_INFO(" Client flow:\n"); SWLOG_INFO(" 1. Gets FIRMWARE_CHECK_ERROR (status=3) immediately (check in progress)\n"); @@ -1189,7 +989,7 @@ static void process_app_request(GDBusConnection *rdkv_conn_dbus, SWLOG_INFO("[DOWNLOADFIRMWARE] Starting validation...\n"); - if (!handler_id_str || !strlen(handler_id_str) || !firmware_name || !strlen(firmware_name) || !download_url || !strlen(download_url)|| !type_of_firmware || !strlen(type_of_firmware)) { + if (!handler_id_str || !strlen(handler_id_str) || !firmware_name || !strlen(firmware_name) || !type_of_firmware || !strlen(type_of_firmware)) { SWLOG_ERROR("[DOWNLOADFIRMWARE] Invalid input. One or more fields are empty or NULL\n"); g_dbus_method_invocation_return_value(resp_ctx, g_variant_new("(sss)", @@ -1331,7 +1131,7 @@ static void process_app_request(GDBusConnection *rdkv_conn_dbus, // ========== CHECK FOR CACHED FILE (Scenario 8) ========== SWLOG_INFO("[DOWNLOADFIRMWARE] Checking for cached file...\n"); - gchar *cache_path = g_strdup_printf("/opt/CDL/%s", firmware_name); // MADHU - check if this is the path always to download image + gchar *cache_path = g_strdup_printf("/opt/CDL/%s", firmware_name); // TODO - check if this is the path always to download image SWLOG_INFO("[DOWNLOADFIRMWARE] Cache path: %s\n", cache_path); if (g_file_test(cache_path, G_FILE_TEST_EXISTS)) { @@ -2688,87 +2488,6 @@ static void rdkfw_xconf_fetch_done(GObject *source_object, GAsyncResult *res, gp * ============================================================================ */ -/** - * @brief Progress callback invoked by curl during firmware download - * - * Thread Context: WORKER THREAD (called by libcurl via xferinfo) - * Thread Safety: Uses g_idle_add() to marshal signals to main loop - * - * Call Chain: - * libcurl (worker thread) → xferinfo() [urlHelper.c] - * → download_progress_callback() [HERE] - * → g_idle_add(rdkfw_emit_download_progress, ...) - * → rdkfw_emit_download_progress() [main loop thread] - * → g_dbus_connection_emit_signal() - * - * Signature matches RdkUpgradeContext_t.progress_callback: - * void (*)(unsigned long long current_bytes, unsigned long long total_bytes, void* user_data) - * - * @param current_bytes Bytes downloaded so far - * @param total_bytes Total file size in bytes - * @param user_data AsyncDownloadContext* pointer - */ -#if 0 -static void download_progress_callback(unsigned long long current_bytes, - unsigned long long total_bytes, - void* user_data) { - AsyncDownloadContext *ctx = (AsyncDownloadContext *)user_data; - - SWLOG_DEBUG("[PROGRESS_CB] Invoked from worker thread (Thread ID: %lu)\n", (unsigned long)pthread_self()); - - // NULL CHECK: Validate context - if (!ctx) { - SWLOG_ERROR("[PROGRESS_CB] ERROR: NULL context received!\n"); - return; - } - - // NULL CHECK: Validate D-Bus connection - if (!ctx->connection) { - SWLOG_ERROR("[PROGRESS_CB] ERROR: NULL D-Bus connection in context!\n"); - return; - } - - // Calculate percentage from bytes - int progress_int = 0; - if (total_bytes > 0) { - double percent = ((double)current_bytes / (double)total_bytes) * 100.0; - progress_int = (int)percent; - if (progress_int > 100) progress_int = 100; - if (progress_int < 0) progress_int = 0; - } - - // Throttle logging (only log on change) - static int last_logged = -1; - if (progress_int != last_logged) { - SWLOG_INFO("[PROGRESS_CB] Download progress: %d%% (%llu/%llu bytes)\n", - progress_int, current_bytes, total_bytes); - SWLOG_INFO("[PROGRESS_CB] Firmware: %s\n", ctx->firmware_name ? ctx->firmware_name : "NULL"); - last_logged = progress_int; - } - - // Update global state - if (current_download) { - current_download->current_progress = progress_int; - } - - // Create progress update for D-Bus signal - ProgressUpdate *update = g_new0(ProgressUpdate, 1); - if (!update) { - SWLOG_ERROR("[PROGRESS_CB] ERROR: Failed to allocate ProgressUpdate!\n"); - return; - } - - update->progress = progress_int; - update->status = FW_DWNL_INPROGRESS; - update->handler_id = ctx->handler_id ? g_strdup(ctx->handler_id) : NULL; - update->firmware_name = ctx->firmware_name ? g_strdup(ctx->firmware_name) : NULL; - update->connection = ctx->connection; - - // Schedule signal emission on main loop (thread-safe!) - SWLOG_DEBUG("[PROGRESS_CB] Scheduling D-Bus signal emission via g_idle_add\n"); - g_idle_add(rdkfw_emit_download_progress, update); -} -#endif /** * @brief Emit DownloadProgress signal on main loop (called via g_idle_add) * @@ -2994,8 +2713,120 @@ static void rdkfw_download_worker(GTask *task, gpointer source_object, return; } - if (!ctx->download_url || strlen(ctx->download_url) == 0) { - SWLOG_ERROR("[DOWNLOAD_WORKER] CRITICAL: Invalid download URL!\n"); + // ========== STEP 1.5: DETERMINE EFFECTIVE DOWNLOAD URL ========== + // If download_url is provided, use it + // If download_url is NULL/empty, try to load from XConf cache + + gchar *effective_download_url = NULL; + + if (ctx->download_url && strlen(ctx->download_url) > 0) { + // Use custom URL provided by client + SWLOG_INFO("[DOWNLOAD_WORKER] Using custom download URL: %s\n", ctx->download_url); + effective_download_url = g_strdup(ctx->download_url); + } else { + // Load URL from XConf cache (try in-memory first, then file cache) + SWLOG_INFO("[DOWNLOAD_WORKER] No custom URL, attempting to load from XConf cache\n"); + + XCONFRES xconf_response; + memset(&xconf_response, 0, sizeof(XCONFRES)); + int http_code = 0; + gboolean cache_loaded = FALSE; + + // Try in-memory cache first (fastest - no file I/O) + SWLOG_INFO("[DOWNLOAD_WORKER] Attempting to load from in-memory cache...\n"); + if (get_cached_xconf_data(&xconf_response, &http_code)) { + SWLOG_INFO("[DOWNLOAD_WORKER] SUCCESS: Loaded from in-memory cache\n"); + SWLOG_INFO("[DOWNLOAD_WORKER] - Version: %s\n", xconf_response.cloudFWVersion); + SWLOG_INFO("[DOWNLOAD_WORKER] - Location: %s\n", xconf_response.cloudFWLocation); + SWLOG_INFO("[DOWNLOAD_WORKER] - HTTP Code: %d\n", http_code); + cache_loaded = TRUE; + } else { + SWLOG_INFO("[DOWNLOAD_WORKER] In-memory cache miss, trying file cache...\n"); + + // Fallback: Check if file cache exists + if (!xconf_cache_exists()) { + SWLOG_ERROR("[DOWNLOAD_WORKER] ERROR: No XConf cache found (neither memory nor file)\n"); + SWLOG_ERROR("[DOWNLOAD_WORKER] Client must call CheckForUpdate first\n"); + + // Emit error signal + ProgressUpdate *error_update = g_new0(ProgressUpdate, 1); + error_update->progress = -1; + error_update->status = FW_DWNL_ERROR; + error_update->handler_id = ctx->handler_id ? g_strdup(ctx->handler_id) : NULL; + error_update->firmware_name = ctx->firmware_name ? g_strdup(ctx->firmware_name) : NULL; + error_update->connection = ctx->connection; + g_idle_add(rdkfw_emit_download_progress, error_update); + + g_task_return_boolean(task, FALSE); + return; + } + + // Load from file cache + if (!load_xconf_from_cache(&xconf_response)) { + SWLOG_ERROR("[DOWNLOAD_WORKER] ERROR: Failed to load XConf file cache\n"); + + // Emit error signal + ProgressUpdate *error_update = g_new0(ProgressUpdate, 1); + error_update->progress = -1; + error_update->status = FW_DWNL_ERROR; + error_update->handler_id = ctx->handler_id ? g_strdup(ctx->handler_id) : NULL; + error_update->firmware_name = ctx->firmware_name ? g_strdup(ctx->firmware_name) : NULL; + error_update->connection = ctx->connection; + g_idle_add(rdkfw_emit_download_progress, error_update); + + g_task_return_boolean(task, FALSE); + return; + } + + SWLOG_INFO("[DOWNLOAD_WORKER] SUCCESS: Loaded from file cache\n"); + cache_loaded = TRUE; + } + + if (!cache_loaded) { + SWLOG_ERROR("[DOWNLOAD_WORKER] ERROR: Failed to load XConf cache from any source\n"); + + // Emit error signal + ProgressUpdate *error_update = g_new0(ProgressUpdate, 1); + error_update->progress = -1; + error_update->status = FW_DWNL_ERROR; + error_update->handler_id = ctx->handler_id ? g_strdup(ctx->handler_id) : NULL; + error_update->firmware_name = ctx->firmware_name ? g_strdup(ctx->firmware_name) : NULL; + error_update->connection = ctx->connection; + g_idle_add(rdkfw_emit_download_progress, error_update); + + g_task_return_boolean(task, FALSE); + return; + } + + // Extract download URL from XConf response + const char *download_location = xconf_response.cloudFWLocation[0] ? + xconf_response.cloudFWLocation : NULL; + + // Validate URL + if (download_location == NULL || strlen(download_location) == 0) { + SWLOG_ERROR("[DOWNLOAD_WORKER] ERROR: XConf cache has no firmware download URL\n"); + SWLOG_ERROR("[DOWNLOAD_WORKER] - cloudFWLocation is empty or NULL\n"); + + // Emit error signal + ProgressUpdate *error_update = g_new0(ProgressUpdate, 1); + error_update->progress = -1; + error_update->status = FW_DWNL_ERROR; + error_update->handler_id = ctx->handler_id ? g_strdup(ctx->handler_id) : NULL; + error_update->firmware_name = ctx->firmware_name ? g_strdup(ctx->firmware_name) : NULL; + error_update->connection = ctx->connection; + g_idle_add(rdkfw_emit_download_progress, error_update); + + g_task_return_boolean(task, FALSE); + return; + } + + effective_download_url = g_strdup(download_location); + SWLOG_INFO("[DOWNLOAD_WORKER] Using firmware download URL from XConf: %s\n", effective_download_url); + } + + // Validate effective URL + if (!effective_download_url || strlen(effective_download_url) == 0) { + SWLOG_ERROR("[DOWNLOAD_WORKER] CRITICAL: No download URL available after resolution!\n"); // Emit error signal ProgressUpdate *error_update = g_new0(ProgressUpdate, 1); @@ -3006,10 +2837,13 @@ static void rdkfw_download_worker(GTask *task, gpointer source_object, error_update->connection = ctx->connection; g_idle_add(rdkfw_emit_download_progress, error_update); + if (effective_download_url) g_free(effective_download_url); g_task_return_boolean(task, FALSE); return; } + SWLOG_INFO("[DOWNLOAD_WORKER] Effective download URL resolved: %s\n", effective_download_url); + // ========== STEP 2: BUILD DOWNLOAD PATH ========== SWLOG_INFO("[DOWNLOAD_WORKER] Building download path...\n"); char download_path[DWNL_PATH_FILE_LENGTH]; @@ -3033,9 +2867,6 @@ static void rdkfw_download_worker(GTask *task, gpointer source_object, difw_path = NULL; } // ========== STEP 3: LOAD DEVICE PROPERTIES ========== - SWLOG_INFO("[DOWNLOAD_WORKER] ========================================\n"); - SWLOG_INFO("[DOWNLOAD_WORKER] LOADING DEVICE PROPERTIES\n"); - SWLOG_INFO("[DOWNLOAD_WORKER] ========================================\n"); DeviceProperty_t device_info; memset(&device_info, 0, sizeof(DeviceProperty_t)); @@ -3048,9 +2879,6 @@ static void rdkfw_download_worker(GTask *task, gpointer source_object, } // ========== STEP 4: LOAD RFC SETTINGS ========== - SWLOG_INFO("[DOWNLOAD_WORKER] ========================================\n"); - SWLOG_INFO("[DOWNLOAD_WORKER] LOADING RFC SETTINGS\n"); - SWLOG_INFO("[DOWNLOAD_WORKER] ========================================\n"); Rfc_t rfc_list; memset(&rfc_list, 0, sizeof(Rfc_t)); @@ -3076,7 +2904,7 @@ static void rdkfw_download_worker(GTask *task, gpointer source_object, SWLOG_INFO("[DOWNLOAD_WORKER] trigger_type = 4 (app-initiated via D-Bus)\n"); // Initialize lastrun as empty string - // In rdkv_main.c: char lastrun[64] = { 0 }; // Store last run time + // In rdkv_main.c: char lastrun[64] = { 0 }; char lastrun[64] = { 0 }; SWLOG_INFO("[DOWNLOAD_WORKER] lastrun = \"\" (empty string, as in rdkv_main.c)\n"); @@ -3112,17 +2940,22 @@ static void rdkfw_download_worker(GTask *task, gpointer source_object, upgrade_ctx.server_type = HTTP_SSR_DIRECT; SWLOG_INFO("[DOWNLOAD_WORKER] server_type = HTTP_SSR_DIRECT\n"); - int url_len = snprintf(imageHTTPURL, sizeof(imageHTTPURL), "%s/%s", ctx->download_url, ctx->firmware_name); + int url_len = snprintf(imageHTTPURL, sizeof(imageHTTPURL), "%s/%s", effective_download_url, ctx->firmware_name); if (url_len < 0 || url_len >= sizeof(imageHTTPURL)) { SWLOG_ERROR("[DOWNLOAD_WORKER] ERROR: URL too long or snprintf failed (len=%d, max=%zu)\n", url_len, sizeof(imageHTTPURL)); - SWLOG_ERROR("[DOWNLOAD_WORKER] URL would be: %s/%s\n", ctx->download_url, ctx->firmware_name); + SWLOG_ERROR("[DOWNLOAD_WORKER] URL would be: %s/%s\n", effective_download_url, ctx->firmware_name); + g_free(effective_download_url); g_task_return_boolean(task, FALSE); - return; + return; } upgrade_ctx.artifactLocationUrl = imageHTTPURL; SWLOG_INFO("[DOWNLOAD_WORKER] artifactLocationUrl = %s\n", upgrade_ctx.artifactLocationUrl); + // Free effective_download_url after building the final URL + g_free(effective_download_url); + effective_download_url = NULL; + upgrade_ctx.dwlloc = download_path; SWLOG_INFO("[DOWNLOAD_WORKER] dwlloc = %s\n", (const char*)upgrade_ctx.dwlloc); @@ -3286,6 +3119,13 @@ static void rdkfw_download_worker(GTask *task, gpointer source_object, SWLOG_INFO("[DOWNLOAD_WORKER] Return value: %d\n", curl_ret_code); SWLOG_INFO("[DOWNLOAD_WORKER] HTTP code: %d\n", http_code); + // Handle library-specific errors (negative values) - Daemon NEVER exits + if (curl_ret_code < 0) { + SWLOG_ERROR("[DOWNLOAD_WORKER] Library error: %s (code: %d)\n", + rdkv_upgrade_strerror(curl_ret_code), curl_ret_code); + // Daemon continues - error will be propagated to D-Bus client via existing error handling + } + // ========== STEP 9: STOP PROGRESS MONITOR THREAD ========== if (monitor_thread != NULL) { SWLOG_INFO("[DOWNLOAD_WORKER] Stopping progress monitor thread...\n"); diff --git a/src/device_status_helper.c b/src/device_status_helper.c index a59e4f92..9225a484 100644 --- a/src/device_status_helper.c +++ b/src/device_status_helper.c @@ -73,7 +73,7 @@ bool CurrentRunningInst(const char *file) while(getdelim(&arg, &size, 0,fp) != -1){ if (arg != NULL) { SWLOG_INFO("proc entry process name:%s\n",arg); - /* Checking process name is same as rdkvfwupgrader, rdkFwupdateMgr(deamon), or deviceInitiatedFWDnld*/ + /* Checking process name is same as rdkvfwupgrader, rdkFwupdateMgr(daemon), or deviceInitiatedFWDnld*/ if (strstr(arg, "rdkvfwupgrader") || strstr(arg, "rdkFwupdateMgr") || strstr(arg, "deviceInitiatedFWDnld")) { SWLOG_INFO("proc entry cmdline and process name matched.\nDevice initiated CDL is in progress..\n"); SWLOG_INFO("Exiting without triggering device initiated firmware download.\n"); @@ -353,20 +353,22 @@ void unsetStateRed(void) /* Description: If state red support is present eneter to state red * @param curlret: Receving curl status from Caller + * @return: 0 on success (no state red entry needed or flag already set) + * -1 on TLS/SSL error (state red entered, process should terminate in CLI mode) * */ -void checkAndEnterStateRed(int curlret, const char *disableStatsUpdate) { +int checkAndEnterStateRed(int curlret, const char *disableStatsUpdate) { int ret = -1; FILE *fp = NULL; struct FWDownloadStatus fwdls; ret = isStateRedSupported(); if(ret == 0) { - return; + return 0; } ret = isInStateRed(); if(ret == 1) { SWLOG_INFO("RED checkAndEnterStateRed: device state red recovery flag already set\n"); t2CountNotify("SYST_INFO_RedstateSet", 1); - return; + return 0; } if((curlret == 35) || (curlret == 51) || (curlret == 53) || (curlret == 54) || (curlret == 58) || (curlret == 59) || (curlret == 60) || (curlret == 64) || (curlret == 66) || (curlret == 77) || (curlret == 80) || (curlret == 82) || (curlret == 83) || (curlret == 90) @@ -405,7 +407,8 @@ void checkAndEnterStateRed(int curlret, const char *disableStatsUpdate) { if(fp != NULL) { fclose(fp); } - exit(1); + SWLOG_ERROR("RED checkAndEnterStateRed: State red entered due to TLS/SSL error %d. Returning error to caller.\n", curlret); + return -1; } else { //Recovery completed event send for the failure case but not due to fatal error if( (filePresentCheck( RED_STATE_REBOOT ) == RDK_API_SUCCESS) ) { @@ -414,6 +417,7 @@ void checkAndEnterStateRed(int curlret, const char *disableStatsUpdate) { unlink(RED_STATE_REBOOT); } } + return 0; } diff --git a/src/include/device_status_helper.h b/src/include/device_status_helper.h index fd5d8d27..d3d7fc56 100644 --- a/src/include/device_status_helper.h +++ b/src/include/device_status_helper.h @@ -44,7 +44,7 @@ bool isDeviceReadyForDownload(); int isStateRedSupported(void); int isInStateRed(void); -void checkAndEnterStateRed(int curlret, const char *); +int checkAndEnterStateRed(int curlret, const char *); int checkVideoStatus(const char *device_name); int isThrottleEnabled(const char *device_name, const char *reboot_immediate_flag, int app_mode); int isOCSPEnable(void); diff --git a/src/include/rdkv_upgrade.h b/src/include/rdkv_upgrade.h index c56fda00..3ab99494 100755 --- a/src/include/rdkv_upgrade.h +++ b/src/include/rdkv_upgrade.h @@ -30,6 +30,24 @@ extern "C" { #ifdef GTEST_ENABLE #include "miscellaneous.h" #endif + +/** + * @brief Library error codes for upgrade operations + * NOTE: All values are negative to distinguish from CURL error codes (positive) + */ +typedef enum { + RDKV_UPGRADE_SUCCESS = 0, + RDKV_UPGRADE_ERROR_THROTTLE_ZERO = -100, // Throttle speed = 0 + RDKV_UPGRADE_ERROR_FORCE_EXIT = -101, // Force exit (curl 23) +} rdkv_upgrade_error_t; + +/** + * @brief Convert error code to human-readable string + * @param error Error code (can be library error or CURL error) + * @return Human-readable error message + */ +const char* rdkv_upgrade_strerror(int error); + /** * @brief Input context structure for rdkv_upgrade_request function * Contains all input parameters passed to the upgrade request function diff --git a/src/rdkFwupdateMgr.c b/src/rdkFwupdateMgr.c index 5da709c2..d8209066 100644 --- a/src/rdkFwupdateMgr.c +++ b/src/rdkFwupdateMgr.c @@ -555,7 +555,14 @@ int peripheral_firmware_dndl( char *pCloudFWLocation, char *pPeripheralFirmwares peripheral_context.rfc_list = &rfc_list; iCurlCode = rdkv_upgrade_request(&peripheral_context, &curl, &http_code); - if( iCurlCode == 0 && http_code == 200 ) + + // Handle library-specific errors (negative values) - Daemon NEVER exits + if (iCurlCode < 0) { + SWLOG_ERROR("%s: Peripheral upgrade failed with library error: %s (code: %d)\n", + __FUNCTION__, rdkv_upgrade_strerror(iCurlCode), iCurlCode); + // Daemon continues running - just log error and continue + iRet = -1; + } else if( iCurlCode == 0 && http_code == 200 ) { if( szRunningLen ) { @@ -694,6 +701,13 @@ int checkTriggerUpgrade(XCONFRES *pResponse, const char *model) pci_context.rfc_list = &rfc_list; pci_curl_code = rdkv_upgrade_request(&pci_context, &curl, &http_code); + + // Handle library-specific errors (negative values) - Daemon NEVER exits + if (pci_curl_code < 0) { + SWLOG_ERROR("%s: PCI upgrade failed with library error: %s (code: %d)\n", + __FUNCTION__, rdkv_upgrade_strerror(pci_curl_code), pci_curl_code); + // Daemon continues running - error already logged + } } else { SWLOG_INFO("checkForValidPCIUpgrade return false\n"); pci_curl_code = 0; @@ -736,6 +750,14 @@ int checkTriggerUpgrade(XCONFRES *pResponse, const char *model) pdri_context.rfc_list = &rfc_list; pdri_curl_code = rdkv_upgrade_request(&pdri_context, &curl, &http_code); + + // Handle library-specific errors (negative values) - Daemon NEVER exits + if (pdri_curl_code < 0) { + SWLOG_ERROR("%s: PDRI upgrade failed with library error: %s (code: %d)\n", + __FUNCTION__, rdkv_upgrade_strerror(pdri_curl_code), pdri_curl_code); + // Daemon continues running - error already logged + } + snprintf(disableStatsUpdate, sizeof(disableStatsUpdate), "%s","no"); if (pdri_curl_code == 100) { pdri_curl_code = 0; @@ -859,7 +881,13 @@ static int MakeXconfComms( XCONFRES *pResponse, int server_type, int *pHttp_code xconf_context.rfc_list = &rfc_list; ret = rdkv_upgrade_request(&xconf_context, &curl, pHttp_code); - if( ret == 0 && *pHttp_code == 200 && DwnLoc.pvOut != NULL ) + + // Handle library-specific errors (negative values) - Daemon NEVER exits + if (ret < 0) { + SWLOG_ERROR("%s: XCONF upgrade failed with library error: %s (code: %d)\n", + __FUNCTION__, rdkv_upgrade_strerror(ret), ret); + // Daemon continues running - ret is already < 0, will be handled by existing error logic + } else if( ret == 0 && *pHttp_code == 200 && DwnLoc.pvOut != NULL ) { SWLOG_INFO( "MakeXconfComms: Calling getXconfRespData with input = %s\n", (char *)DwnLoc.pvOut ); ret = getXconfRespData( pResponse, (char *)DwnLoc.pvOut ); diff --git a/src/rdkv_main.c b/src/rdkv_main.c index e44b94c2..6671208e 100644 --- a/src/rdkv_main.c +++ b/src/rdkv_main.c @@ -498,7 +498,21 @@ int peripheral_firmware_dndl( char *pCloudFWLocation, char *pPeripheralFirmwares peripheral_context.rfc_list = &rfc_list; iCurlCode = rdkv_upgrade_request(&peripheral_context, &curl, &http_code); - if( iCurlCode == 0 && http_code == 200 ) + + // Handle library-specific errors (negative values) + if (iCurlCode < 0) { + SWLOG_ERROR("%s: Peripheral upgrade failed with library error: %s (code: %d)\n", + __FUNCTION__, rdkv_upgrade_strerror(iCurlCode), iCurlCode); + + // CLI binary can exit on fatal library errors + if (iCurlCode == RDKV_UPGRADE_ERROR_THROTTLE_ZERO || + iCurlCode == RDKV_UPGRADE_ERROR_FORCE_EXIT) { + SWLOG_INFO("%s: Fatal library error, exiting process\n", __FUNCTION__); + uninitialize(INITIAL_VALIDATION_SUCCESS); + exit(1); + } + iRet = -1; + } else if( iCurlCode == 0 && http_code == 200 ) { if( szRunningLen ) { @@ -637,6 +651,20 @@ int checkTriggerUpgrade(XCONFRES *pResponse, const char *model) pci_context.rfc_list = &rfc_list; pci_curl_code = rdkv_upgrade_request(&pci_context, &curl, &http_code); + + // Handle library-specific errors (negative values) + if (pci_curl_code < 0) { + SWLOG_ERROR("%s: PCI upgrade failed with library error: %s (code: %d)\n", + __FUNCTION__, rdkv_upgrade_strerror(pci_curl_code), pci_curl_code); + + // CLI binary can exit on fatal library errors + if (pci_curl_code == RDKV_UPGRADE_ERROR_THROTTLE_ZERO || + pci_curl_code == RDKV_UPGRADE_ERROR_FORCE_EXIT) { + SWLOG_INFO("%s: Fatal library error, exiting process\n", __FUNCTION__); + uninitialize(INITIAL_VALIDATION_SUCCESS); + exit(1); + } + } } else { SWLOG_INFO("checkForValidPCIUpgrade return false\n"); pci_curl_code = 0; @@ -679,6 +707,21 @@ int checkTriggerUpgrade(XCONFRES *pResponse, const char *model) pdri_context.rfc_list = &rfc_list; pdri_curl_code = rdkv_upgrade_request(&pdri_context, &curl, &http_code); + + // Handle library-specific errors (negative values) + if (pdri_curl_code < 0) { + SWLOG_ERROR("%s: PDRI upgrade failed with library error: %s (code: %d)\n", + __FUNCTION__, rdkv_upgrade_strerror(pdri_curl_code), pdri_curl_code); + + // CLI binary can exit on fatal library errors + if (pdri_curl_code == RDKV_UPGRADE_ERROR_THROTTLE_ZERO || + pdri_curl_code == RDKV_UPGRADE_ERROR_FORCE_EXIT) { + SWLOG_INFO("%s: Fatal library error, exiting process\n", __FUNCTION__); + uninitialize(INITIAL_VALIDATION_SUCCESS); + exit(1); + } + } + snprintf(disableStatsUpdate, sizeof(disableStatsUpdate), "%s","no"); if (pdri_curl_code == 100) { pdri_curl_code = 0; @@ -801,7 +844,21 @@ static int MakeXconfComms( XCONFRES *pResponse, int server_type, int *pHttp_code xconf_context.rfc_list = &rfc_list; ret = rdkv_upgrade_request(&xconf_context, &curl, pHttp_code); - if( ret == 0 && *pHttp_code == 200 && DwnLoc.pvOut != NULL ) + + // Handle library-specific errors (negative values) + if (ret < 0) { + SWLOG_ERROR("%s: XCONF upgrade failed with library error: %s (code: %d)\n", + __FUNCTION__, rdkv_upgrade_strerror(ret), ret); + + // CLI binary can exit on fatal library errors + if (ret == RDKV_UPGRADE_ERROR_THROTTLE_ZERO || + ret == RDKV_UPGRADE_ERROR_FORCE_EXIT) { + SWLOG_INFO("%s: Fatal library error, exiting process\n", __FUNCTION__); + uninitialize(INITIAL_VALIDATION_SUCCESS); + exit(1); + } + // For non-fatal errors, ret is already < 0, will be handled by existing error logic + } else if( ret == 0 && *pHttp_code == 200 && DwnLoc.pvOut != NULL ) { SWLOG_INFO( "MakeXconfComms: Calling getXconfRespData with input = %s\n", (char *)DwnLoc.pvOut ); ret = getXconfRespData( pResponse, (char *)DwnLoc.pvOut ); diff --git a/src/rdkv_upgrade.c b/src/rdkv_upgrade.c index cc511235..421c70d2 100755 --- a/src/rdkv_upgrade.c +++ b/src/rdkv_upgrade.c @@ -33,6 +33,27 @@ #endif #include "flash.h" +/** + * @brief Convert upgrade error code to human-readable string + * @param error Error code (negative = library error, positive = CURL error, 0 = success) + * @return Human-readable error message string + */ +const char* rdkv_upgrade_strerror(int error) { + switch(error) { + case RDKV_UPGRADE_SUCCESS: + return "Success"; + case RDKV_UPGRADE_ERROR_THROTTLE_ZERO: + return "Throttle speed set to 0 - download blocked"; + case RDKV_UPGRADE_ERROR_FORCE_EXIT: + return "Force exit requested (curl error 23)"; + default: + if (error > 0) { + return "CURL error"; // Existing CURL error codes + } + return "Unknown library error"; + } +} + /* Description: Use for save process id and store inside file. * @param: file: file name to save pid * @param: data: data to save inside file. @@ -175,10 +196,14 @@ void dwnlError(int curl_code, int http_code, int server_type,const DevicePropert // HTTP CODE 495 - Expired client certificate not in servers allow list if( http_code == 495 ) { SWLOG_INFO("%s : Calling checkAndEnterStateRed() with code:%d\n", __FUNCTION__, http_code); - checkAndEnterStateRed(http_code, disableStatsUpdate); + if (checkAndEnterStateRed(http_code, disableStatsUpdate) != 0) { + SWLOG_ERROR("%s : State red entered due to HTTP error %d\n", __FUNCTION__, http_code); + } }else { SWLOG_INFO("%s : Calling checkAndEnterStateRed() with code:%d\n", __FUNCTION__, curl_code); - checkAndEnterStateRed(curl_code, disableStatsUpdate); + if (checkAndEnterStateRed(curl_code, disableStatsUpdate) != 0) { + SWLOG_ERROR("%s : State red entered due to curl error %d\n", __FUNCTION__, curl_code); + } } } @@ -482,6 +507,13 @@ int rdkv_upgrade_request(const RdkUpgradeContext_t* context, void** curl, int* p } unsetStateRed(); } + if (ret_curl_code == RDKV_UPGRADE_ERROR_THROTTLE_ZERO ){ + return RDKV_UPGRADE_ERROR_THROTTLE_ZERO; + } + else if (ret_curl_code == RDKV_UPGRADE_ERROR_FORCE_EXIT) { + return RDKV_UPGRADE_ERROR_FORCE_EXIT; + } + if (ret_curl_code != CURL_SUCCESS || (*pHttp_code != HTTP_SUCCESS && *pHttp_code != HTTP_CHUNK_SUCCESS && *pHttp_code != HTTP_PAGE_NOT_FOUND)) { ret_curl_code = retryDownload(context, RETRY_COUNT, 60, pHttp_code, curl); @@ -506,6 +538,12 @@ int rdkv_upgrade_request(const RdkUpgradeContext_t* context, void** curl, int* p } else if (server_type == HTTP_SSR_CODEBIG || server_type == HTTP_XCONF_CODEBIG) { ret_curl_code = codebigdownloadFile(context, pHttp_code, curl); + if (ret_curl_code == RDKV_UPGRADE_ERROR_THROTTLE_ZERO ){ + return RDKV_UPGRADE_ERROR_THROTTLE_ZERO; + } + else if (ret_curl_code == RDKV_UPGRADE_ERROR_FORCE_EXIT) { + return RDKV_UPGRADE_ERROR_FORCE_EXIT; + } if (ret_curl_code != CURL_SUCCESS || (*pHttp_code != HTTP_SUCCESS && *pHttp_code != HTTP_CHUNK_SUCCESS && *pHttp_code != HTTP_PAGE_NOT_FOUND)) { if( ret_curl_code != CODEBIG_SIGNING_FAILED ) @@ -762,9 +800,9 @@ int codebigdownloadFile( } doStopDownload(*curl); *curl = NULL; - if (*force_exit == 1 && (curl_ret_code == 23)) { - uninitialize(INITIAL_VALIDATION_SUCCESS); - exit(1); + if (force_exit != NULL && *force_exit == 1 && (curl_ret_code == 23)) { + SWLOG_INFO("%s : Force exit after codebig download (curl error 23)\n", __FUNCTION__); + return RDKV_UPGRADE_ERROR_FORCE_EXIT; } } @@ -950,13 +988,17 @@ int downloadFile( SWLOG_INFO("%s : Throttle feature is Enable\n", __FUNCTION__); Upgradet2CountNotify("SYST_INFO_Thrtl_Enable", 1); if (max_dwnl_speed == 0) { - SWLOG_INFO("%s : Throttle speed set to 0. So exiting the download process\n", __FUNCTION__); + SWLOG_INFO("%s : Throttle speed set to 0. Returning error to caller\n", __FUNCTION__); if (!(strncmp(device_info->maint_status, "true", 4))) { eventManager("MaintenanceMGR", MAINT_FWDOWNLOAD_ERROR); } eventManager(FW_STATE_EVENT, FW_STATE_FAILED); - uninitialize(INITIAL_VALIDATION_SUCCESS); - exit(1); //maintenance mode is background and speed set to 0. So exiting the process +#ifdef LIBRDKCERTSELECTOR + if (thisCertSel != NULL) { + rdkcertselector_free(&thisCertSel); + } +#endif + return RDKV_UPGRADE_ERROR_THROTTLE_ZERO; } } else { SWLOG_INFO("%s : Throttle feature is Disable\n", __FUNCTION__); @@ -996,7 +1038,9 @@ int downloadFile( if (ret == MTLS_CERT_FETCH_FAILURE) { SWLOG_ERROR("%s : ret=%d\n", __FUNCTION__, ret); SWLOG_ERROR("%s : All MTLS certs are failed. Falling back to state red.\n", __FUNCTION__); - checkAndEnterStateRed(CURL_MTLS_LOCAL_CERTPROBLEM, disableStatsUpdate); + if (checkAndEnterStateRed(CURL_MTLS_LOCAL_CERTPROBLEM, disableStatsUpdate) != 0) { + SWLOG_ERROR("%s : State red entered due to MTLS cert problem\n", __FUNCTION__); + } return curl_ret_code; } else if (ret == STATE_RED_CERT_FETCH_FAILURE) { SWLOG_ERROR("%s : State red cert failed.\n", __FUNCTION__); @@ -1024,9 +1068,14 @@ int downloadFile( (server_type == HTTP_SSR_DIRECT) ? setDwnlState(RDKV_FWDNLD_DOWNLOAD_EXIT) : setDwnlState(RDKV_XCONF_FWDNLD_DOWNLOAD_EXIT); doStopDownload(*curl); *curl = NULL; - if (*force_exit == 1 && (curl_ret_code == 23)) { - uninitialize(INITIAL_VALIDATION_SUCCESS); - exit(1); + if (force_exit != NULL && *force_exit == 1 && (curl_ret_code == 23)) { + SWLOG_INFO("%s : Force exit (state_red path, curl error 23)\n", __FUNCTION__); +#ifdef LIBRDKCERTSELECTOR + if (thisCertSel != NULL) { + rdkcertselector_free(&thisCertSel); + } +#endif + return RDKV_UPGRADE_ERROR_FORCE_EXIT; } } } @@ -1046,9 +1095,14 @@ int downloadFile( (server_type == HTTP_SSR_DIRECT) ? setDwnlState(RDKV_FWDNLD_DOWNLOAD_EXIT) : setDwnlState(RDKV_XCONF_FWDNLD_DOWNLOAD_EXIT); doStopDownload(*curl); *curl = NULL; - if (*force_exit == 1 && (curl_ret_code == 23)) { - uninitialize(INITIAL_VALIDATION_SUCCESS); - exit(1); + if (force_exit != NULL && *force_exit == 1 && (curl_ret_code == 23)) { + SWLOG_INFO("%s : Force exit (mTLS enabled path, curl error 23)\n", __FUNCTION__); +#ifdef LIBRDKCERTSELECTOR + if (thisCertSel != NULL) { + rdkcertselector_free(&thisCertSel); + } +#endif + return RDKV_UPGRADE_ERROR_FORCE_EXIT; } } } @@ -1068,9 +1122,14 @@ int downloadFile( (server_type == HTTP_SSR_DIRECT) ? setDwnlState(RDKV_FWDNLD_DOWNLOAD_EXIT) : setDwnlState(RDKV_XCONF_FWDNLD_DOWNLOAD_EXIT); doStopDownload(*curl); *curl = NULL; - if (*force_exit == 1 && (curl_ret_code == 23)) { - uninitialize(INITIAL_VALIDATION_SUCCESS); - exit(1); + if (force_exit != NULL && *force_exit == 1 && (curl_ret_code == 23)) { + SWLOG_INFO("%s : Force exit (mTLS disabled path, curl error 23)\n", __FUNCTION__); +#ifdef LIBRDKCERTSELECTOR + if (thisCertSel != NULL) { + rdkcertselector_free(&thisCertSel); + } +#endif + return RDKV_UPGRADE_ERROR_FORCE_EXIT; } } } @@ -1248,6 +1307,12 @@ int fallBack( //curl_ret_code = codebigdownloadFile(artifactLocationUrl, localDownloadLocation, httpCode); SWLOG_INFO("%s: calling retryDownload\n", __FUNCTION__ ); curl_ret_code = retryDownload(context, CB_RETRY_COUNT, 10, httpCode, curl); + if (curl_ret_code == RDKV_UPGRADE_ERROR_THROTTLE_ZERO ){ + return RDKV_UPGRADE_ERROR_THROTTLE_ZERO; + } + else if (curl_ret_code == RDKV_UPGRADE_ERROR_FORCE_EXIT) { + return RDKV_UPGRADE_ERROR_FORCE_EXIT; + } if ((curl_ret_code == CURL_SUCCESS) && (*httpCode == HTTP_SUCCESS || *httpCode == HTTP_CHUNK_SUCCESS)) { SWLOG_INFO("%s : Codebig Image upgrade Success: ret=%d httpcode=%d\n", __FUNCTION__, curl_ret_code, *httpCode); if ((filePresentCheck(DIRECT_BLOCK_FILENAME)) != 0) { diff --git a/test/functional-tests/tests/test_dbus_CheckForUpdate.py b/test/functional-tests/tests/test_dbus_CheckForUpdate.py index 264d9970..df941be9 100644 --- a/test/functional-tests/tests/test_dbus_CheckForUpdate.py +++ b/test/functional-tests/tests/test_dbus_CheckForUpdate.py @@ -22,9 +22,8 @@ import time import os import json -from pathlib import Path -from rdkfw_test_helper import * +import rdkfw_test_helper # D-Bus Configuration DBUS_SERVICE_NAME = "org.rdkfwupdater.Service" @@ -511,7 +510,7 @@ def test_checkupdate_malformed_cache(): # Call CheckForUpdate with timeout (daemon might hang on malformed JSON) try: - response = api.CheckForUpdate(handler_id) + api.CheckForUpdate(handler_id) except dbus.exceptions.DBusException: pass # ignore timeout for this test assert wait_for_log_line( diff --git a/test/functional-tests/tests/test_dbus_DownloadFirmware.py b/test/functional-tests/tests/test_dbus_DownloadFirmware.py index d034812f..732f679f 100644 --- a/test/functional-tests/tests/test_dbus_DownloadFirmware.py +++ b/test/functional-tests/tests/test_dbus_DownloadFirmware.py @@ -24,7 +24,7 @@ import pytest from pathlib import Path -from rdkfw_test_helper import * +import rdkfw_test_helper # D-Bus Configuration DBUS_SERVICE_NAME = "org.rdkfwupdater.Service" @@ -519,12 +519,13 @@ def test_download_delay(): start_time = time.time() # Provide URL explicitly (daemon reads delay from cache, but URL still required) - result = api.DownloadFirmware( + download_result = api.DownloadFirmware( str(handler_id), "ABCD_PDRI_firmware_test.bin", download_url, # URL must be provided (not empty) "PCI" ) + print(f"DownloadFirmware returned: {download_result}") # Wait for delay + download time.sleep(75) @@ -656,7 +657,7 @@ def test_connection_timeout_with_retry(): # Unresolvable hostname - will timeout unresolvable_url = "https://unmockxconf:50052/featureControl/firmware.bin" - result = api.DownloadFirmware( + api.DownloadFirmware( handler_id, "ABCD_PDRI_img.bin", unresolvable_url, @@ -807,11 +808,9 @@ def test_pdri_firmware_type(): # The key validation is D-Bus API acceptance above if wait_for_file("/opt/CDL/ABCD_PDRI_test.bin", timeout=15): print("[PASS] PDRI firmware file created: /opt/CDL/ABCD_PDRI_test.bin") - file_created = True else: print("[INFO] File not created within timeout (may be expected with cert selector)") print("[INFO] D-Bus API correctly accepted PDRI type - primary test objective met") - file_created = False # Verify status file updated (if not skipped by disableStatsUpdate) if os.path.exists(STATUS_FILE): @@ -862,8 +861,8 @@ def test_pdri_firmware_type(): with open(flash_file, 'r') as f: content = f.read() print(f"[DEBUG] Content of {flash_file}: {content[:200]}") - except: - print(f"[DEBUG] {flash_file} exists but cannot read (may be empty)") + except Exception as exc: + print(f"[DEBUG] {flash_file} exists but cannot read (may be empty). Error: {exc}") assert not found_flash_files, \ f"Flash should NOT occur for D-Bus DownloadFirmware (download_only=1). Found: {found_flash_files}" @@ -911,13 +910,10 @@ def test_peripheral_firmware_type(): # Check if file was created (may or may not succeed depending on cert selector) # This is informational - the key validation is API acceptance above - peripheral_found = False if os.path.exists("/opt/CDL/peripheral_fw.bin"): print("[PASS] PERIPHERAL firmware downloaded to /opt/CDL") - peripheral_found = True elif os.path.exists("/tmp/peripheral_fw.bin"): print("[PASS] PERIPHERAL firmware downloaded to /tmp") - peripheral_found = True else: print("[INFO] File not created (expected with cert selector in test environment)") print("[INFO] D-Bus API correctly accepted PERIPHERAL type - test objective met") @@ -961,7 +957,7 @@ def test_progress_file_creation(): handler_id = str(result[0] if isinstance(result, tuple) else result) assert int(handler_id) > 0, "Registration failed" - result = api.DownloadFirmware( + api.DownloadFirmware( handler_id, "test_progress.bin", "https://mockxconf:50052/firmwareupdate/getfirmwaredata/test_progress.bin", @@ -984,7 +980,7 @@ def test_progress_file_creation(): progress_content = f.read() if progress_content.strip(): print(f"[INFO] Progress content: {progress_content[:100]}") - except: + except Exception: pass else: # Progress file might be created briefly and removed after completion diff --git a/test/functional-tests/tests/test_dbus_UpdateFirmware.py b/test/functional-tests/tests/test_dbus_UpdateFirmware.py index 3abfdd07..19cd6dac 100644 --- a/test/functional-tests/tests/test_dbus_UpdateFirmware.py +++ b/test/functional-tests/tests/test_dbus_UpdateFirmware.py @@ -21,10 +21,7 @@ import subprocess import time import os -import signal -from pathlib import Path from threading import Thread, Event -import json import pytest from rdkfw_test_helper import * @@ -261,14 +258,14 @@ def test_update_pci_firmware_success(): VERIFY: - Returns RDKFW_UPDATE_SUCCESS """ - proc = start_daemon() + start_daemon() initial_rdkfw_setup() write_device_prop() cleanup_daemon_files() # Create mock firmware file firmware_name = "ABCD_PCI_test.bin" - firmware_path = create_mock_firmware_file(firmware_name) + create_mock_firmware_file(firmware_name) # Create mock flash script (success) create_mock_flash_script(return_code=0) diff --git a/unittest/Makefile.am b/unittest/Makefile.am index 43548208..29defb78 100644 --- a/unittest/Makefile.am +++ b/unittest/Makefile.am @@ -19,7 +19,6 @@ AUTOMAKE_OPTIONS = subdir-objects # Define the program name and the source files bin_PROGRAMS = rdkfw_device_status_gtest rdkfw_deviceutils_gtest rdkfw_main_gtest rdkfw_interface_gtest rdkfwupdatemgr_main_flow_gtest rdkFwupdateMgr_handlers_gtest dbus_handlers_gtest - #bin_PROGRAMS = rdkfw_device_status_gtest rdkfw_deviceutils_gtest rdkfw_main_gtest rdkfw_interface_gtest dbus_handlers_gtest # Define the include directories # NOTE: We explicitly use -I. to prioritize local test headers over system headers @@ -78,6 +77,10 @@ rdkfw_interface_gtest_SOURCES = fwdl_interface_gtest.cpp \ rdkFwupdateMgr_handlers_gtest_SOURCES = rdkFwupdateMgr_handlers_gtest.cpp \ ./mocks/rdkFwupdateMgr_mock.cpp \ ../src/dbus/rdkFwupdateMgr_handlers.c \ + ../src/rdkv_upgrade.c \ + ../src/chunk.c \ + ../src/device_status_helper.c \ + ../src/download_status_helper.c \ ../src/json_process.c \ deviceutils/json_parse.c # Note: json_process.c is mocked in rdkFwupdateMgr_mock.cpp to control XConf parsing behavior @@ -113,19 +116,23 @@ rdkFwupdateMgr_handlers_gtest_CFLAGS = $(COMMON_CXXFLAGS) # rdkfwupdatemgr_main_flow_gtest: Tests rdkFwupdateMgr.c main flow functions # Covers: getTriggerType, handle_signal, prevCurUpdateInfo, initialValidation gaps, main() logic -# Uses mocks ONLY - no real rfcinterface.c or iarmInterface.c to avoid multiple definition errors +# Uses mocks ONLY - no real implementation files that are mocked in miscellaneous_mock.cpp # Key decisions: # 1. REMOVED rfcinterface.c - mocked in miscellaneous_mock.cpp (getRFCSettings, isIncremetalCDLEnable, etc.) # 2. REMOVED iarmInterface.c - mocked in miscellaneous_mock.cpp (init_event_handler, eventManager, etc.) # 3. REMOVED rbusInterface.c and rbus_mock.c - not needed for main flow tests -# 4. KEPT rdkFwupdateMgr.c - core file under test -# 5. KEPT deviceutils/* - device utility functions with deviceutils_mock.cpp -# 6. KEPT json_process.c - core JSON parsing +# 4. REMOVED rdkv_upgrade.c - mocked in miscellaneous_mock.cpp (rdkv_upgrade_request, downloadFile, etc.) +# 5. REMOVED chunk.c - part of rdkv_upgrade.c functionality, mocked +# 6. REMOVED device_status_helper.c - mocked in miscellaneous_mock.cpp (checkAndEnterStateRed, isDnsResolve, etc.) +# 7. REMOVED download_status_helper.c - mocked in miscellaneous_mock.cpp (updateFWDownloadStatus, notifyDwnlStatus) +# 8. KEPT rdkFwupdateMgr.c - core file under test +# 9. KEPT deviceutils/* - device utility functions with deviceutils_mock.cpp +# 10. KEPT json_process.c - core JSON parsing rdkfwupdatemgr_main_flow_gtest_SOURCES = rdkfwupdatemgr_main_flow_gtest.cpp \ ../src/rdkFwupdateMgr.c \ + ../src/json_process.c \ ../src/deviceutils/device_api.c \ ../src/deviceutils/deviceutils.c \ - ../src/json_process.c \ deviceutils/json_parse.c \ miscellaneous_mock.cpp \ ./mocks/deviceutils_mock.cpp diff --git a/unittest/miscellaneous_mock.cpp b/unittest/miscellaneous_mock.cpp index f94d7ce1..06322777 100644 --- a/unittest/miscellaneous_mock.cpp +++ b/unittest/miscellaneous_mock.cpp @@ -75,7 +75,7 @@ class MockExternal { MOCK_METHOD(void, logMilestone, (const char*), ()); MOCK_METHOD(int, eraseFolderExcePramaFile, (const char*, const char*, const char*), ()); MOCK_METHOD(int, doCurlPutRequest, (void*, FileDwnl_t*, char*, int*), ()); - MOCK_METHOD(void, checkAndEnterStateRed, (int, const char*), ()); + MOCK_METHOD(int, checkAndEnterStateRed, (int, const char*), ()); MOCK_METHOD(int, getRFCSettings, (Rfc_t*), ()); MOCK_METHOD(void, eventManager, (const char*, const char*), ()); MOCK_METHOD(int, updateFWDownloadStatus, (struct FWDownloadStatus*, const char*), ()); @@ -255,11 +255,11 @@ extern "C" { return global_mockexternal_ptr->doCurlPutRequest(in_curl, pfile_dwnl, jsonrpc_auth_token, out_httpCode); } - void checkAndEnterStateRed(int curlret, const char *) { + int checkAndEnterStateRed(int curlret, const char *) { if (global_mockexternal_ptr == nullptr) { - return; // Return default value if global_mockexternal_ptr is NULL + return 0; // Return success if global_mockexternal_ptr is NULL } - global_mockexternal_ptr->checkAndEnterStateRed(curlret, ""); + return global_mockexternal_ptr->checkAndEnterStateRed(curlret, ""); } int getRFCSettings(Rfc_t *rfc_list) { @@ -617,8 +617,34 @@ extern "C" { const char* getRFCErrorString(int code) { return "RFC_SUCCESS"; } -} +#ifndef GTEST_BASIC + // =========================================================================== + // Additional stubs for functions referenced by linked source files + // Only compiled when NOT building GTEST_BASIC (rdkfw_main_gtest) + // rdkfw_main_gtest includes real flash.c, device_status_helper.c, download_status_helper.c + // Other tests (rdkfwupdatemgr_main_flow_gtest) use these mocks instead + // =========================================================================== + + // Stub for flashImage (referenced by rdkv_upgrade.c, defined in flash.c) + // Signature: int flashImage(const char *server_url, const char *upgrade_file, const char *reboot_flag, const char *proto, int upgrade_type, const char *maint, int trigger_type) + int flashImage(const char *server_url, const char *upgrade_file, const char *reboot_flag, const char *proto, int upgrade_type, const char *maint, int trigger_type) { + return 0; // Success + } + + // Stub for isConnectedToInternet (referenced by device_status_helper.c) + // Signature: bool isConnectedToInternet(void) + bool isConnectedToInternet(void) { + return true; // Connected + } + + // Stub for write_RFCProperty (referenced by download_status_helper.c, defined in rfcinterface.c) + // Signature: int write_RFCProperty(char* type, const char* key, const char *data, RFCVALDATATYPE datatype) + int write_RFCProperty(char* type, const char* key, const char *data, RFCVALDATATYPE datatype) { + return 0; // Success + } +#endif // !GTEST_BASIC +} class MockFunctionsInternal { public: MOCK_METHOD(void, RunCommand, (int command, void* arg1, char* jsondata, int size)); diff --git a/unittest/test_dbus_fake.cpp b/unittest/test_dbus_fake.cpp index a94c517d..98664bb5 100755 --- a/unittest/test_dbus_fake.cpp +++ b/unittest/test_dbus_fake.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2023 Comcast Cable Communications Management, LLC + * Copyright 2025 Comcast Cable Communications Management, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.