diff --git a/source/AdvSecurityDml/Makefile.am b/source/AdvSecurityDml/Makefile.am index 92a273c..da1e1c3 100644 --- a/source/AdvSecurityDml/Makefile.am +++ b/source/AdvSecurityDml/Makefile.am @@ -26,7 +26,7 @@ hardware_platform = i686-linux-gnu lib_LTLIBRARIES = libdmlasecurity.la libdmlasecurity_la_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/source/AdvSecurityDml -I$(top_srcdir)/source/AdvSecuritySsp $(CPPFLAGS) -I$(top_srcdir)/../Utopia/source/include/sysevent -I${PKG_CONFIG_SYSROOT_DIR}$(includedir)/rbus libdmlasecurity_la_SOURCES = plugin_main.c cosa_adv_security_internal.c cosa_adv_security_dml.c cosa_adv_security_webconfig.c advsecurity_helpers.c advsecurity_param.c -libdmlasecurity_la_LDFLAGS = -lccsp_common -lsyscfg -lsysevent -lwebconfig_framework -lmsgpackc -ltrower-base64 -lsecure_wrapper -lrbus -lev -lpthread $(SSP_LDFLAGS) +libdmlasecurity_la_LDFLAGS = -lccsp_common -lsyscfg -lsysevent -lwebconfig_framework -lmsgpackc -ltrower-base64 -lsecure_wrapper -lrbus -lev -lpthread -ltelemetry_msgsender $(SSP_LDFLAGS) if WIFI_DATA_COLLECTION libdmlasecurity_la_CPPFLAGS += -DDML_SUPPORT -DWIFI_DATA_COLLECTION diff --git a/source/AdvSecurityDml/cosa_adv_security_internal.c b/source/AdvSecurityDml/cosa_adv_security_internal.c index 6dcb5c6..0564b00 100644 --- a/source/AdvSecurityDml/cosa_adv_security_internal.c +++ b/source/AdvSecurityDml/cosa_adv_security_internal.c @@ -69,6 +69,10 @@ #define ADVSEC_SYSEVENT_CURRENT_WAN_IFNAME_EVENT "current_wan_ifname" #define LEVL_DML "Device.WiFi.Levl" +#define SPEEDTEST_STATUS_DML "Device.IP.Diagnostics.X_RDKCENTRAL-COM_SpeedTest.Status" +#define SPEEDTEST_TIMEOUT_DML "Device.IP.Diagnostics.X_RDK_SpeedTest.SubscriberUnPauseTimeOut" +#define ADVSEC_NETWORKINTELLIGENCE_ENABLED_PATH "/tmp/advsec_networkintelligence_enabled" +#define CUJONICLI_SET_QOSD_ENABLE_CMD "/usr/bin/cujo-ni-cli '{\"method\":\"set_configs\", \"configs\": {\"cujoniqos.daemon.enable\": %d}}' &" #define ADVSEC_WAIT_FOR_TIMEOUT (60 * 60) #define MAX_VALUE 32 @@ -155,6 +159,11 @@ static char *g_RaptrEnabled = "Adv_RaptrRFCEnable"; #ifdef NETWORK_INTELLIGENCE static char *g_AdvSecNetworkIntelligenceEnabled = "Adv_AdvSecNetworkIntelligenceRFCEnable"; static char *g_NetworkIntelligenceMemoryLimit = "Advsecurity_NetworkIntelligenceMemoryLimit"; +STATIC pthread_mutex_t ni_speedtest_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t ni_speedtest_cond = PTHREAD_COND_INITIALIZER; +static struct timespec ni_speedtest_timeout; +STATIC BOOL ni_speedtest_thread_running = FALSE; +static BOOL ni_speedtest_wake_early = FALSE; static char *g_NetworkIntelligenceActivate = "Adv_AdvSecNetworkIntelligenceActivate"; #endif #ifdef WIFI_DATA_COLLECTION @@ -434,6 +443,215 @@ static void eventReceiveHandler( } #endif +#ifdef NETWORK_INTELLIGENCE +static BOOL is_ni_enabled_and_activated(void) +{ + struct stat st; + + if (stat(ADVSEC_NETWORKINTELLIGENCE_ENABLED_PATH, &st) != 0) + { + return FALSE; + } + if (stat(ADVSEC_NETWORKINTELLIGENCE_ACTIVATED_PATH, &st) != 0) + { + return FALSE; + } + return TRUE; +} + +static BOOL ni_qosd_enable(BOOL enable) +{ + errno_t rc; + + rc = v_secure_system(CUJONICLI_SET_QOSD_ENABLE_CMD, enable ? 1 : 0); + if (!WIFEXITED(rc) || WEXITSTATUS(rc) != 0) + { + CcspTraceError(("%s: cujo-ni-cli set_configs cujoniqos.daemon.enable=%d failed, rc=%d\n", + __FUNCTION__, enable ? 1 : 0, WEXITSTATUS(rc))); + return FALSE; + } + if (enable) + { + t2_event_d("SYS_INFO_CUJO_NI_resume", 1); + } + else + { + t2_event_d("SYS_INFO_CUJO_NI_pause", 1); + } + return TRUE; +} + +static void *ni_speedtest_handler(void *arg) +{ + int waitStatus = 0; + BOOL timedOut; + + (void)arg; + + pthread_detach(pthread_self()); + + pthread_mutex_lock(&ni_speedtest_mutex); + if (!ni_qosd_enable(FALSE)) + { + CcspTraceError(("%s: failed to pause Network Intelligence for speedtest\n", __FUNCTION__)); + ni_speedtest_thread_running = FALSE; + pthread_mutex_unlock(&ni_speedtest_mutex); + return NULL; + } + + while (!ni_speedtest_wake_early && waitStatus != ETIMEDOUT) + { + waitStatus = pthread_cond_timedwait(&ni_speedtest_cond, &ni_speedtest_mutex, &ni_speedtest_timeout); + if (waitStatus != 0 && waitStatus != ETIMEDOUT) + { + CcspTraceError(("%s: pthread_cond_timedwait failed, error=%d\n", __FUNCTION__, waitStatus)); + break; + } + } + timedOut = (waitStatus == ETIMEDOUT); + ni_speedtest_wake_early = FALSE; + + if (timedOut) + { + CcspTraceWarning(("%s: speedtest timeout expired, resuming Network Intelligence\n", __FUNCTION__)); + t2_event_d("IMP_CUJO_NI_SubscriberUnPauseTimeOut", 1); + } + if (!ni_qosd_enable(TRUE)) + { + CcspTraceError(("%s: failed to resume Network Intelligence after speedtest\n", __FUNCTION__)); + } + + ni_speedtest_thread_running = FALSE; + pthread_mutex_unlock(&ni_speedtest_mutex); + + return NULL; +} + +static BOOL ni_speedtest_trigger(uint32_t timeout) +{ + struct timespec ni_resume_timeout; + pthread_t tid; + int err; + + if (clock_gettime(CLOCK_REALTIME, &ni_resume_timeout) != 0) + { + CcspTraceError(("%s: clock_gettime failed, errno=%d\n", __FUNCTION__, errno)); + return FALSE; + } + ni_resume_timeout.tv_sec += timeout; + + pthread_mutex_lock(&ni_speedtest_mutex); + if (ni_speedtest_thread_running) + { + ni_speedtest_timeout = ni_resume_timeout; + pthread_cond_signal(&ni_speedtest_cond); + pthread_mutex_unlock(&ni_speedtest_mutex); + CcspTraceInfo(("%s: Network Intelligence already paused for speedtest, refreshed timeout\n", __FUNCTION__)); + return TRUE; + } + + ni_speedtest_timeout = ni_resume_timeout; + + ni_speedtest_wake_early = FALSE; + ni_speedtest_thread_running = TRUE; + + err = pthread_create(&tid, NULL, ni_speedtest_handler, NULL); + if (err != 0) + { + ni_speedtest_thread_running = FALSE; + pthread_mutex_unlock(&ni_speedtest_mutex); + CcspTraceError(("%s: failed to create speedtest timer thread, error=%d\n", __FUNCTION__, err)); + return FALSE; + } + pthread_mutex_unlock(&ni_speedtest_mutex); + return TRUE; +} + +static void ni_speedtest_complete(void) +{ + pthread_mutex_lock(&ni_speedtest_mutex); + if (ni_speedtest_thread_running) + { + ni_speedtest_wake_early = TRUE; + pthread_cond_signal(&ni_speedtest_cond); + } + pthread_mutex_unlock(&ni_speedtest_mutex); +} + +static BOOL speedtestGetTimeout(uint32_t *timeout) +{ + rbusValue_t value = NULL; + int ret; + + ret = rbus_get(rbus_handle, SPEEDTEST_TIMEOUT_DML, &value); + if (ret != RBUS_ERROR_SUCCESS) + { + CcspTraceError(("%s: rbus_get failed for %s, error=%d\n", __FUNCTION__, SPEEDTEST_TIMEOUT_DML, ret)); + if (value != NULL) + { + rbusValue_Release(value); + } + return FALSE; + } + + *timeout = rbusValue_GetUInt32(value); + rbusValue_Release(value); + + return TRUE; +} + +STATIC void speedtestEventReceiveHandler( + rbusHandle_t handle, + rbusEvent_t const* event, + rbusEventSubscription_t* subscription) +{ + rbusValue_t value; + uint32_t status; + uint32_t timeout; + + (void)handle; + (void)subscription; + + value = rbusObject_GetValue(event->data, NULL); + if (value == NULL) + { + CcspTraceError(("SpeedTest status event has no value\n")); + return; + } + + status = rbusValue_GetUInt32(value); + CcspTraceInfo(("%s: speedtest status event received, status=%u\n", __FUNCTION__, status)); + + if (!is_ni_enabled_and_activated()) + { + CcspTraceInfo(("%s: Network Intelligence is disabled or not activated, skipping speedtest event\n", __FUNCTION__)); + return; + } + + if (status == ST_TR181_STATUS_STARTING) + { + if (!speedtestGetTimeout(&timeout)) + { + CcspTraceError(("%s: failed to get speedtest unpause timeout, Network Intelligence will not be paused\n", __FUNCTION__)); + return; + } + if (timeout == 0) + { + CcspTraceInfo(("%s: speedtest unpause timeout is 0, skipping Network Intelligence pause\n", __FUNCTION__)); + return; + } + if (!ni_speedtest_trigger(timeout)) + { + CcspTraceError(("%s: Network Intelligence speedtest trigger failed, Network Intelligence will not be paused\n", __FUNCTION__)); + } + } + else if (status == ST_TR181_STATUS_COMPLETE) + { + ni_speedtest_complete(); + } +} +#endif + #if defined(WIFI_DATA_COLLECTION) || defined(NETWORK_INTELLIGENCE) static int touch_file(const char *filepath) { @@ -1587,6 +1805,14 @@ CosaSecurityInitialize CcspTraceError(("AdvSecurityEventConsumer: rbusEvent_Subscribe %s failed: %d\n", LEVL_DML, ret)); return ANSC_STATUS_FAILURE; } +#endif +#ifdef NETWORK_INTELLIGENCE + ret = rbusEvent_Subscribe(rbus_handle, SPEEDTEST_STATUS_DML, speedtestEventReceiveHandler, NULL, 0); + if(ret != RBUS_ERROR_SUCCESS) + { + CcspTraceError(("AdvSecurityEventConsumer: rbusEvent_Subscribe %s failed: %d\n", SPEEDTEST_STATUS_DML, ret)); + return ANSC_STATUS_FAILURE; + } #endif return returnStatus; } diff --git a/source/AdvSecurityDml/cosa_adv_security_internal.h b/source/AdvSecurityDml/cosa_adv_security_internal.h index d8cacd4..2389bc1 100644 --- a/source/AdvSecurityDml/cosa_adv_security_internal.h +++ b/source/AdvSecurityDml/cosa_adv_security_internal.h @@ -31,6 +31,7 @@ #include #include "ansc_platform.h" #include "ansc_string_util.h" +#include #define ADVSEC_MIN_LOG_TIMEOUT (60 * 1) #define ADVSEC_MAX_LOG_TIMEOUT (60 * 48) @@ -43,6 +44,8 @@ #define ADVSEC_LogLevel_VERBOSE 4 #define BUFLEN_1024 1024 #define PARTNER_REDIRECTORURL_PARAMNAME "Device.DeviceInfo.X_RDKCENTRAL-COM_Syndication.AdvsecRedirectorURL" +#define ST_TR181_STATUS_STARTING 1 +#define ST_TR181_STATUS_COMPLETE 5 typedef enum { ADVSEC_SAFEBROWSING=0, diff --git a/source/AdvSecurityDml/cosa_adv_security_webconfig.c b/source/AdvSecurityDml/cosa_adv_security_webconfig.c index 7e4a8fb..38d4625 100644 --- a/source/AdvSecurityDml/cosa_adv_security_webconfig.c +++ b/source/AdvSecurityDml/cosa_adv_security_webconfig.c @@ -202,7 +202,7 @@ pErr advsec_webconfig_process_request(void *Data) int advsec_webconfig_rollback() { // return 0 to notify framework when rollback is success - CcspTraceInfo((" Entering %s \n",__FUNCTION__)); + CcspTraceInfo(("Entering %s\n", __FUNCTION__)); int ret = 0; @@ -215,7 +215,7 @@ int advsec_webconfig_rollback() void advsec_webconfig_free_resources(void *arg) { - CcspTraceInfo((" Entering %s \n",__FUNCTION__)); + CcspTraceInfo(("Entering %s\n", __FUNCTION__)); execData *blob_exec_data = (execData*) arg; /* CID 158652: Dereference before null check */ diff --git a/source/AdvSecurityDml/cosa_network_intelligence_webconfig.c b/source/AdvSecurityDml/cosa_network_intelligence_webconfig.c index 63713ca..936a7fb 100644 --- a/source/AdvSecurityDml/cosa_network_intelligence_webconfig.c +++ b/source/AdvSecurityDml/cosa_network_intelligence_webconfig.c @@ -93,7 +93,7 @@ pErr ni_webconfig_process_request(void *Data) int ni_webconfig_rollback() { // return 0 to notify framework when rollback is success - CcspTraceInfo((" Entering %s \n",__FUNCTION__)); + CcspTraceInfo(("Entering %s\n", __FUNCTION__)); int ret = 0; @@ -105,7 +105,7 @@ int ni_webconfig_rollback() /* Callback function to free webconfig resources */ void ni_webconfig_free_resources(void *arg) { - CcspTraceInfo((" Entering %s \n",__FUNCTION__)); + CcspTraceInfo(("Entering %s\n", __FUNCTION__)); execData *blob_exec_data = (execData*) arg; if ( blob_exec_data != NULL ) diff --git a/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityDmlTest.cpp b/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityDmlTest.cpp index 3525b14..791fe8c 100644 --- a/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityDmlTest.cpp +++ b/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityDmlTest.cpp @@ -41,6 +41,7 @@ class CcspAdvSecurityDmlTestFixture : public ::testing::Test { g_syseventMock = new SyseventMock(); g_webconfigFwMock = new webconfigFwMock(); g_anscWrapperApiMock = new AnscWrapperApiMock(); + g_telemetryMock = new telemetryMock(); } void TearDown() override { @@ -60,6 +61,7 @@ class CcspAdvSecurityDmlTestFixture : public ::testing::Test { delete g_syseventMock; delete g_webconfigFwMock; delete g_anscWrapperApiMock; + delete g_telemetryMock; g_syscfgMock = nullptr; g_securewrapperMock = nullptr; g_msgpackMock = nullptr; @@ -76,6 +78,7 @@ class CcspAdvSecurityDmlTestFixture : public ::testing::Test { g_syseventMock = nullptr; g_webconfigFwMock = nullptr; g_anscWrapperApiMock = nullptr; + g_telemetryMock = nullptr; } }; diff --git a/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityInternalTest.cpp b/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityInternalTest.cpp index e8695fc..357bf1c 100644 --- a/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityInternalTest.cpp +++ b/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityInternalTest.cpp @@ -43,6 +43,7 @@ class CcspAdvSecurityInternalTestFixture : public ::testing::Test { g_syseventMock = new SyseventMock(); g_webconfigFwMock = new webconfigFwMock(); g_anscWrapperApiMock = new AnscWrapperApiMock(); + g_telemetryMock = new telemetryMock(); } void TearDown() override { @@ -62,6 +63,7 @@ class CcspAdvSecurityInternalTestFixture : public ::testing::Test { delete g_syseventMock; delete g_webconfigFwMock; delete g_anscWrapperApiMock; + delete g_telemetryMock; g_syscfgMock = nullptr; g_securewrapperMock = nullptr; g_msgpackMock = nullptr; @@ -78,6 +80,7 @@ class CcspAdvSecurityInternalTestFixture : public ::testing::Test { g_syseventMock = nullptr; g_webconfigFwMock = nullptr; g_anscWrapperApiMock = nullptr; + g_telemetryMock = nullptr; } }; @@ -1441,6 +1444,419 @@ TEST_F(CcspAdvSecurityInternalTestFixture, CosaRabidSetDNSCacheSize) free(g_pAdvSecAgent); } +#ifdef NETWORK_INTELLIGENCE +#include + +extern "C" void speedtestEventReceiveHandler(rbusHandle_t handle, rbusEvent_t const* event, rbusEventSubscription_t* subscription); +/* Internal SpeedTest timer state exposed (non-static in unit-test builds + * via the STATIC macro) purely so tests can synchronize with the + * asynchronous timer thread. No test-only function is defined in + * production source for this. */ +extern "C" pthread_mutex_t ni_speedtest_mutex; +extern "C" BOOL ni_speedtest_thread_running; + +static const char *g_speedtestNIEnabledPath = "/tmp/advsec_networkintelligence_enabled"; +static const char *g_speedtestNIActivatedPath = "/tmp/advsec_networkintelligence_activated"; + +static void CreateSpeedtestNIEnabledAndActivated(void) +{ + FILE *file = fopen(g_speedtestNIEnabledPath, "w"); + if (file) fclose(file); + file = fopen(g_speedtestNIActivatedPath, "w"); + if (file) fclose(file); +} + +static void RemoveSpeedtestNIEnabledAndActivated(void) +{ + remove(g_speedtestNIEnabledPath); + remove(g_speedtestNIActivatedPath); +} + +/* Test-only helper: reads the SpeedTest timer thread's running state + * under its production mutex. */ +static bool IsSpeedtestThreadRunning(void) +{ + BOOL running; + + pthread_mutex_lock(&ni_speedtest_mutex); + running = ni_speedtest_thread_running; + pthread_mutex_unlock(&ni_speedtest_mutex); + return running ? true : false; +} + +/* Polls IsSpeedtestThreadRunning() until the SpeedTest timer thread + * reaches the desired state or maxWaitMs elapses. The thread's work + * (mocked v_secure_system calls) completes in microseconds, so this + * bounds test time while avoiding a fixed, potentially-flaky sleep. */ +static void WaitForSpeedtestThreadState(bool running, int maxWaitMs) +{ + int waited = 0; + while (IsSpeedtestThreadRunning() != running && waited < maxWaitMs) + { + usleep(2000); + waited += 2; + } +} + +TEST_F(CcspAdvSecurityInternalTestFixture, SpeedTest_Status_Starting_PausesAndCompletes_ResumesNI) +{ + int marker = 0; + int timeoutMarker = 0; + int completeMarker = 5; + rbusValue_t value = (rbusValue_t)▮ + rbusValue_t timeoutValue = (rbusValue_t)&timeoutMarker; + rbusValue_t completeValue = (rbusValue_t)&completeMarker; + rbusEvent_t event = {}; + CreateSpeedtestNIEnabledAndActivated(); + + EXPECT_CALL(*g_rbusMock, rbusObject_GetValue(_, _)) + .Times(2) + .WillOnce(Return(value)) + .WillOnce(Return(completeValue)); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(value)) + .Times(1) + .WillOnce(Return(ST_TR181_STATUS_STARTING)); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(completeValue)) + .Times(1) + .WillOnce(Return(ST_TR181_STATUS_COMPLETE)); + EXPECT_CALL(*g_rbusMock, rbus_get(_, StrEq("Device.IP.Diagnostics.X_RDK_SpeedTest.SubscriberUnPauseTimeOut"), _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<2>(timeoutValue), Return(RBUS_ERROR_SUCCESS))); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(timeoutValue)) + .Times(1) + .WillOnce(Return(86400)); + EXPECT_CALL(*g_rbusMock, rbusValue_Release(timeoutValue)) + .Times(1); + EXPECT_CALL(*g_securewrapperMock, v_secure_system(HasSubstr("cujo-ni-cli"), _)) + .Times(2) + .WillRepeatedly(Return(0)); + + /* status=1: starts the timer thread, which pauses NI immediately. */ + speedtestEventReceiveHandler(NULL, &event, NULL); + WaitForSpeedtestThreadState(true, 200); + EXPECT_TRUE(IsSpeedtestThreadRunning()); + + /* status=5: wakes the thread early so it resumes NI and exits. */ + speedtestEventReceiveHandler(NULL, &event, NULL); + WaitForSpeedtestThreadState(false, 500); + EXPECT_FALSE(IsSpeedtestThreadRunning()); + + RemoveSpeedtestNIEnabledAndActivated(); +} + +TEST_F(CcspAdvSecurityInternalTestFixture, SpeedTest_Status_Starting_PauseFails_ClearsRunningState) +{ + int marker = 0; + int timeoutMarker = 0; + rbusValue_t value = (rbusValue_t)▮ + rbusValue_t timeoutValue = (rbusValue_t)&timeoutMarker; + rbusEvent_t event = {}; + CreateSpeedtestNIEnabledAndActivated(); + + EXPECT_CALL(*g_rbusMock, rbusObject_GetValue(_, _)) + .Times(1) + .WillOnce(Return(value)); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(value)) + .Times(1) + .WillOnce(Return(ST_TR181_STATUS_STARTING)); + EXPECT_CALL(*g_rbusMock, rbus_get(_, StrEq("Device.IP.Diagnostics.X_RDK_SpeedTest.SubscriberUnPauseTimeOut"), _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<2>(timeoutValue), Return(RBUS_ERROR_SUCCESS))); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(timeoutValue)) + .Times(1) + .WillOnce(Return(86400)); + EXPECT_CALL(*g_rbusMock, rbusValue_Release(timeoutValue)) + .Times(1); + /* Simulate cujo-ni-cli failing (non-zero exit status) on the pause + * attempt. The thread must still clear ni_speedtest_thread_running + * so future SpeedTest cycles are not permanently blocked. */ + EXPECT_CALL(*g_securewrapperMock, v_secure_system(HasSubstr("cujo-ni-cli"), _)) + .Times(1) + .WillOnce(Return(256)); + + speedtestEventReceiveHandler(NULL, &event, NULL); + WaitForSpeedtestThreadState(false, 500); + EXPECT_FALSE(IsSpeedtestThreadRunning()); + + RemoveSpeedtestNIEnabledAndActivated(); +} + + +TEST_F(CcspAdvSecurityInternalTestFixture, SpeedTest_Status_Starting_DuplicateStart_RefreshesTimeout) +{ + int marker = 0; + int timeoutMarker = 0; + int completeMarker = 5; + rbusValue_t value = (rbusValue_t)▮ + rbusValue_t timeoutValue = (rbusValue_t)&timeoutMarker; + rbusValue_t completeValue = (rbusValue_t)&completeMarker; + rbusEvent_t event = {}; + CreateSpeedtestNIEnabledAndActivated(); + + EXPECT_CALL(*g_rbusMock, rbusObject_GetValue(_, _)) + .Times(3) + .WillOnce(Return(value)) + .WillOnce(Return(value)) + .WillOnce(Return(completeValue)); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(value)) + .Times(2) + .WillRepeatedly(Return(ST_TR181_STATUS_STARTING)); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(completeValue)) + .Times(1) + .WillOnce(Return(ST_TR181_STATUS_COMPLETE)); + /* Timeout is fetched again on the duplicate STARTING event, since + * ni_speedtest_trigger() now refreshes the running timer's deadline + * instead of ignoring the event. */ + EXPECT_CALL(*g_rbusMock, rbus_get(_, StrEq("Device.IP.Diagnostics.X_RDK_SpeedTest.SubscriberUnPauseTimeOut"), _)) + .Times(2) + .WillRepeatedly(DoAll(SetArgPointee<2>(timeoutValue), Return(RBUS_ERROR_SUCCESS))); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(timeoutValue)) + .Times(2) + .WillRepeatedly(Return(86400)); + EXPECT_CALL(*g_rbusMock, rbusValue_Release(timeoutValue)) + .Times(2); + /* Only ONE pause + ONE resume, even though status=1 fires twice: + * the duplicate STARTING event only refreshes the deadline, it does + * not re-pause Network Intelligence or spawn a new thread. */ + EXPECT_CALL(*g_securewrapperMock, v_secure_system(HasSubstr("cujo-ni-cli"), _)) + .Times(2) + .WillRepeatedly(Return(0)); + + speedtestEventReceiveHandler(NULL, &event, NULL); + WaitForSpeedtestThreadState(true, 200); + EXPECT_TRUE(IsSpeedtestThreadRunning()); + + /* Duplicate status=1 while already running: refreshes the deadline + * on the existing thread, no new pause, no new thread. */ + speedtestEventReceiveHandler(NULL, &event, NULL); + EXPECT_TRUE(IsSpeedtestThreadRunning()); + + speedtestEventReceiveHandler(NULL, &event, NULL); + WaitForSpeedtestThreadState(false, 500); + EXPECT_FALSE(IsSpeedtestThreadRunning()); + + RemoveSpeedtestNIEnabledAndActivated(); +} + +TEST_F(CcspAdvSecurityInternalTestFixture, SpeedTest_Status_Starting_NIDisabled_NoAction) +{ + int marker = 0; + int completeMarker = 5; + rbusValue_t value = (rbusValue_t)▮ + rbusValue_t completeValue = (rbusValue_t)&completeMarker; + rbusEvent_t event = {}; + RemoveSpeedtestNIEnabledAndActivated(); + + EXPECT_CALL(*g_rbusMock, rbusObject_GetValue(_, _)) + .Times(2) + .WillOnce(Return(value)) + .WillOnce(Return(completeValue)); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(value)) + .Times(1) + .WillOnce(Return(ST_TR181_STATUS_STARTING)); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(completeValue)) + .Times(1) + .WillOnce(Return(ST_TR181_STATUS_COMPLETE)); + /* NI is disabled: speedtestEventReceiveHandler now returns before + * ever fetching the timeout, for any status. */ + EXPECT_CALL(*g_rbusMock, rbus_get(_, StrEq("Device.IP.Diagnostics.X_RDK_SpeedTest.SubscriberUnPauseTimeOut"), _)) + .Times(0); + EXPECT_CALL(*g_securewrapperMock, v_secure_system(_, _)) + .Times(0); + + /* NI is disabled: the top-level is_ni_enabled_and_activated() check + * in speedtestEventReceiveHandler skips the event entirely, so + * ni_speedtest_thread_running never becomes TRUE. */ + speedtestEventReceiveHandler(NULL, &event, NULL); + EXPECT_FALSE(IsSpeedtestThreadRunning()); + + speedtestEventReceiveHandler(NULL, &event, NULL); + EXPECT_FALSE(IsSpeedtestThreadRunning()); +} + +TEST_F(CcspAdvSecurityInternalTestFixture, SpeedTest_Status_Starting_NIEnabledButNotActivated_NoAction) +{ + int marker = 0; + int completeMarker = 5; + rbusValue_t value = (rbusValue_t)▮ + rbusValue_t completeValue = (rbusValue_t)&completeMarker; + rbusEvent_t event = {}; + remove(g_speedtestNIActivatedPath); + FILE *file = fopen(g_speedtestNIEnabledPath, "w"); + if (file) fclose(file); + + EXPECT_CALL(*g_rbusMock, rbusObject_GetValue(_, _)) + .Times(2) + .WillOnce(Return(value)) + .WillOnce(Return(completeValue)); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(value)) + .Times(1) + .WillOnce(Return(ST_TR181_STATUS_STARTING)); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(completeValue)) + .Times(1) + .WillOnce(Return(ST_TR181_STATUS_COMPLETE)); + /* NI is enabled but not activated: still gated out before ever + * fetching the timeout. */ + EXPECT_CALL(*g_rbusMock, rbus_get(_, StrEq("Device.IP.Diagnostics.X_RDK_SpeedTest.SubscriberUnPauseTimeOut"), _)) + .Times(0); + EXPECT_CALL(*g_securewrapperMock, v_secure_system(_, _)) + .Times(0); + + /* NI is enabled but not activated: the top-level + * is_ni_enabled_and_activated() check skips the event entirely. */ + speedtestEventReceiveHandler(NULL, &event, NULL); + EXPECT_FALSE(IsSpeedtestThreadRunning()); + + speedtestEventReceiveHandler(NULL, &event, NULL); + EXPECT_FALSE(IsSpeedtestThreadRunning()); + + remove(g_speedtestNIEnabledPath); +} + +TEST_F(CcspAdvSecurityInternalTestFixture, SpeedTest_Status_StartingWithZeroTimeout_NoAction) +{ + int marker = 0; + int timeoutMarker = 0; + rbusValue_t value = (rbusValue_t)▮ + rbusValue_t timeoutValue = (rbusValue_t)&timeoutMarker; + rbusEvent_t event = {}; + CreateSpeedtestNIEnabledAndActivated(); + + EXPECT_CALL(*g_rbusMock, rbusObject_GetValue(_, _)) + .Times(1) + .WillOnce(Return(value)); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(value)) + .Times(1) + .WillOnce(Return(ST_TR181_STATUS_STARTING)); + EXPECT_CALL(*g_rbusMock, rbus_get(_, StrEq("Device.IP.Diagnostics.X_RDK_SpeedTest.SubscriberUnPauseTimeOut"), _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<2>(timeoutValue), Return(RBUS_ERROR_SUCCESS))); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(timeoutValue)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_rbusMock, rbusValue_Release(timeoutValue)) + .Times(1); + EXPECT_CALL(*g_securewrapperMock, v_secure_system(_, _)) + .Times(0); + + speedtestEventReceiveHandler(NULL, &event, NULL); + + EXPECT_FALSE(IsSpeedtestThreadRunning()); + + RemoveSpeedtestNIEnabledAndActivated(); +} + +TEST_F(CcspAdvSecurityInternalTestFixture, SpeedTest_Status_Starting_GetTimeoutFailsWithValue_ReleasesRbusValue) +{ + int marker = 0; + int timeoutMarker = 0; + rbusValue_t value = (rbusValue_t)▮ + rbusValue_t timeoutValue = (rbusValue_t)&timeoutMarker; + rbusEvent_t event = {}; + CreateSpeedtestNIEnabledAndActivated(); + + EXPECT_CALL(*g_rbusMock, rbusObject_GetValue(_, _)) + .Times(1) + .WillOnce(Return(value)); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(value)) + .Times(1) + .WillOnce(Return(ST_TR181_STATUS_STARTING)); + /* rbus_get() fails but still populates a non-NULL value (a real + * rbus behavior in some implementations, e.g. partial/cached state + * before returning an error). speedtestGetTimeout() must release it + * instead of leaking it. */ + EXPECT_CALL(*g_rbusMock, rbus_get(_, StrEq("Device.IP.Diagnostics.X_RDK_SpeedTest.SubscriberUnPauseTimeOut"), _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<2>(timeoutValue), Return(RBUS_ERROR_BUS_ERROR))); + EXPECT_CALL(*g_rbusMock, rbusValue_Release(timeoutValue)) + .Times(1); + EXPECT_CALL(*g_securewrapperMock, v_secure_system(_, _)) + .Times(0); + + speedtestEventReceiveHandler(NULL, &event, NULL); + + EXPECT_FALSE(IsSpeedtestThreadRunning()); + + RemoveSpeedtestNIEnabledAndActivated(); +} + +TEST_F(CcspAdvSecurityInternalTestFixture, SpeedTest_Status_Complete_NoThreadRunning_NoAction) +{ + int marker = 0; + rbusValue_t value = (rbusValue_t)▮ + rbusEvent_t event = {}; + CreateSpeedtestNIEnabledAndActivated(); + + EXPECT_CALL(*g_rbusMock, rbusObject_GetValue(_, _)) + .Times(1) + .WillOnce(Return(value)); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(value)) + .Times(1) + .WillOnce(Return(ST_TR181_STATUS_COMPLETE)); + EXPECT_CALL(*g_securewrapperMock, v_secure_system(_, _)) + .Times(0); + + speedtestEventReceiveHandler(NULL, &event, NULL); + + RemoveSpeedtestNIEnabledAndActivated(); +} + +TEST_F(CcspAdvSecurityInternalTestFixture, SpeedTest_Status_Complete_NIDisabled_NoAction) +{ + int marker = 0; + rbusValue_t value = (rbusValue_t)▮ + rbusEvent_t event = {}; + RemoveSpeedtestNIEnabledAndActivated(); + + EXPECT_CALL(*g_rbusMock, rbusObject_GetValue(_, _)) + .Times(1) + .WillOnce(Return(value)); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(value)) + .Times(1) + .WillOnce(Return(ST_TR181_STATUS_COMPLETE)); + EXPECT_CALL(*g_securewrapperMock, v_secure_system(_, _)) + .Times(0); + + /* NI disabled: the top-level is_ni_enabled_and_activated() check now + * gates COMPLETE the same as STARTING. */ + speedtestEventReceiveHandler(NULL, &event, NULL); +} + +TEST_F(CcspAdvSecurityInternalTestFixture, SpeedTest_Status_NullValue_NoAction) +{ + rbusEvent_t event = {}; + + EXPECT_CALL(*g_rbusMock, rbusObject_GetValue(_, _)) + .Times(1) + .WillOnce(Return((rbusValue_t)NULL)); + EXPECT_CALL(*g_securewrapperMock, v_secure_system(_, _)) + .Times(0); + + speedtestEventReceiveHandler(NULL, &event, NULL); +} + +TEST_F(CcspAdvSecurityInternalTestFixture, SpeedTest_Status_Other_NoAction) +{ + int marker = 0; + rbusValue_t value = (rbusValue_t)▮ + rbusEvent_t event = {}; + CreateSpeedtestNIEnabledAndActivated(); + + EXPECT_CALL(*g_rbusMock, rbusObject_GetValue(_, _)) + .Times(1) + .WillOnce(Return(value)); + EXPECT_CALL(*g_rbusMock, rbusValue_GetUInt32(value)) + .Times(1) + .WillOnce(Return(2)); + EXPECT_CALL(*g_securewrapperMock, v_secure_system(_, _)) + .Times(0); + + speedtestEventReceiveHandler(NULL, &event, NULL); + + RemoveSpeedtestNIEnabledAndActivated(); +} +#endif + TEST_F(CcspAdvSecurityInternalTestFixture, CosaAdvPCInit) { const char *AdvParentalControlRFCEnabled = "Adv_PCRFCEnable"; diff --git a/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityMock.cpp b/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityMock.cpp index a690f88..342b12e 100644 --- a/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityMock.cpp +++ b/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityMock.cpp @@ -34,3 +34,4 @@ cjsonMock *g_cjsonMock = NULL; SyseventMock *g_syseventMock = NULL; webconfigFwMock *g_webconfigFwMock = NULL; AnscWrapperApiMock * g_anscWrapperApiMock = NULL; +telemetryMock *g_telemetryMock = NULL; diff --git a/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityMock.h b/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityMock.h index f73fbf9..1223968 100644 --- a/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityMock.h +++ b/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityMock.h @@ -43,6 +43,7 @@ #include #include #include +#include extern SyscfgMock *g_syscfgMock; extern SecureWrapperMock *g_securewrapperMock; @@ -60,6 +61,7 @@ extern cjsonMock *g_cjsonMock; extern SyseventMock *g_syseventMock; extern webconfigFwMock *g_webconfigFwMock; extern AnscWrapperApiMock *g_anscWrapperApiMock; +extern telemetryMock *g_telemetryMock; using namespace std; using std::experimental::filesystem::exists; diff --git a/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityWebconfigTest.cpp b/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityWebconfigTest.cpp index 2c24502..a1e000c 100644 --- a/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityWebconfigTest.cpp +++ b/source/test/CcspAdvSecurityDmlTest/CcspAdvSecurityWebconfigTest.cpp @@ -37,6 +37,7 @@ class CcspAdvSecurityWebconfigTestFixture : public ::testing::Test { g_syseventMock = new SyseventMock(); g_webconfigFwMock = new webconfigFwMock(); g_anscWrapperApiMock = new AnscWrapperApiMock(); + g_telemetryMock = new telemetryMock(); } void TearDown() override { @@ -56,6 +57,7 @@ class CcspAdvSecurityWebconfigTestFixture : public ::testing::Test { delete g_syseventMock; delete g_webconfigFwMock; delete g_anscWrapperApiMock; + delete g_telemetryMock; g_syscfgMock = nullptr; g_securewrapperMock = nullptr; g_msgpackMock = nullptr; @@ -72,6 +74,7 @@ class CcspAdvSecurityWebconfigTestFixture : public ::testing::Test { g_syseventMock = nullptr; g_webconfigFwMock = nullptr; g_anscWrapperApiMock = nullptr; + g_telemetryMock = nullptr; } }; diff --git a/source/test/CcspAdvSecurityDmlTest/Makefile.am b/source/test/CcspAdvSecurityDmlTest/Makefile.am index 0fcbcb0..49db1db 100644 --- a/source/test/CcspAdvSecurityDmlTest/Makefile.am +++ b/source/test/CcspAdvSecurityDmlTest/Makefile.am @@ -57,6 +57,7 @@ CcspAdvSecurityDmlTest_gtest_bin_LDADD = \ $(HOME)/usr/local/lib/libmock_secure_wrapper.la \ $(HOME)/usr/local/lib/libmock_msgpack.la \ $(HOME)/usr/local/lib/libmock_safec_lib.la \ + $(HOME)/usr/local/lib/libmock_telemetry.la \ $(HOME)/usr/local/lib/libmock_webconfigframework.la \ $(HOME)/usr/local/lib/libmock_base64.la \ $(HOME)/usr/local/lib/libmock_rbus.la \