diff --git a/.github/workflows/L2-tests.yml b/.github/workflows/L2-tests.yml index d9650e42..83828884 100644 --- a/.github/workflows/L2-tests.yml +++ b/.github/workflows/L2-tests.yml @@ -51,15 +51,11 @@ jobs: - name: Start mock-xconf service run: | - docker run -d --name mockxconf -p 50050:50050 -p 50051:50051 -p 50052:50052 -v ${{ github.workspace }}:/mnt/L2_CONTAINER_SHARED_VOLUME ghcr.io/rdkcentral/docker-device-mgt-service-test/mockxconf:latest + docker run -d --name mockxconf -p 50050:50050 -p 50051:50051 -p 50052:50052 -e ENABLE_MTLS=true -v ${{ github.workspace }}:/mnt/L2_CONTAINER_SHARED_VOLUME ghcr.io/rdkcentral/docker-device-mgt-service-test/mockxconf:latest - - name: Copy xconf-dcm-response2 json to mockxconf service - run: | - docker cp ${{ github.workspace }}/test/test-artifacts/mockxconf/xconf-dcm-response2.json mockxconf:/etc/xconf/xconf-dcm-response2.json - - name: Start l2-container service run: | - docker run -d --name native-platform --link mockxconf -v ${{ github.workspace }}:/mnt/L2_CONTAINER_SHARED_VOLUME ghcr.io/rdkcentral/docker-device-mgt-service-test/native-platform:latest + docker run -d --name native-platform --link mockxconf -e ENABLE_MTLS=true -v ${{ github.workspace }}:/mnt/L2_CONTAINER_SHARED_VOLUME ghcr.io/rdkcentral/docker-device-mgt-service-test/native-platform:latest - name: Enter Inside Platform native container and run L2 Test run: | diff --git a/build_inside_container.sh b/build_inside_container.sh index 72467960..f962ea15 100755 --- a/build_inside_container.sh +++ b/build_inside_container.sh @@ -27,8 +27,8 @@ autoreconf --install # FLags to print compiler warnings DEBUG_CFLAGS="-Wall -Werror -Wextra" -export CFLAGS=" ${DEBUG_CFLAGS} -I${INSTALL_DIR}/include/rtmessage -I${INSTALL_DIR}/include/msgpack -I${INSTALL_DIR}/include/rbus -I${INSTALL_DIR}/include -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/local/include -DFEATURE_SUPPORT_WEBCONFIG -DRDK_LOGGER -DPERSIST_LOG_MON_REF -DDCMAGENT" +export CFLAGS=" ${DEBUG_CFLAGS} -I${INSTALL_DIR}/include/rtmessage -I${INSTALL_DIR}/include/msgpack -I${INSTALL_DIR}/include/rbus -I${INSTALL_DIR}/include -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/local/include -DFEATURE_SUPPORT_WEBCONFIG -DRDK_LOGGER -DPERSIST_LOG_MON_REF -DDCMAGENT -DENABLE_MTLS" export LDFLAGS="-L/usr/lib/x86_64-linux-gnu -lglib-2.0" -./configure --prefix=${INSTALL_DIR} && make && make install +./configure --prefix=${INSTALL_DIR} --enable-rdkcertselector=yes && make && make install diff --git a/source/docs/protocol/curl_usage_architecture.md b/source/docs/protocol/curl_usage_architecture.md new file mode 100644 index 00000000..a6b35bea --- /dev/null +++ b/source/docs/protocol/curl_usage_architecture.md @@ -0,0 +1,426 @@ +# CURL Usage Architecture Documentation + +## Overview + +This document describes the architecture of the CURL-based HTTP communication subsystem in the Telemetry component. The system implements a connection pooling mechanism for efficient HTTP operations with thread-safe access and mTLS support. + +## Components + +### 1. **multicurlinterface.c** - Connection Pool Layer +The core component implementing a thread-safe connection pool for CURL handles. + +**Key Features:** +- Connection pooling with configurable pool size (1-5 connections) +- Thread-safe handle acquisition and release +- Support for both GET and POST operations +- mTLS certificate management integration +- Keep-alive and connection reuse optimization + +**Key Data Structures:** +```c +typedef struct { + CURL *easy_handle; // Single CURL handle + bool handle_available; // Availability flag + rdkcertselector_h cert_selector; // Certificate selector (LIBRDKCERTSEL_BUILD) + rdkcertselector_h rcvry_cert_selector; // Recovery cert selector +} http_pool_entry_t; +``` + +**Global State:** +- `pool_entries`: Array of pool entries +- `pool_mutex`: Protects pool state (statically initialized) +- `pool_cond`: Condition variable for handle availability +- `pool_initialized`: Initialization flag +- `active_requests`: Counter for in-use handles +- `pool_size`: Configured pool size + +### 2. **curlinterface.c** - Wrapper Layer +Simple wrapper providing backward compatibility and convenience functions. + +**Key Functions:** +- `sendReportOverHTTP()`: Sends telemetry reports via HTTP POST +- `sendCachedReportsOverHTTP()`: Sends multiple cached reports sequentially + +### 3. **xconfclient.c** - Configuration Client +Manages fetching configuration from XConf server with threading and retry logic. + +**Key Features:** +- Background thread for configuration fetching +- Retry logic with exponential backoff +- Multiple condition variables for flow control +- DCM integration (when DCMAGENT is defined) + +**Synchronization Primitives:** +- `xcMutex` & `xcCond`: Controls retry timing +- `xcThreadMutex` & `xcThreadCond`: Controls thread lifecycle +- `xcrThread`: Configuration fetching thread +- `dcmThread`: DCM notification thread (DCMAGENT) + +## Architecture Diagrams + +### Component Architecture + +### HTTP GET Flow + +```mermaid +sequenceDiagram + participant XC as XConf Client + participant MCI as multicurlinterface + participant POOL as Connection Pool + participant CURL as libcurl + participant CERT as Cert Selector + + XC->>MCI: http_pool_get(url, response_data) + MCI->>MCI: acquire_pool_handle(&easy, &idx) + + alt Handle Available + MCI->>POOL: Lock pool_mutex + POOL-->>MCI: Return handle & index + else All Handles Busy + MCI->>MCI: pthread_cond_wait(pool_cond) + Note over MCI: Wait until handle available + POOL-->>MCI: Return handle & index + end + + MCI->>MCI: Clear POST options + MCI->>MCI: Set GET options (URL, callback) + + alt mTLS Enabled + MCI->>CERT: rdkcertselector_getCertificate() + CERT-->>MCI: Certificate URI + MCI->>CURL: Set CURLOPT_SSLCERT + + loop Retry on Certificate Error + MCI->>CURL: curl_easy_perform() + CURL-->>MCI: CURLcode result + alt Certificate Error + MCI->>CERT: rdkcertselector_setCurlStatus(TRY_ANOTHER) + CERT-->>MCI: Next certificate + else Success or Other Error + MCI->>MCI: Break loop + end + end + else mTLS Disabled + MCI->>CURL: curl_easy_perform() + CURL-->>MCI: CURLcode result + end + + MCI->>MCI: release_pool_handle(idx) + MCI->>POOL: Mark handle available + MCI->>POOL: pthread_cond_signal(pool_cond) + MCI-->>XC: Return T2ERROR status +``` + +### HTTP POST Flow + +```mermaid +sequenceDiagram + participant APP as Application + participant CI as curlinterface + participant MCI as multicurlinterface + participant POOL as Connection Pool + participant CURL as libcurl + + APP->>CI: sendReportOverHTTP(url, payload) + CI->>MCI: http_pool_post(url, payload) + + MCI->>MCI: acquire_pool_handle(&easy, &idx) + + rect rgb(200, 220, 240) + Note over MCI,POOL: Critical Section: pool_mutex locked + POOL-->>MCI: Handle acquired + end + + MCI->>MCI: Set POST options + MCI->>CURL: CURLOPT_POSTFIELDS = payload + MCI->>CURL: CURLOPT_HTTPHEADER = post_headers + + alt mTLS with Certificates + MCI->>MCI: Configure SSL certificates + MCI->>CURL: curl_easy_perform() + else Standard POST + MCI->>CURL: curl_easy_perform() + end + + CURL-->>MCI: HTTP Response Code + + MCI->>MCI: release_pool_handle(idx) + + rect rgb(200, 220, 240) + Note over MCI,POOL: Critical Section: pool_mutex locked + MCI->>POOL: Mark handle available + MCI->>POOL: pthread_cond_signal(pool_cond) + end + + MCI-->>CI: T2ERROR status + CI-->>APP: T2ERROR status +``` + +### XConf Client Thread Flow + +```mermaid +sequenceDiagram + participant MAIN as Main Thread + participant XCT as XConf Thread + participant MCI as multicurlinterface + participant MUTEX as xcThreadMutex + + MAIN->>XCT: initXConfClient() + activate XCT + + XCT->>MUTEX: pthread_mutex_lock(&xcThreadMutex) + + loop While isXconfInit + XCT->>XCT: getRemoteConfigURL(&configURL) + + alt URL Not Ready + XCT->>XCT: Lock xcMutex + XCT->>XCT: pthread_cond_timedwait(xcCond, RFC_RETRY_TIMEOUT) + Note over XCT: Wait 60 seconds + XCT->>XCT: Unlock xcMutex + else URL Ready + XCT->>XCT: Break loop + end + + XCT->>MCI: fetchRemoteConfiguration(url, &data) + MCI-->>XCT: Configuration data + + alt Config Fetch Success + XCT->>XCT: Process configuration + XCT->>XCT: stopFetchRemoteConfiguration = true + else Config Fetch Failed + XCT->>XCT: Increment retry count + XCT->>XCT: pthread_cond_timedwait(xcCond, XCONF_RETRY_TIMEOUT) + Note over XCT: Wait 180 seconds + end + + alt Done Fetching + XCT->>XCT: pthread_cond_wait(xcThreadCond) + Note over XCT: Wait for restart signal + end + end + + MAIN->>XCT: uninitXConfClient() + MAIN->>MUTEX: pthread_cond_signal(xcThreadCond) + XCT->>MUTEX: pthread_mutex_unlock(&xcThreadMutex) + deactivate XCT + MAIN->>XCT: pthread_join(xcrThread) +``` + +### Connection Pool Initialization + +```mermaid +stateDiagram-v2 + [*] --> Uninitialized: pool_initialized = false + + Uninitialized --> Initializing: init_connection_pool() + + Initializing --> CheckInitialized: Lock pool_mutex + CheckInitialized --> AlreadyInitialized: pool_initialized == true + CheckInitialized --> ConfigureSize: pool_initialized == false + + ConfigureSize --> AllocatePool: Read T2_CONNECTION_POOL_SIZE + AllocatePool --> AllocateHandles: calloc(pool_size) + + AllocateHandles --> ConfigureHandle1: Create CURL handles + ConfigureHandle1 --> ConfigureHandle2: Set CURLOPT_* options + ConfigureHandle2 --> InitCertSelector: Configure keepalive, SSL + + InitCertSelector --> SetupHeaders: rdkcertselector_init() + SetupHeaders --> MarkInitialized: curl_slist_append() + + MarkInitialized --> Ready: pool_initialized = true + AlreadyInitialized --> Ready + + Ready --> [*]: Unlock pool_mutex + + Ready --> Cleanup: http_pool_cleanup() + Cleanup --> CleanupWait: Lock pool_mutex + CleanupWait --> WaitForActive: active_requests > 0 + WaitForActive --> BroadcastShutdown: pthread_cond_broadcast() + BroadcastShutdown --> FreeResources: Free handles & cert selectors + FreeResources --> [*]: pool_initialized = false +``` + +### Handle Acquisition & Release Flow + +```mermaid +flowchart TD + Start([acquire_pool_handle]) --> Lock[Lock pool_mutex] + Lock --> CheckInit{pool_initialized?} + + CheckInit -->|No| InitPool[Call init_connection_pool] + InitPool --> CheckInitFail{Init Success?} + CheckInitFail -->|No| ReturnError1[Return T2ERROR_FAILURE] + CheckInitFail -->|Yes| WaitLoop + + CheckInit -->|Yes| WaitLoop[Enter while loop] + + WaitLoop --> CheckAvail{Handle available?} + + CheckAvail -->|Yes| FindHandle[Search for available handle] + FindHandle --> MarkBusy[Mark handle busy] + MarkBusy --> IncrActive[active_requests++] + IncrActive --> Unlock1[Unlock pool_mutex] + Unlock1 --> ReturnSuccess[Return T2ERROR_SUCCESS] + + CheckAvail -->|No| CheckShutdown{pool_initialized?} + CheckShutdown -->|No| Unlock2[Unlock pool_mutex] + Unlock2 --> ReturnError2[Return T2ERROR_FAILURE] + + CheckShutdown -->|Yes| CondWait[pthread_cond_wait] + CondWait --> WaitLoop + + ReturnSuccess --> End1([End]) + ReturnError1 --> End2([End]) + ReturnError2 --> End3([End]) + + Release([release_pool_handle]) --> LockR[Lock pool_mutex] + LockR --> CheckIdx{Valid index?} + CheckIdx -->|Yes| MarkAvail[Mark handle available] + MarkAvail --> DecrActive[active_requests--] + DecrActive --> Signal[pthread_cond_signal] + Signal --> UnlockR[Unlock pool_mutex] + + CheckIdx -->|No| LogError[Log error] + LogError --> UnlockR + + UnlockR --> EndR([End]) +``` + +## Synchronization Mechanisms + +## Critical Race Conditions & Deadlock Risks + +### 🔴 HIGH RISK: Pool Cleanup During Active Requests + +**Location:** `http_pool_cleanup()` in multicurlinterface.c + +**Issue:** +```c +while(active_requests > 0) { + pthread_cond_wait(&pool_cond, &pool_mutex); +} +``` + +**Risk:** If a thread is blocked in `acquire_pool_handle()` waiting for an available handle when cleanup starts: +1. Cleanup sets `pool_initialized = false` +2. Waiting thread wakes up, sees `!pool_initialized`, exits with error +3. If the thread had incremented `active_requests` before waiting, the counter may be incorrect +4. Cleanup may wait forever if signals are missed + +**Mitigation Required:** +- Use `pthread_cond_broadcast()` instead of waiting +- Add shutdown flag checked in acquire path +- Ensure proper signal on all return paths + + + +### 🟡 MEDIUM RISK: Static Initialization Race + +**Location:** `init_connection_pool()` in multicurlinterface.c + +**Issue:** +```c +static pthread_mutex_t pool_mutex = PTHREAD_MUTEX_INITIALIZER; +static bool pool_initialized = false; + +// Later in code: +pthread_mutex_lock(&pool_mutex); +if(pool_initialized) { + pthread_mutex_unlock(&pool_mutex); + return T2ERROR_SUCCESS; +} +``` + +**Risk:** Although `PTHREAD_MUTEX_INITIALIZER` provides static initialization, there's a check-then-act pattern that could theoretically have issues if multiple threads call `init_connection_pool()` simultaneously before `pool_initialized` is set. + +**Current Protection:** The lock is acquired before checking, so this is actually safe. However, the `acquire_pool_handle()` function calls `init_connection_pool()` while holding `pool_mutex`, which is correct but creates nested critical sections. + +**Status:** Currently protected, but recursive pattern is fragile. + +### 🟡 MEDIUM RISK: Condition Variable Missed Signals + +**Location:** Various locations using `pthread_cond_wait()` + + +### 🟢 LOW RISK: Active Request Counter + +**Location:** `acquire_pool_handle()` and `release_pool_handle()` in multicurlinterface.c + +**Issue:** +```c +active_requests++; // In acquire +active_requests--; // In release +``` + +**Risk:** Counter could become inaccurate if: +- Release called without acquire +- Error path doesn't decrement +- Signal handling interrupts + +**Current Protection:** +- All paths protected by `pool_mutex` +- index validation in release prevents double-release + +**Status:** Well-protected but no assertions to detect corruption. + + +## Thread Safety Analysis + +### Thread-Safe Operations +✅ `http_pool_get()` - Protected by pool_mutex +✅ `http_pool_post()` - Protected by pool_mutex +✅ `init_connection_pool()` - Double-check locking with mutex +✅ `http_pool_cleanup()` - Synchronized with broadcast +✅ Handle acquisition/release - Mutex + condition variable + +### Non-Thread-Safe (By Design) +⚠️ Individual CURL handle operations - Assumes single thread per handle (correct) +⚠️ Certificate selector callbacks - Called within handle's execution context (correct) + + +## Recommended Improvements + +### 1. Add Timeout to Acquire - Prevent deadlocks + +```c +T2ERROR acquire_pool_handle_timeout(CURL **easy, int *idx, int timeout_ms); +``` + +### 2. Separate Cert Selectors from Pool (Not needed now for pool sie 1) + +Consider managing cert selectors independently to reduce coupling and simplify state management. + + + +## Testing Recommendations + +### Unit Tests Needed +1. ✅ Pool initialization and cleanup +2. ✅ Concurrent acquire/release from multiple threads +3. ✅ Pool exhaustion behavior (all handles busy) +4. ⚠️ Cleanup during active requests +5. ⚠️ Signal/wait patterns with multiple threads +6. ⚠️ Lock ordering validation +7. ⚠️ Environment variable edge cases + +### Integration Tests Needed +1. ✅ Full GET/POST operations +2. ⚠️ Certificate rotation scenarios +3. ⚠️ Network failures and retries +4. ⚠️ Graceful shutdown with pending requests +5. ⚠️ Pool size scaling under load + +### Stress Tests Needed +1. High-frequency requests (pool saturation) +2. Long-running requests (blocking pool) simulated by long responding connection +3. Rapid init/cleanup cycles +4. Mixed GET/POST workloads + +--- + +**Document Version:** 1.0 +**Last Updated:** February 24, 2026 +**Author:** Architecture Analysis +**Status:** Initial Analysis diff --git a/source/protocol/http/Makefile.am b/source/protocol/http/Makefile.am index 5590a1dc..460bfa2a 100644 --- a/source/protocol/http/Makefile.am +++ b/source/protocol/http/Makefile.am @@ -24,7 +24,7 @@ libhttp_la_SOURCES = curlinterface.c multicurlinterface.c libhttp_la_LDFLAGS = -shared -fPIC -lcurl if IS_LIBRDKCERTSEL_ENABLED libhttp_la_CFLAGS = $(LIBRDKCERTSEL_FLAG) -libhttp_la_LDFLAGS += -lRdkCertSelector +libhttp_la_LDFLAGS += -lRdkCertSelector -lrdkconfig endif libhttp_la_CPPFLAGS = -fPIC -I${PKG_CONFIG_SYSROOT_DIR}$(includedir)/dbus-1.0 \ -I${PKG_CONFIG_SYSROOT_DIR}$(libdir)/dbus-1.0/include \ diff --git a/source/protocol/http/curlinterface.c b/source/protocol/http/curlinterface.c index 718132a8..695fa828 100644 --- a/source/protocol/http/curlinterface.c +++ b/source/protocol/http/curlinterface.c @@ -42,9 +42,11 @@ #include "rdkcertselector.h" #define FILESCHEME "file://" #endif +#ifndef LIBRDKCERTSEL_BUILD #ifdef LIBRDKCONFIG_BUILD #include "rdkconfig.h" #endif +#endif #ifdef GTEST_ENABLE #define curl_easy_setopt curl_easy_setopt_mock #define curl_easy_getinfo curl_easy_getinfo_mock @@ -82,11 +84,11 @@ T2ERROR sendReportOverHTTP(char *httpUrl, char *payload) if(ret == T2ERROR_SUCCESS) { - T2Info("Report Sent Successfully over HTTP using connection pool\n"); + T2Debug("Report Sent Successfully over HTTP\n"); } else { - T2Error("Failed to send report using connection pool\n"); + T2Error("Failed to send report\n"); } T2Debug("%s --out\n", __FUNCTION__); diff --git a/source/protocol/http/multicurlinterface.c b/source/protocol/http/multicurlinterface.c index 1d7d80e1..2d0113dc 100644 --- a/source/protocol/http/multicurlinterface.c +++ b/source/protocol/http/multicurlinterface.c @@ -32,6 +32,7 @@ #include #include #include +#include #include "multicurlinterface.h" #include "busInterface.h" #include "t2log_wrapper.h" @@ -63,15 +64,17 @@ //Global variables #define IFINTERFACE "erouter0" -#define DEFAULT_POOL_SIZE 2 +#define DEFAULT_POOL_SIZE 1 #define MAX_ALLOWED_POOL_SIZE 5 // Maximum allowed pool size #define MIN_ALLOWED_POOL_SIZE 1 // Minimum allowed pool size #define HTTP_RESPONSE_FILE "/tmp/httpOutput.txt" +#define POOL_ACQUIRE_TIMEOUT_SEC 35 +#define POOL_ACQUIRE_RETRY_MS 100 static bool pool_initialized = false; +static bool pool_shutting_down = false; -// Static initialization of mutex and condition variable to avoid race conditions +// pool_mutex protects pool state and synchronizes access to pool entries static pthread_mutex_t pool_mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t pool_cond = PTHREAD_COND_INITIALIZER; // Single pool entry structure typedef struct @@ -88,6 +91,7 @@ typedef struct static http_pool_entry_t *pool_entries = NULL; // Dynamic array of pool entries static struct curl_slist *post_headers = NULL; // Shared POST headers static int pool_size = 0; // Number of pool entries +static unsigned int active_requests = 0; // Number of in-flight curl_easy_perform() calls #ifdef LIBRDKCERTSEL_BUILD #if defined(ENABLE_RED_RECOVERY_SUPPORT) @@ -103,7 +107,8 @@ static int get_configured_pool_size(void) { int configured_size = DEFAULT_POOL_SIZE; - // Check environment variable T2_CONNECTION_POOL_SIZE + // Check environment variable T2_CONNECTION_POOL_SIZE to have the size + // of the connection pool based on the device type const char *env_size = getenv("T2_CONNECTION_POOL_SIZE"); if (env_size != NULL) { @@ -111,7 +116,7 @@ static int get_configured_pool_size(void) if (env_value >= MIN_ALLOWED_POOL_SIZE && env_value <= MAX_ALLOWED_POOL_SIZE) { configured_size = env_value; - T2Info("Using connection pool size from environment: %d\n", configured_size); + T2Info("Curl connection pool size set from environment: %d\n", configured_size); } else { @@ -134,7 +139,6 @@ static size_t httpGetCallBack(void *responseBuffer, size_t len, size_t nmemb, size_t realsize = len * nmemb; curlResponseData* response = (curlResponseData*) stream; - // FIX: Check for NULL stream to prevent crashes if (!response) { T2Error("httpGetCallBack: NULL stream parameter\n"); @@ -146,8 +150,6 @@ static size_t httpGetCallBack(void *responseBuffer, size_t len, size_t nmemb, if (!ptr) { T2Error("%s:%u , T2:memory realloc failed\n", __func__, __LINE__); - // On realloc failure, abort the transfer to avoid silently dropping data - // Return 0 to signal an error to curl return 0; } response->data = ptr; @@ -216,7 +218,6 @@ T2ERROR init_connection_pool() pthread_mutex_lock(&pool_mutex); - // Check if already initialized if(pool_initialized) { T2Info("Connection pool already initialized with size %d\n", pool_size); @@ -224,15 +225,11 @@ T2ERROR init_connection_pool() return T2ERROR_SUCCESS; } - // Get configured pool size from environment variable + //Get configured pool size from environment variable for low end devices pool_size = get_configured_pool_size(); - - T2Info("Initializing connection pool with size: %d\n", pool_size); - - // Allocate dynamic array of pool entries based on configured pool size + T2Debug("Initializing connection pool with size: %d\n", pool_size); pool_entries = (http_pool_entry_t *)calloc(pool_size, sizeof(http_pool_entry_t)); - // Check if allocation succeeded if (!pool_entries) { T2Error("Failed to allocate memory for connection pool of size %d\n", pool_size); @@ -241,14 +238,14 @@ T2ERROR init_connection_pool() return T2ERROR_FAILURE; } - // Pre-allocate easy handles and initialize pool entries + // Allocating easy handles and initialize pool entries for(int i = 0; i < pool_size; i++) { pool_entries[i].easy_handle = curl_easy_init(); if(pool_entries[i].easy_handle == NULL) { T2Error("%s : Failed to initialize curl handle %d\n", __FUNCTION__, i); - // Cleanup previously initialized handles using helper function + // Cleanup previously initialized handles to prevent memory leak cleanup_curl_handles(); pthread_mutex_unlock(&pool_mutex); return T2ERROR_FAILURE; @@ -283,7 +280,7 @@ T2ERROR init_connection_pool() CURL_SETOPT_CHECK(pool_entries[i].easy_handle, CURLOPT_SSL_VERIFYPEER, 1L); #ifdef LIBRDKCERTSEL_BUILD - // Initialize per-entry certificate selectors + // Initialize certificate selectors for each easy handle bool state_red_enable = false; #if defined(ENABLE_RED_RECOVERY_SUPPORT) state_red_enable = isStateRedEnabled(); @@ -291,7 +288,7 @@ T2ERROR init_connection_pool() if (state_red_enable) { - // Initialize recovery certificate selector for this entry + // Initialize recovery certificate selector while in state red pool_entries[i].rcvry_cert_selector = rdkcertselector_new(NULL, NULL, "RCVRY"); if (pool_entries[i].rcvry_cert_selector == NULL) { @@ -304,11 +301,11 @@ T2ERROR init_connection_pool() { T2Info("%s: Initialized recovery cert selector for entry %d\n", __func__, i); } - pool_entries[i].cert_selector = NULL; // Not used in recovery mode + pool_entries[i].cert_selector = NULL; } else { - // Initialize normal certificate selector for this entry + // Initialize normal certificate selector while not in red state pool_entries[i].cert_selector = rdkcertselector_new(NULL, NULL, "MTLS"); if (pool_entries[i].cert_selector == NULL) { @@ -321,7 +318,7 @@ T2ERROR init_connection_pool() { T2Info("%s: Initialized cert selector for entry %d\n", __func__, i); } - pool_entries[i].rcvry_cert_selector = NULL; // Not used in normal mode + pool_entries[i].rcvry_cert_selector = NULL; } #endif } @@ -352,75 +349,103 @@ T2ERROR init_connection_pool() return T2ERROR_SUCCESS; } -// Helper function to acquire any available handle with waiting + static T2ERROR acquire_pool_handle(CURL **easy, int *idx) { + struct timespec start_time, current_time; + if (clock_gettime(CLOCK_MONOTONIC, &start_time) != 0) + { + T2Error("clock_gettime failed for start_time: %s\n", strerror(errno)); + return T2ERROR_FAILURE; + } + pthread_mutex_lock(&pool_mutex); - if (!pool_initialized) + bool need_init = !pool_initialized && !pool_shutting_down; + pthread_mutex_unlock(&pool_mutex); + + if (need_init) { - pthread_mutex_unlock(&pool_mutex); T2ERROR ret = init_connection_pool(); if(ret != T2ERROR_SUCCESS) { T2Error("Failed to initialize connection pool\n"); return ret; } - pthread_mutex_lock(&pool_mutex); } - // Waits until a handle becomes available or pool is being cleaned up + // Poll for an available handle with a bounded timeout. + // If no handle is available within POOL_ACQUIRE_TIMEOUT_SEC, treat as upload failure. while(1) { - // Check if pool is being shut down - if (!pool_initialized) + pthread_mutex_lock(&pool_mutex); + + if (pool_shutting_down || !pool_initialized) { - T2Info("Pool is being cleaned up, aborting handle acquisition\n"); pthread_mutex_unlock(&pool_mutex); + T2Info("Pool is shutting down or not initialized, aborting handle acquisition\n"); return T2ERROR_FAILURE; } - // Find an available handle + // Try to find an available handle for(int i = 0; i < pool_size; i++) { if(pool_entries[i].handle_available) { - T2Info("acquire_pool_handle ; Available handle = %d (pool size: %d)\n", i, pool_size); + T2Info("Acquired handle = %d (pool size: %d)\n", i, pool_size); *idx = i; pool_entries[i].handle_available = false; *easy = pool_entries[i].easy_handle; + // Guard against overflow + if (active_requests >= (unsigned int)pool_size) + { + T2Error("Warning: active_requests (%u) >= pool_size (%d) before increment\n", active_requests, pool_size); + } + active_requests++; pthread_mutex_unlock(&pool_mutex); return T2ERROR_SUCCESS; } } - // No handle available, wait for one to become free - T2Info("No curl handle available (pool size: %d), waiting for one to become free...\n", pool_size); - pthread_cond_wait(&pool_cond, &pool_mutex); - // After waking up, loop back to check pool_initialized status - // This handles both spurious wakeups and shutdown signals + pthread_mutex_unlock(&pool_mutex); + + // Check elapsed time against timeout + if (clock_gettime(CLOCK_MONOTONIC, ¤t_time) != 0) + { + T2Error("clock_gettime failed for current_time: %s\n", strerror(errno)); + return T2ERROR_FAILURE; + } + long elapsed_sec = current_time.tv_sec - start_time.tv_sec; + + if (elapsed_sec >= POOL_ACQUIRE_TIMEOUT_SEC) + { + T2Error("Timeout waiting for available curl handle after %ld seconds, treating as upload failure\n", elapsed_sec); + return T2ERROR_FAILURE; + } + + // Sleep briefly before retrying + T2Debug("No curl handle available (pool size: %d), retrying in %dms...\n", pool_size, POOL_ACQUIRE_RETRY_MS); + usleep(POOL_ACQUIRE_RETRY_MS * 1000); } } -// Helper function to release handle back to pool static void release_pool_handle(int idx) { pthread_mutex_lock(&pool_mutex); if (idx >= 0 && idx < pool_size) { pool_entries[idx].handle_available = true; - pthread_cond_signal(&pool_cond); // Signal waiting threads - T2Info("release_pool_handle ; Released handle = %d (pool size: %d)\n", idx, pool_size); + active_requests--; + T2Info("Released curl handle = %d, active_requests = %d\n", idx, active_requests); } else { - T2Error("release_pool_handle ; Invalid handle index = %d (pool size: %d)\n", idx, pool_size); + T2Error("Invalid curl handle index = %d (pool size: %d)\n", idx, pool_size); } pthread_mutex_unlock(&pool_mutex); } #if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER) -// Function to configure WAN interface for CURL handle static void configure_wan_interface(CURL *easy) { T2Debug("%s ++in\n", __FUNCTION__); @@ -453,7 +478,7 @@ static void configure_wan_interface(CURL *easy) } #endif -// GET API - Updated to use shared pool + T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_output) { T2Debug("%s ++in\n", __FUNCTION__); @@ -464,7 +489,7 @@ T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_ou return T2ERROR_FAILURE; } - T2Info("%s ; GET url = %s\n", __FUNCTION__, url); + T2Info("GET url = %s\n", url); CURL *easy; int idx = -1; @@ -477,13 +502,11 @@ T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_ou } // Clear any POST-specific settings so that the handle can be used for GET operations - CURL_SETOPT_CHECK(easy, CURLOPT_CUSTOMREQUEST, NULL); + // curl_easy_perform ends up in crash when POST specific options are not cleared CURL_SETOPT_CHECK(easy, CURLOPT_POSTFIELDS, NULL); CURL_SETOPT_CHECK(easy, CURLOPT_POSTFIELDSIZE, 0L); CURL_SETOPT_CHECK(easy, CURLOPT_HTTPHEADER, NULL); - T2Debug("http_pool_get using handle %d\n", idx); - // Allocate response buffer locally for GET requests only curlResponseData* response = (curlResponseData *) malloc(sizeof(curlResponseData)); if (!response) @@ -565,7 +588,7 @@ T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_ou CURL_SETOPT_CHECK_STR(easy, CURLOPT_KEYPASSWD, pCertPC); CURL_SETOPT_CHECK(easy, CURLOPT_SSL_VERIFYPEER, 1L); - // Execute the request directly + // Execute the request and retry incase of certificate related error curl_code = curl_easy_perform(easy); long http_code; @@ -573,11 +596,11 @@ T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_ou if(curl_code != CURLE_OK || http_code != 200) { - T2Error("%s: Failed to establish connection using xPKI certificate: %s, Curl failed : %d (entry %d)\n", __func__, pCertFile, curl_code, idx); + T2Error("%s: Failed to establish connection using xPKI certificate: %s, Curl failed : %d\n", __func__, pCertFile, curl_code); } else { - T2Info("%s: Using xpki Certs connection certname : %s (entry %d)\n", __FUNCTION__, pCertFile, idx); + T2Info("%s: Using xpki Certs connection certname : %s \n", __FUNCTION__, pCertFile); } } } @@ -586,13 +609,11 @@ T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_ou // Fallback to getMtlsCerts if certificate selector not available if(T2ERROR_SUCCESS == getMtlsCerts(&pCertFile, &pCertPC)) { - // Configure mTLS certificates CURL_SETOPT_CHECK_STR(easy, CURLOPT_SSLCERTTYPE, "P12"); CURL_SETOPT_CHECK_STR(easy, CURLOPT_SSLCERT, pCertFile); CURL_SETOPT_CHECK_STR(easy, CURLOPT_KEYPASSWD, pCertPC); CURL_SETOPT_CHECK(easy, CURLOPT_SSL_VERIFYPEER, 1L); - // Execute the request curl_code = curl_easy_perform(easy); } else @@ -610,13 +631,13 @@ T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_ou return T2ERROR_FAILURE; } #endif + //reset the security options set to curl handle CURL_SETOPT_CHECK(easy, CURLOPT_SSLCERT, NULL); CURL_SETOPT_CHECK(easy, CURLOPT_KEYPASSWD, NULL); } else { T2Info("Attempting curl communication without mtls\n"); - // Execute without mTLS curl_code = curl_easy_perform(easy); } @@ -647,7 +668,6 @@ T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_ou long http_code; curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &http_code); - T2Info("%s ; HTTP response code: %ld\n", __FUNCTION__, http_code); T2ERROR result = T2ERROR_FAILURE; if (http_code == 200) @@ -665,7 +685,7 @@ T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_ou int fd = open(HTTP_RESPONSE_FILE, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (fd >= 0) { - FILE *httpOutput = fdopen(fd, "w+"); + FILE *httpOutput = fdopen(fd, "w"); if (httpOutput) { T2Debug("Update config data in response file %s \n", HTTP_RESPONSE_FILE); @@ -684,7 +704,7 @@ T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_ou } } - // Copy response data + // Copy response data to return to the caller function if (response_data) { *response_data = NULL; @@ -739,6 +759,7 @@ T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_ou } // Clean up certificates - only for non-LIBRDKCERTSEL_BUILD path + // In case of CertSelector, CertSelector will take care of freeing the certicates #ifndef LIBRDKCERTSEL_BUILD if(pCertFile != NULL) { @@ -759,6 +780,7 @@ T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_ou pCertPC = NULL; } #endif + // Important Note: When using LIBRDKCERTSEL_BUILD, pCertURI and pCertPC are owned by the // cert selector object and are freed when rdkcertselector_free() is called @@ -768,7 +790,6 @@ T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_ou return result; } -// POST API - Updated to use shared pool T2ERROR http_pool_post(const char *url, const char *payload) { T2Debug("%s ++in\n", __FUNCTION__); @@ -778,7 +799,7 @@ T2ERROR http_pool_post(const char *url, const char *payload) return T2ERROR_FAILURE; } - T2Info("%s ; POST url = %s\n", __FUNCTION__, url); + T2Info("POST url = %s\n", url); // Acquire any available handle (with waiting) CURL *easy; @@ -791,16 +812,13 @@ T2ERROR http_pool_post(const char *url, const char *payload) return ret; } - T2Debug("http_pool_post using handle %d\n", idx); - // Clear any GET-specific settings from previous use - CURL_SETOPT_CHECK(easy, CURLOPT_HTTPGET, 0L); CURL_SETOPT_CHECK(easy, CURLOPT_WRITEFUNCTION, NULL); - CURL_SETOPT_CHECK(easy, CURLOPT_WRITEDATA, NULL); + CURL_SETOPT_CHECK(easy, CURLOPT_WRITEDATA, (void *)stdout); // Configure request-specific options for POST CURL_SETOPT_CHECK_STR(easy, CURLOPT_URL, url); - CURL_SETOPT_CHECK_STR(easy, CURLOPT_CUSTOMREQUEST, "POST"); + CURL_SETOPT_CHECK(easy, CURLOPT_POST, 1L); CURL_SETOPT_CHECK(easy, CURLOPT_HTTPHEADER, post_headers); CURL_SETOPT_CHECK_STR(easy, CURLOPT_POSTFIELDS, payload); CURL_SETOPT_CHECK(easy, CURLOPT_POSTFIELDSIZE, strlen(payload)); @@ -812,6 +830,7 @@ T2ERROR http_pool_post(const char *url, const char *payload) // curl_easy_perform crashes without file output configuration. This can be removed once the root cause of the crash is identified and fixed. // For now, we will set up file output for POST requests to ensure stability. // Set up file output for POST requests + // If file opens successfully, override the default stdout with the file pointer int curl_output_fd = open("/tmp/curlOutput.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); FILE *fp = NULL; if (curl_output_fd >= 0) @@ -824,13 +843,13 @@ T2ERROR http_pool_post(const char *url, const char *payload) } else { - T2Error("fdopen failed for /tmp/curlOutput.txt\n"); + T2Error("fdopen failed for /tmp/curlOutput.txt, will use stdout as fallback\n"); close(curl_output_fd); } } else { - T2Error("Unable to open /tmp/curlOutput.txt for writing\n"); + T2Error("Unable to open /tmp/curlOutput.txt for writing, will use stdout as fallback\n"); } // Certificate handling - check if mTLS is enabled @@ -860,11 +879,11 @@ T2ERROR http_pool_post(const char *url, const char *payload) // RED recovery mode - use recovery cert selector if (pool_entries[idx].rcvry_cert_selector == NULL) { - T2Info("%s: Initializing recovery cert selector for entry %d\n", __func__, idx); + T2Info("Initializing recovery cert selector for this easy handle\n"); pool_entries[idx].rcvry_cert_selector = rdkcertselector_new(NULL, NULL, "RCVRY"); if (pool_entries[idx].rcvry_cert_selector == NULL) { - T2Error("%s: Failed to initialize recovery cert selector for entry %d\n", __func__, idx); + T2Error("Failed to initialize recovery cert selector for this easy handle\n"); release_pool_handle(idx); if(fp) { @@ -945,7 +964,6 @@ T2ERROR http_pool_post(const char *url, const char *payload) CURL_SETOPT_CHECK_STR(easy, CURLOPT_KEYPASSWD, pCertPC); CURL_SETOPT_CHECK(easy, CURLOPT_SSL_VERIFYPEER, 1L); - // Execute the request directly curl_code = curl_easy_perform(easy); long http_code; @@ -972,7 +990,6 @@ T2ERROR http_pool_post(const char *url, const char *payload) CURL_SETOPT_CHECK_STR(easy, CURLOPT_KEYPASSWD, pCertPC); CURL_SETOPT_CHECK(easy, CURLOPT_SSL_VERIFYPEER, 1L); - // Execute the request curl_code = curl_easy_perform(easy); } else @@ -991,7 +1008,6 @@ T2ERROR http_pool_post(const char *url, const char *payload) } else { - // Execute without mTLS T2Info("Attempting curl communication without mtls\n"); curl_code = curl_easy_perform(easy); } @@ -1005,16 +1021,16 @@ T2ERROR http_pool_post(const char *url, const char *payload) { long http_code; curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &http_code); - T2Debug("%s ; HTTP response code: %ld\n", __FUNCTION__, http_code); + T2Debug("HTTP response code: %ld\n", http_code); if (http_code == 200) { result = T2ERROR_SUCCESS; - T2Info("%s ; HTTP Request successful\n", __FUNCTION__); + T2Info("Report Sent Successfully over HTTP : %ld\n", http_code); } else { - T2Error("%s ; HTTP request failed with code: %ld\n", __FUNCTION__, http_code); + T2Error("HTTP request failed with code: %ld\n", http_code); } } else @@ -1064,11 +1080,54 @@ T2ERROR http_pool_cleanup(void) T2Info("Cleaning up http pool resources\n"); - // Reset initialization flag - pool_initialized = false; + // Signal all new acquire attempts to bail out immediately + pool_shutting_down = true; + pthread_mutex_unlock(&pool_mutex); + + // Poll until all in-flight curl_easy_perform() calls complete. + // Max wait = POOL_ACQUIRE_TIMEOUT_SEC (curl timeout 30s + 5s buffer). + // Polling avoids the deadlock where cleanup holds mutex while waiting, + // and release_pool_handle also needs the mutex to decrement active_requests. + struct timespec start_time, current_time; + if (clock_gettime(CLOCK_MONOTONIC, &start_time) != 0) + { + T2Error("clock_gettime failed for start_time: %s\n", strerror(errno)); + return T2ERROR_FAILURE; + } + + while(1) + { + pthread_mutex_lock(&pool_mutex); + unsigned int pending = active_requests; + pthread_mutex_unlock(&pool_mutex); - // Signal any waiting threads to wake up - pthread_cond_broadcast(&pool_cond); + if (pending == 0) + { + T2Info("All active requests completed, proceeding with cleanup\n"); + break; + } + + if (clock_gettime(CLOCK_MONOTONIC, ¤t_time) != 0) + { + T2Error("clock_gettime failed for current_time: %s\n", strerror(errno)); + return T2ERROR_FAILURE; + } + long elapsed_sec = current_time.tv_sec - start_time.tv_sec; + + if (elapsed_sec >= POOL_ACQUIRE_TIMEOUT_SEC) + { + T2Error("Cleanup timeout after %ld seconds with %u request(s) still active, forcing cleanup\n", + elapsed_sec, pending); + break; + } + + T2Info("Waiting for %u active request(s) to complete before pool cleanup\n", pending); + usleep(POOL_ACQUIRE_RETRY_MS * 1000); + } + + pthread_mutex_lock(&pool_mutex); + pool_initialized = false; + pool_shutting_down = false; pthread_mutex_unlock(&pool_mutex); // Cleanup all curl handles and per-entry certificate selectors diff --git a/source/test/mocks/FileioMock.cpp b/source/test/mocks/FileioMock.cpp index 81cf54d1..60e19aaf 100644 --- a/source/test/mocks/FileioMock.cpp +++ b/source/test/mocks/FileioMock.cpp @@ -49,13 +49,14 @@ typedef struct curl_slist* (*curl_slist_append_ptr) (struct curl_slist *list, co typedef CURL* (*curl_easy_init_ptr)(); typedef CURLcode (*curl_easy_setopt_mock_ptr) (CURL *curl, CURLoption option, void* parameter); typedef CURLcode (*curl_easy_getinfo_mock_ptr) (CURL *curl, CURLINFO info, void* arg); +typedef CURLcode (*curl_easy_getinfo_ptr) (CURL *curl, CURLINFO info, ...); typedef long (*ftell_ptr) (FILE *stream); typedef int (*fscanf_ptr) (FILE *, const char *, va_list); typedef pid_t (*fork_ptr) (); typedef ssize_t (*write_ptr) (int fd, const void *buf, size_t count); //typedef int (*stat_ptr) (const char *pathname, struct stat *statbuf); typedef int (*fprintf_ptr) (FILE* stream, const char* format, va_list args); -//pedef CURLcode (*curl_easy_setopt_ptr) (CURL *curl, CURLoption option, parameter); +typedef CURLcode (*curl_easy_setopt_ptr) (CURL *curl, CURLoption option, ...); typedef void (*curl_easy_cleanup_ptr) (CURL *handle); typedef void (*curl_slist_free_all_ptr) (struct curl_slist *list); typedef int (*munmap_ptr) (void *addr, size_t len); @@ -102,7 +103,9 @@ curl_easy_setopt_mock_ptr curl_easy_setopt_mock_func = (curl_easy_setopt_mock_pt curl_easy_getinfo_mock_ptr curl_easy_getinfo_mock_func = (curl_easy_getinfo_mock_ptr) dlsym(RTLD_NEXT, "curl_easy_getinfo_mock"); curl_easy_cleanup_ptr curl_easy_cleanup_func = (curl_easy_cleanup_ptr) dlsym(RTLD_NEXT, "curl_easy_cleanup"); curl_slist_free_all_ptr curl_slist_free_all_func = (curl_slist_free_all_ptr) dlsym(RTLD_NEXT, "curl_slist_free_all"); -//curl_easy_setopt_ptr curl_easy_setopt_func = (curl_easy_setopt_ptr) dlsym(RTLD_NEXT, "curl_easy_setopt"); +curl_easy_setopt_ptr curl_easy_setopt_func = (curl_easy_setopt_ptr) dlsym(RTLD_NEXT, "curl_easy_setopt"); +curl_easy_getinfo_ptr curl_easy_getinfo_func = (curl_easy_getinfo_ptr) dlsym(RTLD_NEXT, "curl_easy_getinfo"); + munmap_ptr munmap_func = (munmap_ptr) dlsym(RTLD_NEXT, "munmap"); mmap_ptr mmap_func = (mmap_ptr) dlsym(RTLD_NEXT, "mmap"); mkstemp_ptr mkstemp_func = (mkstemp_ptr) dlsym(RTLD_NEXT, "mkstemp"); @@ -386,6 +389,34 @@ extern "C" CURLcode curl_easy_getinfo_mock(CURL *curl, CURLINFO info, void* arg) return g_fileIOMock->curl_easy_getinfo_mock(curl, info, arg); } +extern "C" CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...) +{ + if (g_fileIOMock != nullptr) { + va_list args; + va_start(args, info); + void* arg = va_arg(args, void*); + va_end(args); + return g_fileIOMock->curl_easy_getinfo_mock(curl, info, arg); + } + + // Fallback to real function + if (curl_easy_getinfo_func) { + va_list args; + va_start(args, info); + void* arg = va_arg(args, void*); + va_end(args); + + // Call real function with extracted argument + if (info == CURLINFO_RESPONSE_CODE) { + return curl_easy_getinfo_func(curl, info, (long*)arg); + } else { + return curl_easy_getinfo_func(curl, info, arg); + } + } + + return CURLE_FAILED_INIT; +} + extern "C" struct curl_slist *curl_slist_append(struct curl_slist *list, const char * string) { if (g_fileIOMock == nullptr){ @@ -459,6 +490,19 @@ extern "C" CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...) return result; } */ + +extern "C" CURLcode curl_easy_setopt(CURL* handle, CURLoption option, ...) { + if (g_fileIOMock != nullptr) { + va_list args; + va_start(args, option); + void* param = va_arg(args, void*); + va_end(args); + return g_fileIOMock->curl_easy_setopt(handle, option, param); + } + return CURLE_FAILED_INIT; +} + + extern "C" void curl_easy_cleanup(CURL *handle) { if (g_fileIOMock == nullptr){ diff --git a/source/test/mocks/FileioMock.h b/source/test/mocks/FileioMock.h index f97cb717..9551ac7b 100644 --- a/source/test/mocks/FileioMock.h +++ b/source/test/mocks/FileioMock.h @@ -86,9 +86,9 @@ class FileMock MOCK_METHOD(ssize_t, getline, (char** lineptr, size_t* n, FILE* stream), ()); MOCK_METHOD(pid_t, fork, (), ()); MOCK_METHOD(ssize_t, write, (int fd, const void* buf, size_t count), ()); - //MOCK_METHOD(CURLcode, curl_easy_setopt, (CURL *handle, CURLoption option, parameter), ()); + MOCK_METHOD(CURLcode, curl_easy_setopt, (CURL *handle, CURLoption option, void *parameter), ()); MOCK_METHOD(void, curl_easy_cleanup, (CURL *handle), ()); - MOCK_METHOD(CURLcode, curl_easy_setopt_mock, (CURL *handle, CURLoption option, void* parameter), ()); + MOCK_METHOD(CURLcode, curl_easy_setopt_mock, (CURL *handle, CURLoption option, void *parameter), ()); MOCK_METHOD(CURLcode, curl_easy_getinfo_mock, (CURL *curl, CURLINFO info, void* arg), ()); MOCK_METHOD(void, curl_slist_free_all, (struct curl_slist *list), ()); //MOCK_METHOD(int, stat, (const char *pathname, struct stat *statbuf), ()); diff --git a/source/test/protocol/ProtocolTest.cpp b/source/test/protocol/ProtocolTest.cpp index 25a24f76..14252f26 100644 --- a/source/test/protocol/ProtocolTest.cpp +++ b/source/test/protocol/ProtocolTest.cpp @@ -80,7 +80,6 @@ using ::testing::_; using ::testing::Return; using ::testing::StrEq; - class protocolTestFixture : public ::testing::Test { protected: void SetUp() override @@ -91,6 +90,46 @@ class protocolTestFixture : public ::testing::Test { g_rbusMock = new rbusMock(); g_rdkconfigMock = new rdkconfigMock(); + // Set default behaviors for curl functions to prevent them from being called + EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Return(nullptr)); + + EXPECT_CALL(*g_fileIOMock, curl_easy_perform(::testing::_)) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Return(CURLE_FAILED_INIT)); + + EXPECT_CALL(*g_fileIOMock, curl_easy_cleanup(::testing::_)) + .Times(::testing::AnyNumber()); + + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt(::testing::_, ::testing::_, ::testing::_)) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Return(CURLE_OK)); + + // Add curl_slist functions to prevent deadlock during pool initialization + EXPECT_CALL(*g_fileIOMock, curl_slist_append(::testing::_, ::testing::_)) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Invoke([](struct curl_slist* list, const char* str) { + // Return a fake non-null pointer to simulate successful append + static int counter = 1; + return (struct curl_slist*)(uintptr_t)(0x2000 + counter++); + })); + + EXPECT_CALL(*g_fileIOMock, curl_slist_free_all(::testing::_)) + .Times(::testing::AnyNumber()); + + EXPECT_CALL(*g_fileIOMock, fopen(::testing::_, ::testing::_)) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Return((FILE*)nullptr)) + .RetiresOnSaturation(); + + EXPECT_CALL(*g_fileIOMock, fwrite(::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Invoke( + [](const void* ptr, size_t size, size_t nitems, FILE* stream) { + return ::fwrite(ptr, size, nitems, stream); + })) + .RetiresOnSaturation(); } void TearDown() override @@ -109,8 +148,6 @@ class protocolTestFixture : public ::testing::Test { } }; - -#if 0 TEST(SENDREPORTOVERHTTP, 1_NULL_CHECK) { char *payload = "This is a payload string"; @@ -123,6 +160,7 @@ TEST(SENDREPORTOVERHTTP, 2_NULL_CHECK) EXPECT_EQ(T2ERROR_FAILURE, sendReportOverHTTP(url, NULL)); } + TEST(SENDCACREPOVERHTTP, 1_NULL_CHECK) { Vector* reportlist; @@ -138,7 +176,6 @@ TEST(SENDCACREPOVERHTTP, 2_NULL_CHECK) char *url = "https://test.com"; EXPECT_EQ(T2ERROR_FAILURE, sendCachedReportsOverHTTP(url, NULL)); } -#endif TEST(SENDRBUDREPORTOVERRBUS, 1_NULL_CHECK) { @@ -188,15 +225,53 @@ TEST(SENDRBUSCACHEREPORTOVERRBUS, NULL_CHECK) Vector_Destroy(reportList, free); } -#if 0 +TEST_F(protocolTestFixture, SENDREPORTOVERHTTP0) +{ + char* httpURL = "https://mockxconf:50051/dataLakeMock"; + char* payload = strdup("This is a payload string"); + //http_pool_cleanup(); // Ensure pool is cleaned up before test to prevent interference from previous tests + EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Return(nullptr)); + EXPECT_CALL(*m_xconfclientMock, isMtlsEnabled()) + .Times(1) + .WillOnce(Return(0)); + EXPECT_EQ(T2ERROR_FAILURE, sendReportOverHTTP(httpURL, payload)); + free(payload); +} + TEST_F(protocolTestFixture, SENDREPORTOVERHTTP1) { - char* httpURL = "https://mockxconf:50051/dataLakeMock"; - char* payload = strdup("This is a payload string"); - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(1) - .WillOnce(Return(-1)); - EXPECT_EQ(T2ERROR_FAILURE, sendReportOverHTTP(httpURL, payload)); + char* httpURL = "https://mockxconf:50051/dataLakeMock"; + char* payload = strdup("This is a payload string"); + + EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Invoke([]() { + // Return unique fake handles for each call + static int handle_counter = 1; + return (CURL*)(uintptr_t)(0x1000 + handle_counter++); + })); + + EXPECT_CALL(*m_xconfclientMock, isMtlsEnabled()) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_fileIOMock, curl_easy_perform(_)) + .Times(1) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*g_fileIOMock, curl_easy_getinfo_mock(_,_,_)) + .Times(::testing::AtLeast(1)) + .WillRepeatedly([](CURL* curl, CURLINFO info, void* response_code) { + if (info == CURLINFO_RESPONSE_CODE) { + *(long*)response_code = 200; + } + return CURLE_OK; + }); + + //Add curl_easy_cleanup expectation + EXPECT_CALL(*g_fileIOMock, curl_easy_cleanup(_)) + .Times(::testing::AnyNumber()); + EXPECT_EQ(T2ERROR_SUCCESS, sendReportOverHTTP(httpURL, payload)); free(payload); } @@ -204,10 +279,15 @@ TEST_F(protocolTestFixture, SENDREPORTOVERHTTP2) { char* httpURL = "https://mockxconf:50051/dataLakeMock"; char* payload = strdup("This is a payload string"); - char *cm = (char*)0xFFFFFFFF; - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(1) - .WillOnce(Return(0)); + + EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Invoke([]() { + // Return unique fake handles for each call + static int handle_counter = 1; + return (CURL*)(uintptr_t)(0x1000 + handle_counter++); + })); + #if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER) #if defined(WAN_FAILOVER_SUPPORTED) || defined(FEATURE_RDKB_CONFIGURABLE_WAN_INTERFACE) EXPECT_CALL(*m_xconfclientMock, getParameterValue(_,_)) @@ -226,10 +306,22 @@ TEST_F(protocolTestFixture, SENDREPORTOVERHTTP2) .Times(1) .WillOnce(Return(RDKCONFIG_OK)); #endif - EXPECT_CALL(*g_fileIOMock, fork()) - .Times(1) - .WillOnce(Return(-1)); - EXPECT_EQ(T2ERROR_FAILURE, sendReportOverHTTP(httpURL, payload)); + EXPECT_CALL(*g_fileIOMock, curl_easy_perform(_)) + .Times(1) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*g_fileIOMock, curl_easy_getinfo_mock(_,_,_)) + .Times(::testing::AtLeast(1)) + .WillRepeatedly([](CURL* curl, CURLINFO info, void* response_code) { + if (info == CURLINFO_RESPONSE_CODE) { + *(long*)response_code = 200; + } + return CURLE_OK; + }); + + //Add curl_easy_cleanup expectation + EXPECT_CALL(*g_fileIOMock, curl_easy_cleanup(_)) + .Times(::testing::AnyNumber()); + EXPECT_EQ(T2ERROR_SUCCESS, sendReportOverHTTP(httpURL, payload)); free(payload); } @@ -239,9 +331,15 @@ TEST_F(protocolTestFixture, SENDREPORTOVERHTTP3) char* httpURL = "https://mockxconf:50051/dataLakeMock"; char* payload = strdup("This is a payload string"); char *cm = (char*)0xFFFFFFFF; - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(1) - .WillOnce(Return(0)); + + EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Invoke([]() { + // Return unique fake handles for each call + static int handle_counter = 1; + return (CURL*)(uintptr_t)(0x1000 + handle_counter++); + })); + #if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER) #if defined(WAN_FAILOVER_SUPPORTED) || defined(FEATURE_RDKB_CONFIGURABLE_WAN_INTERFACE) EXPECT_CALL(*m_xconfclientMock, getParameterValue(_,_)) @@ -260,17 +358,7 @@ TEST_F(protocolTestFixture, SENDREPORTOVERHTTP3) .Times(1) .WillOnce(Return(RDKCONFIG_OK)); #endif - EXPECT_CALL(*g_fileIOMock, fork()) - .Times(1) - .WillOnce(Return(1)); - EXPECT_CALL(*g_fileIOMock, close(_)) - .Times(2) - .WillOnce(Return(-1)) - .WillOnce(Return(-1)); - EXPECT_CALL(*g_fileIOMock, read(_,_,_)) - .Times(1) - .WillOnce(Return(-1)); - EXPECT_EQ(T2ERROR_SUCCESS, sendReportOverHTTP(httpURL, payload)); + EXPECT_EQ(T2ERROR_FAILURE, sendReportOverHTTP(httpURL, payload)); free(payload); } @@ -282,9 +370,14 @@ TEST_F(protocolTestFixture, SENDCACHEDREPORTOVERHTTP) Vector_Create(&reportlist); Vector_PushBack(reportlist, payload); char *cm = (char*)0xFFFFFFFF; - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(1) - .WillOnce(Return(0)); + + EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Invoke([]() { + // Return unique fake handles for each call + static int handle_counter = 1; + return (CURL*)(uintptr_t)(0x1000 + handle_counter++); + })); #if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER) #if defined(WAN_FAILOVER_SUPPORTED) || defined(FEATURE_RDKB_CONFIGURABLE_WAN_INTERFACE) EXPECT_CALL(*m_xconfclientMock, getParameterValue(_,_)) @@ -303,20 +396,25 @@ TEST_F(protocolTestFixture, SENDCACHEDREPORTOVERHTTP) .Times(1) .WillOnce(Return(RDKCONFIG_OK)); #endif - EXPECT_CALL(*g_fileIOMock, fork()) - .Times(1) - .WillOnce(Return(1)); - EXPECT_CALL(*g_fileIOMock, close(_)) - .Times(2) - .WillOnce(Return(-1)) - .WillOnce(Return(-1)); - EXPECT_CALL(*g_fileIOMock, read(_,_,_)) - .Times(1) - .WillOnce(Return(-1)); + EXPECT_CALL(*g_fileIOMock, curl_easy_perform(_)) + .Times(1) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*g_fileIOMock, curl_easy_getinfo_mock(_,_,_)) + .Times(::testing::AtLeast(1)) + .WillRepeatedly([](CURL* curl, CURLINFO info, void* response_code) { + if (info == CURLINFO_RESPONSE_CODE) { + *(long*)response_code = 200; + } + return CURLE_OK; + }); + + //Add curl_easy_cleanup expectation + EXPECT_CALL(*g_fileIOMock, curl_easy_cleanup(_)) + .Times(::testing::AnyNumber()); + EXPECT_EQ(T2ERROR_SUCCESS, sendCachedReportsOverHTTP(httpURL, reportlist)); Vector_Destroy(reportlist, free); } -#endif TEST_F(protocolTestFixture, SENDREPORTSOVERRBUSMETHOD1) { @@ -432,69 +530,6 @@ TEST_F(protocolTestFixture, sendCachedReportsOverRBUSMethod) Vector_Destroy(reportlist,free); } -#if 0 -//sendReportOverHTTP -TEST_F(protocolTestFixture, sendReportOverHTTP_6) -{ - char* httpURL = "https://mockxconf:50051/dataLakeMock"; - char* payload = strdup("This is a payload string"); - Vector* reportlist = NULL; - Vector_Create(&reportlist); - Vector_PushBack(reportlist, payload); - char *cm = (char*)0xFFFFFFFF; - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(1) - .WillOnce(Return(0)); - #if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER) - #if defined(WAN_FAILOVER_SUPPORTED) || defined(FEATURE_RDKB_CONFIGURABLE_WAN_INTERFACE) - EXPECT_CALL(*m_xconfclientMock, getParameterValue(_,_)) - .WillOnce([](const char* paramName, char** paramValue) { - if (strcmp(paramName, "Device.X_RDK_WanManager.CurrentActiveInterface") == 0) - *paramValue = strdup("erouter0"); - else - *paramValue = strdup("unknown"); - return T2ERROR_SUCCESS; - }); - #endif - #endif - EXPECT_CALL(*m_xconfclientMock, isMtlsEnabled()) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(*g_systemMock, access(_,_)) - .Times(1) - .WillOnce(Return(0)); - #ifdef LIBRDKCONFIG_BUILD - EXPECT_CALL(*g_rdkconfigMock, rdkconfig_get(_,_,_)) - .Times(1) - .WillOnce(Return(RDKCONFIG_OK)); - #endif - EXPECT_CALL(*g_fileIOMock, fork()) - .Times(1) - .WillOnce(Return(1)); - EXPECT_CALL(*g_fileIOMock, close(_)) - .Times(2) - .WillOnce(Return(-1)) - .WillOnce(Return(-1)); - EXPECT_CALL(*g_fileIOMock, read(_,_,_)) - .Times(1) - .WillOnce([](int fd, void *buf, size_t count) { - childResponse* resp = (childResponse*)buf; - resp->curlStatus = true; - resp->curlResponse = CURLE_OK; - resp->curlSetopCode = CURLE_OK; - resp->http_code = 200; - resp->lineNumber = 123; // Set to test value - return sizeof(childResponse); - }); - #ifdef LIBRDKCONFIG_BUILD - EXPECT_CALL(*g_rdkconfigMock, rdkconfig_free(_, _)) - .Times(1) - .WillOnce(Return(RDKCONFIG_OK)); - #endif - EXPECT_EQ(T2ERROR_SUCCESS, sendReportOverHTTP(httpURL, payload)); - Vector_Destroy(reportlist, free); -} - TEST_F(protocolTestFixture, sendCachedReportsOverHTTP_FailureCase) { char *httpURL = "https://mockxconf:50051/dataLakeMock"; @@ -507,26 +542,16 @@ TEST_F(protocolTestFixture, sendCachedReportsOverHTTP_FailureCase) Vector_PushBack(reportList, payload1); Vector_PushBack(reportList, payload2); - // Mock failure for sendReportOverHTTP on the first payload - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(1) - .WillOnce(Return(0)); - EXPECT_CALL(*m_xconfclientMock, isMtlsEnabled()) .Times(1) .WillOnce(Return(false)); - EXPECT_CALL(*g_fileIOMock, fork()) - .Times(1) - .WillOnce(Return(-1)); - // Ensure that the function returns a failure due to the mocked failure EXPECT_EQ(T2ERROR_FAILURE, sendCachedReportsOverHTTP(httpURL, reportList)); // Clean up Vector_Destroy(reportList, free); } -#endif #ifdef GTEST_ENABLE // Unit test for static writeToFile via its function pointer TEST(CURLINTERFACE_STATIC, WriteToFile) @@ -557,470 +582,4 @@ TEST_F(protocolTestFixture, sendCachedReportsOverHTTP_FailureCase) remove(testFile); } -#if 0 -TEST(CURLINTERFACE_STATIC, SetHeader) -{ - childResponse resp; - memset(&resp, 0, sizeof(resp)); - SetHeaderFunc setHeaderCb = getSetHeaderCallback(); - ASSERT_NE(setHeaderCb, nullptr); - CURL *curl = nullptr; // purposely NULL - const char *destURL = "http://localhost"; - struct curl_slist *headerList = nullptr; - T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); - // According to implementation, curl==NULL returns T2ERROR_FAILURE - EXPECT_EQ(result, T2ERROR_FAILURE); -} -TEST(CURLINTERFACE_STATIC, SetMtlsHeaders_NULL) -{ - childResponse resp; - memset(&resp, 0, sizeof(resp)); - SetMtlsHeadersFunc cb = getSetMtlsHeadersCallback(); - ASSERT_NE(cb, nullptr); - // NULL for CURL - EXPECT_EQ(cb(nullptr, "cert", "pwd", &resp), T2ERROR_FAILURE); - // NULL for certFile - EXPECT_EQ(cb((CURL*)0x1, nullptr, "pwd", &resp), T2ERROR_FAILURE); - // NULL for passwd - EXPECT_EQ(cb((CURL*)0x1, "cert", nullptr, &resp), T2ERROR_FAILURE); -} - -TEST(CURLINTERFACE_STATIC, SetPayload_NULL) -{ - childResponse resp; - memset(&resp, 0, sizeof(resp)); - SetPayloadFunc cb = getSetPayloadCallback(); - ASSERT_NE(cb, nullptr); - // NULL for CURL - EXPECT_EQ(cb(nullptr, "payload", &resp), T2ERROR_FAILURE); - // NULL for payload - EXPECT_EQ(cb((CURL*)0x1, nullptr, &resp), T2ERROR_FAILURE); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_NULL_DESTURL) -{ - childResponse resp; - memset(&resp, 0, sizeof(resp)); - SetHeaderFunc setHeaderCb = getSetHeaderCallback(); - ASSERT_NE(setHeaderCb, nullptr); - - CURL *curl = (CURL*)0x1; - struct curl_slist *headerList = nullptr; - - // destURL NULL should immediately fail - T2ERROR result = setHeaderCb(curl, NULL, &headerList, &resp); - EXPECT_EQ(result, T2ERROR_FAILURE); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_setopt_failure) -{ - childResponse resp; - memset(&resp, 0, sizeof(resp)); - SetHeaderFunc setHeaderCb = getSetHeaderCallback(); - ASSERT_NE(setHeaderCb, nullptr); - - CURL *curl = (CURL*)0x1; - const char *destURL = "http://localhost"; - struct curl_slist *headerList = nullptr; - - // Make the first curl_easy_setopt call fail to exercise early failure path. - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) - .Times(1) - .WillOnce(Return(CURLE_FAILED_INIT)); - - T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); - EXPECT_EQ(result, T2ERROR_FAILURE); - // curlSetopCode should be set to the failing CURLE code - EXPECT_EQ(resp.curlSetopCode, CURLE_FAILED_INIT); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_FirstSetopt_failure_lineNumber) -{ - SetHeaderFunc setHeaderCb = getSetHeaderCallback(); - ASSERT_NE(setHeaderCb, nullptr); - - CURL *curl = (CURL*)0x1; - const char *destURL = "http://localhost"; - struct curl_slist *headerList = nullptr; - childResponse resp; - memset(&resp, 0, sizeof(resp)); - - // The very first curl_easy_setopt_mock call returns CURLE_FAILED_INIT. - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) - .Times(1) - .WillOnce(Return(CURLE_FAILED_INIT)); - - T2ERROR code = setHeaderCb(curl, destURL, &headerList, &resp); - EXPECT_EQ(code, T2ERROR_FAILURE); - EXPECT_EQ(resp.curlSetopCode, CURLE_FAILED_INIT); - // Assert that lineNumber field gets set. (Should match the __LINE__ where the macro is expanded; we test it's nonzero.) - EXPECT_NE(resp.lineNumber, 0); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetMtlsHeaders_setopt_failure) -{ - SetMtlsHeadersFunc cb = getSetMtlsHeadersCallback(); - ASSERT_NE(cb, nullptr); - - childResponse resp; - memset(&resp, 0, sizeof(resp)); - - // Make the first curl_easy_setopt call fail and ensure function returns failure - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) - .Times(1) - .WillOnce(Return(CURLE_UNKNOWN_OPTION)); - - T2ERROR result = cb((CURL*)0x1, "dummyCert", "dummyPwd", &resp); - EXPECT_EQ(result, T2ERROR_FAILURE); - EXPECT_EQ(resp.curlSetopCode, CURLE_UNKNOWN_OPTION); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetPayload_setopt_failure) -{ - SetPayloadFunc cb = getSetPayloadCallback(); - ASSERT_NE(cb, nullptr); - - childResponse resp; - memset(&resp, 0, sizeof(resp)); - - // Make CURLOPT_POSTFIELDS setopt call fail - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) - .Times(1) - .WillOnce(Return(CURLE_OUT_OF_MEMORY)); - - T2ERROR result = cb((CURL*)0x1, "payload", &resp); - EXPECT_EQ(result, T2ERROR_FAILURE); - EXPECT_EQ(resp.curlSetopCode, CURLE_OUT_OF_MEMORY); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_SSLVERSION_failure) -{ - SetHeaderFunc setHeaderCb = getSetHeaderCallback(); - ASSERT_NE(setHeaderCb, nullptr); - - CURL *curl = (CURL*)0x1; - const char *destURL = "http://localhost"; - struct curl_slist *headerList = nullptr; - childResponse resp; - memset(&resp, 0, sizeof(resp)); - - // First setopt (CURLOPT_URL) succeeds, second (CURLOPT_SSLVERSION) fails. - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) - .Times(2) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_SSL_CONNECT_ERROR)); - - T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); - EXPECT_EQ(result, T2ERROR_FAILURE); - EXPECT_EQ(resp.curlSetopCode, CURLE_SSL_CONNECT_ERROR); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_CUSTOMREQUEST_failure) -{ - SetHeaderFunc setHeaderCb = getSetHeaderCallback(); - ASSERT_NE(setHeaderCb, nullptr); - - CURL *curl = (CURL*)0x1; - const char *destURL = "http://localhost"; - struct curl_slist *headerList = nullptr; - childResponse resp; - memset(&resp, 0, sizeof(resp)); - - // Sequence: CURLOPT_URL -> OK, CURLOPT_SSLVERSION -> OK, CURLOPT_CUSTOMREQUEST -> fail - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) - .Times(3) - .WillOnce(Return(CURLE_OK)) // CURLOPT_URL - .WillOnce(Return(CURLE_OK)) // CURLOPT_SSLVERSION - .WillOnce(Return(CURLE_UNKNOWN_OPTION)); // CURLOPT_CUSTOMREQUEST fails - - T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); - EXPECT_EQ(result, T2ERROR_FAILURE); - EXPECT_EQ(resp.curlSetopCode, CURLE_UNKNOWN_OPTION); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_TIMEOUT_failure) -{ - SetHeaderFunc setHeaderCb = getSetHeaderCallback(); - ASSERT_NE(setHeaderCb, nullptr); - - CURL *curl = (CURL*)0x1; - const char *destURL = "http://localhost"; - struct curl_slist *headerList = nullptr; - childResponse resp; - memset(&resp, 0, sizeof(resp)); - - // Sequence: CURLOPT_URL -> OK, CURLOPT_SSLVERSION -> OK, CURLOPT_CUSTOMREQUEST -> OK, CURLOPT_TIMEOUT -> fail - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) - .Times(4) - .WillOnce(Return(CURLE_OK)) // CURLOPT_URL - .WillOnce(Return(CURLE_OK)) // CURLOPT_SSLVERSION - .WillOnce(Return(CURLE_OK)) // CURLOPT_CUSTOMREQUEST - .WillOnce(Return(CURLE_OUT_OF_MEMORY)); // CURLOPT_TIMEOUT fails - - T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); - EXPECT_EQ(result, T2ERROR_FAILURE); - EXPECT_EQ(resp.curlSetopCode, CURLE_OUT_OF_MEMORY); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_HTTPHEADER_failure) -{ - SetHeaderFunc setHeaderCb = getSetHeaderCallback(); - ASSERT_NE(setHeaderCb, nullptr); - - CURL *curl = (CURL*)0x1; - const char *destURL = "http://localhost"; - struct curl_slist *headerList = nullptr; - childResponse resp; - memset(&resp, 0, sizeof(resp)); - - // Ensure curl_slist_append calls return a valid non-null list pointer - EXPECT_CALL(*g_fileIOMock, curl_slist_append(_, _)) - .Times(2) - .WillRepeatedly(Return((struct curl_slist*)0x1)); - - // Sequence of curl_easy_setopt_mock returns: - // URL, SSLVERSION, CUSTOMREQUEST, TIMEOUT, (optional INTERFACE), HTTPHEADER -> fail - // Use 6 returns: first 5 OK, 6th is the failing HTTPHEADER setopt. - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) - .Times(6) - .WillOnce(Return(CURLE_OK)) // CURLOPT_URL - .WillOnce(Return(CURLE_OK)) // CURLOPT_SSLVERSION - .WillOnce(Return(CURLE_OK)) // CURLOPT_CUSTOMREQUEST - .WillOnce(Return(CURLE_OK)) // CURLOPT_TIMEOUT - .WillOnce(Return(CURLE_OK)) // CURLOPT_INTERFACE or extra opt (if present) - .WillOnce(Return(CURLE_COULDNT_CONNECT)); // CURLOPT_HTTPHEADER fails - - T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); - EXPECT_EQ(result, T2ERROR_FAILURE); - EXPECT_EQ(resp.curlSetopCode, CURLE_COULDNT_CONNECT); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_HTTPHEADER_failure_block) -{ - SetHeaderFunc setHeaderCb = getSetHeaderCallback(); - ASSERT_NE(setHeaderCb, nullptr); - - CURL *curl = (CURL*)0x1; - const char *destURL = "http://localhost"; - struct curl_slist *headerList = nullptr; - childResponse resp; - memset(&resp, 0, sizeof(resp)); - - // Simulate curl_slist_append returns - EXPECT_CALL(*g_fileIOMock, curl_slist_append(_, _)) - .Times(2) - .WillRepeatedly(Return((struct curl_slist*)0x1)); - - // Mock all prior curl_easy_setopt calls to return OK, only HTTPHEADER fails - ::testing::Sequence s; - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_, ::testing::Ne(CURLOPT_HTTPHEADER), _)) - .Times(::testing::AtLeast(1)) - .InSequence(s) - .WillRepeatedly(Return(CURLE_OK)); - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_, CURLOPT_HTTPHEADER, _)) - .InSequence(s) - .WillOnce(Return(CURLE_COULDNT_CONNECT)); // Simulate failure at HTTPHEADER - - T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); - - EXPECT_EQ(result, T2ERROR_FAILURE); - EXPECT_EQ(resp.curlSetopCode, CURLE_COULDNT_CONNECT); - // Should be set to a non-zero line number corresponding to line 166 in your source - EXPECT_NE(resp.lineNumber, 0); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_WRITEFUNCTION_failure) -{ - SetHeaderFunc setHeaderCb = getSetHeaderCallback(); - ASSERT_NE(setHeaderCb, nullptr); - - CURL *curl = (CURL*)0x1; - const char *destURL = "http://localhost"; - struct curl_slist *headerList = nullptr; - childResponse resp; - memset(&resp, 0, sizeof(resp)); - - // Ensure curl_slist_append calls return a valid non-null list pointer - EXPECT_CALL(*g_fileIOMock, curl_slist_append(_, _)) - .Times(2) - .WillRepeatedly(Return((struct curl_slist*)0x1)); - - // Make CURLOPT_WRITEFUNCTION fail specifically (robust to optional extra setopt calls). - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_, CURLOPT_WRITEFUNCTION, _)) - .WillOnce(Return(CURLE_SEND_ERROR)); - - // All other curl_easy_setopt_mock invocations should succeed. - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_, ::testing::Ne(CURLOPT_WRITEFUNCTION), _)) - .WillRepeatedly(Return(CURLE_OK)); - - T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); - EXPECT_EQ(result, T2ERROR_FAILURE); - EXPECT_EQ(resp.curlSetopCode, CURLE_SEND_ERROR); -} -/* New tests to cover success paths for static helpers in curlinterface.c */ -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetPayload_SUCCESS) -{ - SetPayloadFunc cb = getSetPayloadCallback(); - ASSERT_NE(cb, nullptr); - childResponse resp; - // Allow any curl_easy_setopt_mock calls and return CURLE_OK - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) - .Times(::testing::AtLeast(2)) - .WillRepeatedly(Return(CURLE_OK)); - T2ERROR res = cb((CURL*)0x1, "dummy-payload", &resp); - EXPECT_EQ(res, T2ERROR_SUCCESS); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetMtlsHeaders_SSLCERT_failure) -{ - SetMtlsHeadersFunc cb = getSetMtlsHeadersCallback(); - ASSERT_NE(cb, nullptr); - - childResponse resp; - memset(&resp, 0, sizeof(resp)); - - CURL *curl = (CURL*)0x1; - const char* certFile = "/tmp/mycert.p12"; - const char* passwd = "dummyPwd"; - - // We need to return CURLE_OK for CURLOPT_SSLCERTTYPE, - // And return error for CURLOPT_SSLCERT only, then do not expect calls for subsequent steps - { - ::testing::InSequence seq; - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_SSLCERTTYPE, ::testing::_)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_SSLCERT, ::testing::_)) - .WillOnce(Return(CURLE_SSL_CERTPROBLEM)); - // No further setopt calls expected after error - } - - T2ERROR result = cb(curl, certFile, passwd, &resp); - EXPECT_EQ(result, T2ERROR_FAILURE); - EXPECT_EQ(resp.curlSetopCode, CURLE_SSL_CERTPROBLEM); - // Optionally: check that lineNumber is set to a value after the failing line -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetMtlsHeaders_KEYPASSWD_failure) -{ - SetMtlsHeadersFunc cb = getSetMtlsHeadersCallback(); - ASSERT_NE(cb, nullptr); - - childResponse resp; - memset(&resp, 0, sizeof(resp)); - - CURL *curl = (CURL*)0x1; - const char* certFile = "/tmp/mycert.p12"; - const char* passwd = "dummyPwd"; - - { - ::testing::InSequence seq; - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_SSLCERTTYPE, ::testing::_)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_SSLCERT, ::testing::_)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_KEYPASSWD, ::testing::_)) - .WillOnce(Return(CURLE_LOGIN_DENIED)); - // No further setopt calls expected - } - - T2ERROR result = cb(curl, certFile, passwd, &resp); - EXPECT_EQ(result, T2ERROR_FAILURE); - EXPECT_EQ(resp.curlSetopCode, CURLE_LOGIN_DENIED); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetMtlsHeaders_SSLVERIFYPEER_failure) -{ - SetMtlsHeadersFunc cb = getSetMtlsHeadersCallback(); - ASSERT_NE(cb, nullptr); - - childResponse resp; - memset(&resp, 0, sizeof(resp)); - - CURL *curl = (CURL*)0x1; - const char* certFile = "/tmp/mycert.p12"; - const char* passwd = "dummyPwd"; - - { - ::testing::InSequence seq; - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_SSLCERTTYPE, ::testing::_)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_SSLCERT, ::testing::_)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_KEYPASSWD, ::testing::_)) - .WillOnce(Return(CURLE_OK)); - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_SSL_VERIFYPEER, ::testing::_)) - .WillOnce(Return(CURLE_PEER_FAILED_VERIFICATION)); - } - - T2ERROR result = cb(curl, certFile, passwd, &resp); - EXPECT_EQ(result, T2ERROR_FAILURE); - EXPECT_EQ(resp.curlSetopCode, CURLE_PEER_FAILED_VERIFICATION); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetPayload_POSTFIELDSIZE_failure) -{ - SetPayloadFunc cb = getSetPayloadCallback(); - ASSERT_NE(cb, nullptr); - - childResponse resp; - memset(&resp, 0, sizeof(resp)); - CURL *curl = (CURL*)0x1; - const char *payload = "payload-for-testing"; - - { - ::testing::InSequence seq; - // First call(s) for setting postfields may succeed - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_POSTFIELDS, ::testing::_)) - .WillOnce(Return(CURLE_OK)); - // We force failure on POSTFIELDSIZE - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_POSTFIELDSIZE, ::testing::_)) - .WillOnce(Return(CURLE_WRITE_ERROR)); - } - - T2ERROR result = cb(curl, payload, &resp); - EXPECT_EQ(result, T2ERROR_FAILURE); - EXPECT_EQ(resp.curlSetopCode, CURLE_WRITE_ERROR); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetMtlsHeaders_SUCCESS) -{ - SetMtlsHeadersFunc cb = getSetMtlsHeadersCallback(); - ASSERT_NE(cb, nullptr); - childResponse resp; - // Expect at least 4 curl_easy_setopt calls for the mtls settings - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) - .Times(::testing::AtLeast(4)) - .WillRepeatedly(Return(CURLE_OK)); - T2ERROR res = cb((CURL*)0x1, "/tmp/cert.p12", "passwd", &resp); - EXPECT_EQ(res, T2ERROR_SUCCESS); -} - -TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_SUCCESS) -{ - SetHeaderFunc setHeaderCb = getSetHeaderCallback(); - ASSERT_NE(setHeaderCb, nullptr); - - CURL *curl = (CURL*)0x1; - const char *destURL = "http://localhost"; - struct curl_slist *headerList = nullptr; - childResponse resp; - memset(&resp, 0, sizeof(resp)); - - // Expect curl_slist_append to be called twice for the two headers and return a non-null list - EXPECT_CALL(*g_fileIOMock, curl_slist_append(_, _)) - .Times(2) - .WillRepeatedly(Return((struct curl_slist*)0x1)); - - // Allow the curl_easy_setopt_mock calls that setHeader performs and return CURLE_OK. - // Use AtLeast to be resilient to small differences in option counts due to build flags. - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) - .Times(::testing::AtLeast(6)) - .WillRepeatedly(Return(CURLE_OK)); - - T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); - EXPECT_EQ(result, T2ERROR_SUCCESS); - // headerList should have been set by curl_slist_append to a non-null pointer - EXPECT_NE(headerList, nullptr); -} -#endif #endif diff --git a/source/test/xconf-client/xconfclientTest.cpp b/source/test/xconf-client/xconfclientTest.cpp index 1e46ffe3..5738c9a2 100644 --- a/source/test/xconf-client/xconfclientTest.cpp +++ b/source/test/xconf-client/xconfclientTest.cpp @@ -20,16 +20,13 @@ #include "gtest/gtest.h" #include #include -#define curl_easy_setopt curl_easy_setopt_mock -#define curl_easy_getinfo curl_easy_getinfo_mock #include "test/mocks/SystemMock.h" #include "test/mocks/FileioMock.h" -#undef curl_easy_setopt -#undef curl_easy_getinfo #include "test/mocks/rdklogMock.h" #include "test/mocks/rbusMock.h" #include "xconfclientMock.h" + extern "C" { #include #include @@ -57,22 +54,32 @@ using ::testing::StrEq; using ::testing::SetArgPointee; using ::testing::DoAll; +extern FileMock *g_fileIOMock; XconfclientMock *m_xconfclientMock = NULL; rbusMock *g_rbusMock = NULL; +// Helper macro to prevent deadlock from mocked fwrite and fputs calls in the protocol code when GTest tries to log output, which can cause a deadlock if the logging functions are mocked without allowing real calls to them. +#define PREVENT_GTEST_LOGGING_DEADLOCK() \ + EXPECT_CALL(*g_fileIOMock, fwrite(::testing::_, ::testing::_, ::testing::_, ::testing::_)) \ + .Times(::testing::AnyNumber()) \ + .WillRepeatedly(::testing::Invoke( \ + [](const void* ptr, size_t size, size_t nitems, FILE* stream) { \ + return ::fwrite(ptr, size, nitems, stream); \ + })) TEST(GETBUILDTYPE, NULL_CHECK) { EXPECT_EQ(T2ERROR_FAILURE, getBuildType(NULL)); } -#if 0 TEST(DOHTTPGET, HTTPURL_CHECK) { char* data = NULL; + FileMock fileMock; + g_fileIOMock = &fileMock; + PREVENT_GTEST_LOGGING_DEADLOCK(); EXPECT_EQ(T2ERROR_FAILURE, doHttpGet(NULL, &data)); } -#endif TEST(FETCHREMOTECONFIG, CONFIGUURL_NULL) { @@ -87,6 +94,34 @@ class xconfclientTestFixture : public ::testing::Test { g_fileIOMock = new FileMock(); g_systemMock = new SystemMock(); m_xconfclientMock = new XconfclientMock(); + + // Set default behaviors for curl functions to prevent them from being called + EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Return(nullptr)); + + EXPECT_CALL(*g_fileIOMock, curl_easy_perform(::testing::_)) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Return(CURLE_FAILED_INIT)); + + EXPECT_CALL(*g_fileIOMock, curl_easy_cleanup(::testing::_)) + .Times(::testing::AnyNumber()); + + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt(::testing::_, ::testing::_, ::testing::_)) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Return(CURLE_OK)); + + // Add curl_slist functions to prevent deadlock during pool initialization + EXPECT_CALL(*g_fileIOMock, curl_slist_append(::testing::_, ::testing::_)) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Invoke([](struct curl_slist* list, const char* str) { + // Return a fake non-null pointer to simulate successful append + static int counter = 1; + return (struct curl_slist*)(uintptr_t)(0x2000 + counter++); + })); + + EXPECT_CALL(*g_fileIOMock, curl_slist_free_all(::testing::_)) + .Times(::testing::AnyNumber()); } void TearDown() override @@ -290,16 +325,12 @@ TEST_F(xconfclientTestFixture, fetchRemoteConfiguration) EXPECT_EQ(T2ERROR_FAILURE, fetchRemoteConfiguration(configURL, &configData)); } -#if 0 TEST_F(xconfclientTestFixture, doHttpGet) { char* data = NULL; - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(1) - .WillOnce(Return(-1)); + EXPECT_EQ(T2ERROR_FAILURE, doHttpGet("https://test.com", &data)); } -#endif TEST_F(xconfclientTestFixture, getRemoteConfigURL) { @@ -382,24 +413,37 @@ TEST_F(xconfclientTestFixture, getRemoteConfigURL3) EXPECT_EQ(T2ERROR_SUCCESS, getRemoteConfigURL(&configURL)); } -#if 0 TEST_F(xconfclientTestFixture, doHttpGet1) { char* data = NULL; - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(2) - .WillOnce(Return(0)) - .WillOnce(Return(-1)); + + // Set up curl_easy_init to return valid handles for pool initialization + // The pool will call curl_easy_init() based on pool size (default 2) + EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Invoke([]() { + // Return unique fake handles for each call + static int handle_counter = 1; + return (CURL*)(uintptr_t)(0x1000 + handle_counter++); + })); + EXPECT_CALL(*m_xconfclientMock, isMtlsEnabled()) + .Times(1) + .WillOnce(Return(0)); EXPECT_EQ(T2ERROR_FAILURE, doHttpGet("https://test.com", &data)); } TEST_F(xconfclientTestFixture, doHttpGet2) { char* data = NULL; - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(2) - .WillOnce(Return(0)) - .WillOnce(Return(0)); + + EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Invoke([]() { + // Return unique fake handles for each call + static int handle_counter = 1; + return (CURL*)(uintptr_t)(0x1000 + handle_counter++); + })); + #if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER) #if defined(WAN_FAILOVER_SUPPORTED) || defined(FEATURE_RDKB_CONFIGURABLE_WAN_INTERFACE) EXPECT_CALL(*m_xconfclientMock, getParameterValue(_,_)) @@ -412,26 +456,44 @@ TEST_F(xconfclientTestFixture, doHttpGet2) }); #endif #endif + EXPECT_CALL(*m_xconfclientMock, isMtlsEnabled()) .Times(1) .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, fork()) + EXPECT_CALL(*g_fileIOMock, curl_easy_perform(_)) .Times(1) - .WillOnce(Return(-1)); - EXPECT_EQ(T2ERROR_FAILURE, doHttpGet("https://test.com", &data)); + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*g_fileIOMock, curl_easy_getinfo_mock(_,_,_)) + .Times(::testing::AtLeast(1)) + .WillRepeatedly([](CURL* curl, CURLINFO info, void* response_code) { + if (info == CURLINFO_RESPONSE_CODE) { + *(long*)response_code = 200; + } + return CURLE_OK; + }); + + //Add curl_easy_cleanup expectation + EXPECT_CALL(*g_fileIOMock, curl_easy_cleanup(_)) + .Times(::testing::AnyNumber()); + EXPECT_EQ(T2ERROR_SUCCESS, doHttpGet("https://test.com", &data)); } -//parent + TEST_F(xconfclientTestFixture, doHttpGet3) { char* data = NULL; - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(2) - .WillOnce(Return(0)) - .WillOnce(Return(0)); + + EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Invoke([]() { + // Return unique fake handles for each call + static int handle_counter = 1; + return (CURL*)(uintptr_t)(0x1000 + handle_counter++); + })); + #if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER) #if defined(WAN_FAILOVER_SUPPORTED) || defined(FEATURE_RDKB_CONFIGURABLE_WAN_INTERFACE) - EXPECT_CALL(*m_xconfclientMock, getParameterValue(_,_)) + EXPECT_CALL(*m_xconfclientMock, getParameterValue(_,_)) .WillOnce([](const char* paramName, char** paramValue) { if (strcmp(paramName, "Device.X_RDK_WanManager.CurrentActiveInterface") == 0) *paramValue = strdup("erouter0"); @@ -441,32 +503,43 @@ TEST_F(xconfclientTestFixture, doHttpGet3) }); #endif #endif + EXPECT_CALL(*m_xconfclientMock, isMtlsEnabled()) .Times(1) .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, fork()) - .Times(1) - .WillOnce(Return(1)); - EXPECT_CALL(*g_fileIOMock, close(_)) - .Times(1) - .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, read(_,_,_)) + EXPECT_CALL(*g_fileIOMock, curl_easy_perform(_)) .Times(1) - .WillOnce(Return(-1)); - EXPECT_EQ(T2ERROR_FAILURE, doHttpGet("https://test.com", &data)); + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*g_fileIOMock, curl_easy_getinfo_mock(_,_,_)) + .Times(::testing::AtLeast(1)) + .WillRepeatedly([](CURL* curl, CURLINFO info, void* response_code) { + if (info == CURLINFO_RESPONSE_CODE) { + *(long*)response_code = 404; + } + return CURLE_OK; + }); + + //Add curl_easy_cleanup expectation + EXPECT_CALL(*g_fileIOMock, curl_easy_cleanup(_)) + .Times(::testing::AnyNumber()); + EXPECT_EQ(T2ERROR_PROFILE_NOT_SET, doHttpGet("https://test.com", &data)); } TEST_F(xconfclientTestFixture, doHttpGet4) { char* data = NULL; - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(2) - .WillOnce(Return(0)) - .WillOnce(Return(0)); - #if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER) + + EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Invoke([]() { + // Return unique fake handles for each call + static int handle_counter = 1; + return (CURL*)(uintptr_t)(0x1000 + handle_counter++); + })); + +#if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER) #if defined(WAN_FAILOVER_SUPPORTED) || defined(FEATURE_RDKB_CONFIGURABLE_WAN_INTERFACE) EXPECT_CALL(*m_xconfclientMock, getParameterValue(_,_)) - .Times(1) .WillOnce([](const char* paramName, char** paramValue) { if (strcmp(paramName, "Device.X_RDK_WanManager.CurrentActiveInterface") == 0) *paramValue = strdup("erouter0"); @@ -476,97 +549,43 @@ TEST_F(xconfclientTestFixture, doHttpGet4) }); #endif #endif + EXPECT_CALL(*m_xconfclientMock, isMtlsEnabled()) .Times(1) .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, fork()) + EXPECT_CALL(*g_fileIOMock, curl_easy_perform(_)) .Times(1) - .WillOnce(Return(1)); - EXPECT_CALL(*g_fileIOMock, close(_)) - .Times(3) - .WillOnce(Return(0)) - .WillOnce(Return(0)) - .WillOnce(Return(0)); + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*g_fileIOMock, curl_easy_getinfo_mock(_,_,_)) + .Times(::testing::AtLeast(1)) + .WillRepeatedly([](CURL* curl, CURLINFO info, void* response_code) { + if (info == CURLINFO_RESPONSE_CODE) { + *(long*)response_code = 302; + } + return CURLE_OK; + }); - EXPECT_CALL(*g_fileIOMock, read(_, _, _)) - .WillOnce([](int fd, void* buf, size_t count) { - if (count == sizeof(T2ERROR)) { - *static_cast(buf) = T2ERROR_SUCCESS; - } - return static_cast(sizeof(T2ERROR)); // Simulate full read - }) - .WillOnce(Return(-1)); - EXPECT_EQ(T2ERROR_FAILURE, doHttpGet("https://test.com", &data)); + //Add curl_easy_cleanup expectation + EXPECT_CALL(*g_fileIOMock, curl_easy_cleanup(_)) + .Times(::testing::AnyNumber()); + EXPECT_EQ(T2ERROR_FAILURE, doHttpGet("https://test.com", &data)); } TEST_F(xconfclientTestFixture, doHttpGet5) { char* data = NULL; - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(2) - .WillOnce(Return(0)) - .WillOnce(Return(0)); - #if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER) -#if defined(WAN_FAILOVER_SUPPORTED) || defined(FEATURE_RDKB_CONFIGURABLE_WAN_INTERFACE) - EXPECT_CALL(*m_xconfclientMock, getParameterValue(_,_)) - .Times(1) - .WillOnce([](const char* paramName, char** paramValue) { - if (strcmp(paramName, "Device.X_RDK_WanManager.CurrentActiveInterface") == 0) - *paramValue = strdup("erouter0"); - else - *paramValue = strdup("unknown"); - return T2ERROR_SUCCESS; - }); -#endif -#endif - EXPECT_CALL(*m_xconfclientMock, isMtlsEnabled()) - .Times(1) - .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, fork()) - .Times(1) - .WillOnce(Return(1)); - EXPECT_CALL(*g_fileIOMock, close(_)) - .Times(4) - .WillOnce(Return(0)) - .WillOnce(Return(0)) - .WillOnce(Return(0)) - .WillOnce(Return(0)); - - EXPECT_CALL(*g_fileIOMock, read(_, _, _)) - .WillOnce([](int fd, void* buf, size_t count) { - if (count == sizeof(T2ERROR)) { - *static_cast(buf) = T2ERROR_SUCCESS; - } - return static_cast(sizeof(T2ERROR)); // Simulate full read - }) - .WillOnce([](int fd, void* buf, size_t count) { - if (count == sizeof(size_t)) { - *static_cast(buf) = 12345; - } - return static_cast(sizeof(size_t)); // Simulate full read - }); - - EXPECT_CALL(*g_fileIOMock, fopen(_,_)) - .Times(1) - .WillOnce(Return((FILE*)0XFFFFFFFF)); - EXPECT_CALL(*g_fileIOMock, fread(_,_,_,_)) - .Times(1) - .WillOnce(Return(-1)); - EXPECT_EQ(T2ERROR_FAILURE, doHttpGet("https://test.com", &data)); -} + EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Invoke([]() { + // Return unique fake handles for each call + static int handle_counter = 1; + return (CURL*)(uintptr_t)(0x1000 + handle_counter++); + })); -TEST_F(xconfclientTestFixture, doHttpGet6) -{ - char* data = NULL; - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(2) - .WillOnce(Return(0)) - .WillOnce(Return(0)); - #if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER) +#if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER) #if defined(WAN_FAILOVER_SUPPORTED) || defined(FEATURE_RDKB_CONFIGURABLE_WAN_INTERFACE) EXPECT_CALL(*m_xconfclientMock, getParameterValue(_,_)) - .Times(1) .WillOnce([](const char* paramName, char** paramValue) { if (strcmp(paramName, "Device.X_RDK_WanManager.CurrentActiveInterface") == 0) *paramValue = strdup("erouter0"); @@ -576,54 +595,49 @@ TEST_F(xconfclientTestFixture, doHttpGet6) }); #endif #endif + EXPECT_CALL(*m_xconfclientMock, isMtlsEnabled()) .Times(1) .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, fork()) + // Mock curl_easy_perform to simulate receiving data + EXPECT_CALL(*g_fileIOMock, curl_easy_perform(_)) .Times(1) - .WillOnce(Return(1)); - EXPECT_CALL(*g_fileIOMock, close(_)) - .Times(4) - .WillOnce(Return(0)) - .WillOnce(Return(0)) - .WillOnce(Return(0)) - .WillOnce(Return(0)); + .WillOnce(::testing::Invoke([](CURL* handle) { + // Simulate curl calling the write callback with response data + const char* test_response = "{\"status\":\"success\",\"data\":\"test response data\"}"; + + // In a real scenario, curl would call the callback set via CURLOPT_WRITEFUNCTION + // For testing, we need to directly populate the response structure + // This is tricky because we need access to the response pointer + + return CURLE_OK; + })); + EXPECT_CALL(*g_fileIOMock, curl_easy_getinfo_mock(_,_,_)) + .Times(::testing::AtLeast(1)) + .WillRepeatedly([](CURL* curl, CURLINFO info, void* response_code) { + if (info == CURLINFO_RESPONSE_CODE) { + *(long*)response_code = 200; + } + return CURLE_OK; + }); - EXPECT_CALL(*g_fileIOMock, read(_, _, _)) - .WillOnce([](int fd, void* buf, size_t count) { - if (count == sizeof(T2ERROR)) { - *static_cast(buf) = T2ERROR_SUCCESS; - } - return static_cast(sizeof(T2ERROR)); // Simulate full read - }) - .WillOnce([](int fd, void* buf, size_t count) { - if (count == sizeof(size_t)) { - *static_cast(buf) = 12345; - } - return static_cast(sizeof(size_t)); // Simulate full read - }); - - EXPECT_CALL(*g_fileIOMock, fopen(_,_)) - .Times(1) - .WillOnce(Return((FILE*)0XFFFFFFFF)); - EXPECT_CALL(*g_fileIOMock, fread(_,_,_,_)) - .Times(1) - .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, fclose(_)) - .Times(1) - .WillOnce(Return(0)); - - EXPECT_EQ(T2ERROR_SUCCESS, doHttpGet("https://test.com", &data)); + //Add curl_easy_cleanup expectation + EXPECT_CALL(*g_fileIOMock, curl_easy_cleanup(_)) + .Times(::testing::AnyNumber()); + EXPECT_EQ(T2ERROR_SUCCESS, doHttpGet("https://test.com", &data)); } -//child process TEST_F(xconfclientTestFixture, doHttpGet7) { char* data = NULL; - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(2) - .WillOnce(Return(0)) - .WillOnce(Return(0)); + + EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Invoke([]() { + // Return unique fake handles for each call + static int handle_counter = 1; + return (CURL*)(uintptr_t)(0x1000 + handle_counter++); + })); #if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER) #if defined(WAN_FAILOVER_SUPPORTED) || defined(FEATURE_RDKB_CONFIGURABLE_WAN_INTERFACE) EXPECT_CALL(*m_xconfclientMock, getParameterValue(_,_)) @@ -639,21 +653,11 @@ TEST_F(xconfclientTestFixture, doHttpGet7) #endif EXPECT_CALL(*m_xconfclientMock, isMtlsEnabled()) .Times(1) - .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, fork()) - .Times(1) - .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .WillOnce(Return(1)); + EXPECT_CALL(*g_systemMock, access(_,_)) .Times(1) - .WillOnce(Return((CURL*)0XFFFFFFFF)); - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) - .Times(6) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_OK)); + .WillOnce(Return(1)); + EXPECT_CALL(*g_fileIOMock, curl_easy_perform(_)) .Times(1) .WillOnce(Return(CURLE_OK)); @@ -666,31 +670,21 @@ TEST_F(xconfclientTestFixture, doHttpGet7) } return CURLE_OK; }); - EXPECT_CALL(*g_fileIOMock, close(_)) - .Times(4) - .WillOnce(Return(0)) - .WillOnce(Return(0)) - .WillOnce(Return(0)) - .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, write(_,_,_)) - .Times(2) - .WillOnce(Return(0)) - .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, fopen(_,_)) - .Times(1) - .WillOnce(Return((FILE*)NULL)); - EXPECT_CALL(*g_fileIOMock, curl_easy_cleanup(_)) - .Times(1); - EXPECT_THROW(doHttpGet("https://test.com", &data), std::runtime_error); + + EXPECT_EQ(doHttpGet("https://test.com", &data), T2ERROR_SUCCESS); } TEST_F(xconfclientTestFixture, doHttpGet8) { char* data = NULL; - EXPECT_CALL(*g_fileIOMock, pipe(_)) - .Times(2) - .WillOnce(Return(0)) - .WillOnce(Return(0)); + + EXPECT_CALL(*g_fileIOMock, curl_easy_init()) + .Times(::testing::AnyNumber()) + .WillRepeatedly(::testing::Invoke([]() { + // Return unique fake handles for each call + static int handle_counter = 1; + return (CURL*)(uintptr_t)(0x1000 + handle_counter++); + })); #if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER) #if defined(WAN_FAILOVER_SUPPORTED) || defined(FEATURE_RDKB_CONFIGURABLE_WAN_INTERFACE) EXPECT_CALL(*m_xconfclientMock, getParameterValue(_,_)) @@ -710,24 +704,7 @@ TEST_F(xconfclientTestFixture, doHttpGet8) EXPECT_CALL(*g_systemMock, access(_,_)) .Times(1) .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, fork()) - .Times(1) - .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, curl_easy_init()) - .Times(1) - .WillOnce(Return((CURL*)0XFFFFFFFF)); - EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) - .Times(10) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_OK)) - .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*g_fileIOMock, curl_easy_perform(_)) .Times(1) .WillOnce(Return(CURLE_OK)); @@ -740,25 +717,9 @@ TEST_F(xconfclientTestFixture, doHttpGet8) } return CURLE_OK; }); - EXPECT_CALL(*g_fileIOMock, close(_)) - .Times(4) - .WillOnce(Return(0)) - .WillOnce(Return(0)) - .WillOnce(Return(0)) - .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, write(_,_,_)) - .Times(2) - .WillOnce(Return(0)) - .WillOnce(Return(0)); - EXPECT_CALL(*g_fileIOMock, fopen(_,_)) - .Times(1) - .WillOnce(Return((FILE*)NULL)); - EXPECT_CALL(*g_fileIOMock, curl_easy_cleanup(_)) - .Times(1); - EXPECT_THROW(doHttpGet("https://test.com", &data), std::runtime_error); -} -#endif + EXPECT_EQ(doHttpGet("https://test.com", &data), T2ERROR_SUCCESS); +} TEST_F(xconfclientTestFixture, initXConfClient_failure) { diff --git a/source/utils/t2MtlsUtils.c b/source/utils/t2MtlsUtils.c index b52933b5..a8a8e5b6 100644 --- a/source/utils/t2MtlsUtils.c +++ b/source/utils/t2MtlsUtils.c @@ -30,9 +30,11 @@ #include "t2log_wrapper.h" #include "t2common.h" +#ifndef LIBRDKCERTSEL_BUILD #ifdef LIBRDKCONFIG_BUILD #include "rdkconfig.h" #endif +#endif #if !defined(ENABLE_RDKC_SUPPORT) diff --git a/source/xconf-client/Makefile.am b/source/xconf-client/Makefile.am index f7be2830..201526b7 100644 --- a/source/xconf-client/Makefile.am +++ b/source/xconf-client/Makefile.am @@ -24,7 +24,7 @@ libxconfclient_la_SOURCES = xconfclient.c libxconfclient_la_LDFLAGS = -shared -fPIC -lcjson -lcurl if IS_LIBRDKCERTSEL_ENABLED libxconfclient_la_CFLAGS = $(LIBRDKCERTSEL_FLAG) -libxconfclient_la_LDFLAGS += -lRdkCertSelector +libxconfclient_la_LDFLAGS += -lRdkCertSelector -lrdkconfig endif libxconfclient_la_LIBADD = ${top_builddir}/source/t2parser/libt2parser.la ${top_builddir}/source/ccspinterface/libccspinterface.la libxconfclient_la_CPPFLAGS = -fPIC -I${PKG_CONFIG_SYSROOT_DIR}$(includedir)/dbus-1.0 \ diff --git a/source/xconf-client/xconfclient.c b/source/xconf-client/xconfclient.c index c1871637..b2cc656c 100644 --- a/source/xconf-client/xconfclient.c +++ b/source/xconf-client/xconfclient.c @@ -50,9 +50,11 @@ #include "rdkcertselector.h" #define FILESCHEME "file://" #endif +#ifndef LIBRDKCERTSEL_BUILD #ifdef LIBRDKCONFIG_BUILD #include "rdkconfig.h" #endif +#endif #define RFC_RETRY_TIMEOUT 60 #define XCONF_RETRY_TIMEOUT 180 #define MAX_XCONF_RETRY_COUNT 5 @@ -274,7 +276,15 @@ static char *getTimezone () free(zoneValue); zoneValue = NULL ; } - zoneValue = strdup(zone); + if (zone != NULL && strlen(zone) > 0) + { + zoneValue = strdup(zone); + } + else + { + zoneValue = NULL; + T2Warning("Warning: zone is NULL or empty, skipping\n"); + } } fclose(file); free(zone); @@ -534,11 +544,11 @@ T2ERROR doHttpGet(char* httpsUrl, char **data) ret = http_pool_get(httpsUrl, data, true); if(ret == T2ERROR_SUCCESS) { - T2Info("HTTP GET request completed successfully using connection pool\n"); + T2Debug("HTTP GET request completed successfully\n"); } else { - T2Error("Failed to perform HTTP GET request using connection pool\n"); + T2Error("Failed to perform HTTP GET request\n"); } T2Debug("%s --out\n", __FUNCTION__); return ret; diff --git a/test/functional-tests/tests/helper_functions.py b/test/functional-tests/tests/helper_functions.py index a287d426..214fb619 100644 --- a/test/functional-tests/tests/helper_functions.py +++ b/test/functional-tests/tests/helper_functions.py @@ -26,6 +26,42 @@ import signal from urllib.parse import unquote, quote, urlparse, parse_qsl +# Certificate configuration for mTLS +# Check for combined PEM files (cert+key in one file) first, then separate files +CERT_COMBINED_PATHS = [ + '/opt/certs/client.pem', + '/etc/pki/Test-RDK-root/Test-RDK-client-ICA/certs/rdkclient.pem', +] + +CERT_SEPARATE_PATHS = [ + ('/opt/certs/client.pem', '/opt/certs/client.key'), + ('/etc/pki/Test-RDK-root/Test-RDK-client-ICA/certs/rdkclient.pem', '/etc/pki/Test-RDK-root/Test-RDK-client-ICA/private/rdkclient.key'), +] + +# Find the first valid certificate +CERT_PATH = None + +# Try combined PEM files first (cert+key in single file) +for cert_file in CERT_COMBINED_PATHS: + if os.path.exists(cert_file): + CERT_PATH = cert_file + print(f"Using combined client certificate+key: {cert_file}") + break + +# If no combined file found, try separate cert and key files +if CERT_PATH is None: + for cert_file, key_file in CERT_SEPARATE_PATHS: + if os.path.exists(cert_file) and os.path.exists(key_file): + CERT_PATH = (cert_file, key_file) + print(f"Using client certificate: {cert_file} with key: {key_file}") + break + +if CERT_PATH is None: + print("WARNING: No valid client certificate found!") + +# CA certificate for server verification +CA_BUNDLE = '/etc/ssl/certs/ca-certificates.crt' # System CA bundle + def run_telemetry(): return subprocess.run("/usr/local/bin/telemetry2_0", shell=True) @@ -75,12 +111,12 @@ def sigterm_telemetry(pid): def adminSupport_cache(save: bool = True): - if(requests.get(ADMIN_SUPPORT_SET, verify=False,params={ADMIN_CACHE_ARG: str(save).lower()})): + if(requests.get(ADMIN_SUPPORT_SET, verify=CA_BUNDLE, cert=CERT_PATH, params={ADMIN_CACHE_ARG: str(save).lower()})): return True return False def adminSupport_requestData(): - return_data = requests.get(ADMIN_SUPPORT_GET, verify=False,params={ADMIN_RQUEST_DATA: "true"}) + return_data = requests.get(ADMIN_SUPPORT_GET, verify=CA_BUNDLE, cert=CERT_PATH, params={ADMIN_RQUEST_DATA: "true"}) try: return_json = return_data.json() except: @@ -146,34 +182,18 @@ def check_Rbus_data(): result = subprocess.run(RBUSCLI_GET_CMD+T2_TEMP_REPORT_PROFILE_PARAM, shell=True, capture_output=True, text=True) assert RBUS_EXCEPTION_STRING not in result.stdout -def adminSupport_DLXconf_requestData(): - return_data = requests.get(ADMIN_SUPPORT_URL, verify=False,params={ADMIN_RQUEST_DATA: "true"}) - try: - return_json = return_data.json() - except: - return_json = [] - return return_json - -def adminSupport_DLReport_requestData(): - return_data = requests.get(ADMIN_SUPPORT_URL, verify=False,params={ADMIN_RQUEST_DATA: "true"}) - try: - return_json = return_data.json() - except: - return_json = [] - return return_json - def adminSupport_DLXconf_cache(save:bool = True): - if(requests.get(DL_ADMIN_URL +"Xconf", verify=False,params={ADMIN_CACHE_ARG: str(save).lower()})): + if(requests.get(DL_ADMIN_URL +"Xconf", verify=CA_BUNDLE, cert=CERT_PATH, params={ADMIN_CACHE_ARG: str(save).lower()})): return True return False def adminSupport_DLReport_cache(save:bool = True): - if(requests.get(DL_ADMIN_URL +"Report", verify=False,params={ADMIN_CACHE_ARG: str(save).lower()})): + if(requests.get(DL_ADMIN_URL +"Report", verify=CA_BUNDLE, cert=CERT_PATH, params={ADMIN_CACHE_ARG: str(save).lower()})): return True return False def adminSupport_DLXconf_requestData(): - return_data = requests.get(DL_ADMIN_URL+"Xconf", verify=False,params={ADMIN_RQUEST_DATA: "true"}) + return_data = requests.get(DL_ADMIN_URL+"Xconf", verify=CA_BUNDLE, cert=CERT_PATH, params={ADMIN_RQUEST_DATA: "true"}) try: return_json = return_data.json() except: @@ -181,7 +201,7 @@ def adminSupport_DLXconf_requestData(): return return_json def adminSupport_DLReport_requestData(): - return_data = requests.get(DL_ADMIN_URL+"Report", verify=False,params={ADMIN_RQUEST_DATA: "true"}) + return_data = requests.get(DL_ADMIN_URL+"Report", verify=CA_BUNDLE, cert=CERT_PATH, params={ADMIN_RQUEST_DATA: "true"}) try: return_json = return_data.json() except: diff --git a/test/functional-tests/tests/report_profiles.py b/test/functional-tests/tests/report_profiles.py index 5e8836ee..54359306 100644 --- a/test/functional-tests/tests/report_profiles.py +++ b/test/functional-tests/tests/report_profiles.py @@ -1234,7 +1234,7 @@ "MaxUploadLatency": 10 }, "HTTP": { - "URL": "https://mockxconf:50051/dataLookeMock/", + "URL": "https://mockxconf:50051/dataLakeMockXconf/", "Compression": "None", "Method": "POST", "RequestURIParameter": [ diff --git a/test/functional-tests/tests/test_multiprofile_msgpacket.py b/test/functional-tests/tests/test_multiprofile_msgpacket.py index 7ff35191..c455085f 100644 --- a/test/functional-tests/tests/test_multiprofile_msgpacket.py +++ b/test/functional-tests/tests/test_multiprofile_msgpacket.py @@ -71,7 +71,6 @@ def test_without_namefield(): assert LOG_MSG not in grep_T2logs(LOG_MSG) #Empty string in namefield assert HASH_ERROR_MSG in grep_T2logs(HASH_ERROR_MSG) #without hash field - #negative case without hashvalue, without version field & without Protocol field @pytest.mark.run(order=2) def test_without_hashvalue(): diff --git a/test/run_l2.sh b/test/run_l2.sh index 7cfe5165..00bcdc97 100755 --- a/test/run_l2.sh +++ b/test/run_l2.sh @@ -33,8 +33,17 @@ fi gcc test/functional-tests/tests/app.c -o test/functional-tests/tests/t2_app -ltelemetry_msgsender -lt2utils +final_result=0 # removing --exitfirst flag as it is causing the test to exit after first failure -pytest -v --json-report --json-report-summary --json-report-file $RESULT_DIR/runs_as_daemon.json test/functional-tests/tests/test_runs_as_daemon.py -pytest -v --json-report --json-report-summary --json-report-file $RESULT_DIR/bootup_sequence.json test/functional-tests/tests/test_bootup_sequence.py -pytest -v --json-report --json-report-summary --json-report-file $RESULT_DIR/xconf_communications.json test/functional-tests/tests/test_xconf_communications.py --exitfirst -pytest -v --json-report --json-report-summary --json-report-file $RESULT_DIR/msg_packet.json test/functional-tests/tests/test_multiprofile_msgpacket.py --exitfirst +pytest -v --json-report --json-report-summary --json-report-file $RESULT_DIR/runs_as_daemon.json test/functional-tests/tests/test_runs_as_daemon.py || final_result=1 +pytest -v --json-report --json-report-summary --json-report-file $RESULT_DIR/bootup_sequence.json test/functional-tests/tests/test_bootup_sequence.py || final_result=1 +pytest -v --json-report --json-report-summary --json-report-file $RESULT_DIR/xconf_communications.json test/functional-tests/tests/test_xconf_communications.py || final_result=1 +pytest -v --json-report --json-report-summary --json-report-file $RESULT_DIR/msg_packet.json test/functional-tests/tests/test_multiprofile_msgpacket.py || final_result=1 + +if [ $final_result -ne 0 ]; then + echo "Some tests failed. Please check the JSON reports in $RESULT_DIR for details." +else + echo "All tests passed successfully." +fi + +exit $final_result diff --git a/test/test-artifacts/mockxconf/xconf-dcm-response2.json b/test/test-artifacts/mockxconf/xconf-dcm-response2.json deleted file mode 100644 index e2deb481..00000000 --- a/test/test-artifacts/mockxconf/xconf-dcm-response2.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "urn:settings:GroupName": "Generic", - "urn:settings:CheckOnReboot": false, - "urn:settings:TimeZoneMode": "UTC", - "urn:settings:CheckSchedule:cron": "37 5 * * *", - "urn:settings:CheckSchedule:DurationMinutes": 300, - "urn:settings:LogUploadSettings:Message": null, - "urn:settings:LogUploadSettings:Name": "Generic", - "urn:settings:LogUploadSettings:NumberOfDays": 0, - "urn:settings:LogUploadSettings:UploadRepositoryName": "S3", - "urn:settings:LogUploadSettings:UploadRepository:URL": "https://secure.s3.bucket.test.url", - "urn:settings:LogUploadSettings:UploadRepository:uploadProtocol": "HTTP", - "urn:settings:LogUploadSettings:UploadOnReboot": false, - "urn:settings:LogUploadSettings:UploadImmediately": false, - "urn:settings:LogUploadSettings:upload": true, - "urn:settings:LogUploadSettings:UploadSchedule:cron": "36 6 * * *", - "urn:settings:LogUploadSettings:UploadSchedule:levelone:cron": null, - "urn:settings:LogUploadSettings:UploadSchedule:leveltwo:cron": null, - "urn:settings:LogUploadSettings:UploadSchedule:levelthree:cron": null, - "urn:settings:LogUploadSettings:UploadSchedule:TimeZoneMode": "UTC", - "urn:settings:LogUploadSettings:UploadSchedule:DurationMinutes": 240, - "urn:settings:VODSettings:Name": null, - "urn:settings:VODSettings:LocationsURL": null, - "urn:settings:VODSettings:SRMIPList": null, - "urn:settings:TelemetryProfile": { - "@type": "PermanentTelemetryProfile", - "id": "9e9fce57-8053-423e-a647-a537ca8b7899", - "telemetryProfile": [ - { - "header":"IUI_accum", - "content":"Device.DeviceInfo.X_RDKCENTRAL-COM.IUI.Version", - "type":"", - "pollingFrequency":"0" - }, - { - "header":"Test_datamodel_1", - "content":"Device.X_RDK_WebConfig.webcfgSubdocForceReset", - "type":"", - "pollingFrequency":"3" - }, - { - "header": "SYS_GREP_TEST", - "content": "This log file is for previous logs", - "type": "session0.txt", - "pollingFrequency": "0" - }, - { - "header": "SYS_GREP_TEST_2", - "content": "This log file is for previous logs", - "type": "session0.txt", - "pollingFrequency": "0" - }, - { - "header": "SYS_TEST_ReportUpload", - "content": "uploading report", - "type": "core_log.txt", - "pollingFrequency": "0" - }, - { - "header": "SYS_EVENT_TEST_accum", - "content": "generic", - "type": "", - "pollingFrequency": "0" - } - ], - "schedule": "*/1 * * * *", - "expires": 0, - "telemetryProfile:name": "NEW TEST PROFILE", - "uploadRepository:URL": "https://mock1xconf:50051/dataLakeMockXconf", - "uploadRepository:uploadProtocol": "HTTP" - } -}