From 146b3ac616e7693b1e968859b490bc7119f19ab6 Mon Sep 17 00:00:00 2001 From: tukken-comcast Date: Sat, 22 Aug 2026 05:50:42 +0530 Subject: [PATCH 1/2] RDK-62042: Improve performance of GetAvailableInterfaces and GetInterfaceState (#337) * RDK-62042: Improve performance of GetAvailableInterfaces and GetInterfaceState Serve interface-state reads from an event-maintained cache. GetAvailableInterfaces and GetInterfaceState on the Gnome backend created a throwaway NMClient per call (nm_client_new), which synchronously dumps NetworkManager's entire object model and cost ~300ms-1.4s, blowing the 100ms SLA for GetAvailableInterfaces. Serve both reads from a gnome-owned cache of raw NMDeviceState (plus MAC), maintained solely by the event monitor: - Record state on the startup device walk, device-added, and every notify::state transition; drop the entry on device-removed. - Capture the MAC and keep it in sync whenever NM reports a new HW address. - Derive enabled/connected at read time from a single canonical definition, removing the prior >= vs > drift between the two APIs. - Make the reads pure: drop their side-effect writes to the shared connected/enabled atomics (the event path is now the sole writer). - Treat an interface absent from the cache as omitted, and an empty interface list as a valid (successful) result rather than an error. The cache holds a libnm type, so it lives in the Gnome backend; the backend-agnostic NetworkManagerImplementation header stays libnm-free. Add microsecond-resolution [PERF] instrumentation for both reads across the JSON-RPC and impl layers. It is logged at DEBUG level so it stays silent in normal operation and can be enabled on demand for diagnostics. When either COM-RPC read takes one second or more, also emit a WARN so pathological latencies surface without enabling DEBUG. Gate the log level check ahead of message formatting in NetworkManagerLogger::logPrint. Previously vsnprintf ran unconditionally and the level was only checked afterward, so every disabled log still paid the formatting cost. This affects all logging: disabled logs (at any level) now short-circuit before formatting. The RDK-logger build gates on rdk_logger_is_logLevel_enabled and the native build on the configured level, so filtering stays authoritative for each variant. Rework the libnm L1 tests to drive GetAvailableInterfaces and GetInterfaceState through the event-state cache (the sole public writer) instead of mocking the per-call NMClient device enumeration, and reset the process-global cache in test setup for order-independent runs. * Updated include Function Name Updated include Function Name --------- Co-authored-by: Karunakaran A <48997923+karuna2git@users.noreply.github.com> --- plugin/NetworkManagerJsonRpc.cpp | 22 +++ plugin/NetworkManagerLogger.cpp | 14 +- plugin/gnome/NetworkManagerGnomeEvents.cpp | 67 ++++++++- plugin/gnome/NetworkManagerGnomeEvents.h | 17 +++ plugin/gnome/NetworkManagerGnomeProxy.cpp | 139 ++++-------------- tests/l2Test/libnm/l2_test_libnmproxy.cpp | 136 ++++++----------- tests/l2Test/libnm/l2_test_libnmproxyInit.cpp | 10 +- 7 files changed, 197 insertions(+), 208 deletions(-) diff --git a/plugin/NetworkManagerJsonRpc.cpp b/plugin/NetworkManagerJsonRpc.cpp index d49e6c5d4..053ba8f7f 100644 --- a/plugin/NetworkManagerJsonRpc.cpp +++ b/plugin/NetworkManagerJsonRpc.cpp @@ -21,6 +21,8 @@ #include "INetworkManager.h" #include "NetworkManagerJsonEnum.h" +#include + #define LOG_INPARAM() { string json; parameters.ToString(json); NMLOG_INFO("params=%s", json.c_str() ); } #define LOG_OUTPARAM() { string json; response.ToString(json); NMLOG_INFO("response=%s", json.c_str() ); } @@ -164,11 +166,19 @@ namespace WPEFramework Exchange::INetworkManager::IInterfaceDetailsIterator* _interfaces{}; + const auto tStart = std::chrono::steady_clock::now(); + if (_networkManager) rc = _networkManager->GetAvailableInterfaces(_interfaces); else rc = Core::ERROR_UNAVAILABLE; + const auto tAfterComRpc = std::chrono::steady_clock::now(); + const long long comRpcUs = static_cast(std::chrono::duration_cast(tAfterComRpc - tStart).count()); + NMLOG_DEBUG("[PERF] GetAvailableInterfaces COM-RPC call took %lld us", comRpcUs); + if (comRpcUs >= 1000000) + NMLOG_WARNING("[PERF] GetAvailableInterfaces COM-RPC call took %lld us (>= 1s)", comRpcUs); + if (Core::ERROR_NONE == rc) { if (_interfaces != nullptr) @@ -193,6 +203,11 @@ namespace WPEFramework } } + const auto tEnd = std::chrono::steady_clock::now(); + NMLOG_DEBUG("[PERF] GetAvailableInterfaces iterator drain+serialize took %lld us, total %lld us", + static_cast(std::chrono::duration_cast(tEnd - tAfterComRpc).count()), + static_cast(std::chrono::duration_cast(tEnd - tStart).count())); + returnJson(rc); } @@ -241,6 +256,8 @@ namespace WPEFramework LOG_INPARAM(); uint32_t rc = Core::ERROR_GENERAL; + const auto tStart = std::chrono::steady_clock::now(); + if (parameters.HasLabel("interface")) { const string interface = parameters["interface"].String(); @@ -259,6 +276,11 @@ namespace WPEFramework else rc = Core::ERROR_BAD_REQUEST; + const long long comRpcUs = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - tStart).count()); + NMLOG_DEBUG("[PERF] GetInterfaceState COM-RPC call took %lld us", comRpcUs); + if (comRpcUs >= 1000000) + NMLOG_WARNING("[PERF] GetInterfaceState COM-RPC call took %lld us (>= 1s)", comRpcUs); + returnJson(rc); } diff --git a/plugin/NetworkManagerLogger.cpp b/plugin/NetworkManagerLogger.cpp index 6a58a1fe2..99aa51bde 100644 --- a/plugin/NetworkManagerLogger.cpp +++ b/plugin/NetworkManagerLogger.cpp @@ -84,6 +84,15 @@ namespace NetworkManagerLogger { void logPrint(LogLevel level, const char* file, const char* func, int line, const char* format, ...) { + // Gate on level before formatting so disabled logs incur no vsnprintf cost. +#ifdef USE_RDK_LOGGER + if (!rdk_logger_is_logLevel_enabled(RDKLOGGER_MODULE_NAME, mapTordkLogLevel(level))) + return; +#else + if (gDefaultLogLevel < level) + return; +#endif + size_t n = 0; const short kFormatMessageSize = 1024; char formattedLog[kFormatMessageSize] = {0}; @@ -102,16 +111,13 @@ namespace NetworkManagerLogger { } formattedLog[kFormatMessageSize - 1] = '\0'; #ifdef USE_RDK_LOGGER - RDK_LOG(mapTordkLogLevel(level), RDKLOGGER_MODULE_NAME, "[%s +%d] %s\n", trimPath(file), line, formattedLog); + RDK_LOG(mapTordkLogLevel(level), RDKLOGGER_MODULE_NAME, "[%s +%d] %s : %s\n", trimPath(file), line, func, formattedLog); #else const char* levelMap[] = {"Fatal", "Error", "Warn", "Info", "Debug"}; struct timeval tv; struct tm* lt; const char* fileName = trimPath(file); - if (gDefaultLogLevel < level) - return; - gettimeofday(&tv, NULL); lt = localtime(&tv.tv_sec); diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 7a87cc200..35793d79b 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "Module.h" #include "NetworkManagerGnomeEvents.h" @@ -44,6 +45,49 @@ namespace WPEFramework extern NetworkManagerImplementation* _instance; static GnomeNetworkManagerEvents *_nmEventInstance = nullptr; + /* Gnome-owned interface-state cache (raw NMDeviceState + MAC per interface). + Written only by the event monitor; read by the proxy's pure readers. */ + static std::map _ifaceStateCache; + static std::mutex _ifaceStateCacheMutex; + + bool GnomeNetworkManagerEvents::isInterfaceStateEnabled(NMDeviceState state) + { + return (state >= NM_DEVICE_STATE_UNAVAILABLE); + } + + bool GnomeNetworkManagerEvents::isInterfaceStateConnected(NMDeviceState state) + { + return (state > NM_DEVICE_STATE_DISCONNECTED && state < NM_DEVICE_STATE_DEACTIVATING); + } + + void GnomeNetworkManagerEvents::updateInterfaceStateCache(const std::string& iface, NMDeviceState state, const char* mac) + { + if (iface.empty()) + return; + std::lock_guard lock(_ifaceStateCacheMutex); + InterfaceStateInfo& info = _ifaceStateCache[iface]; + info.state = state; + info.present = true; + if (mac != nullptr && mac[0] != '\0') + info.mac = mac; // keep in sync when NM reports a new/randomized HW address + } + + void GnomeNetworkManagerEvents::removeInterfaceStateCache(const std::string& iface) + { + std::lock_guard lock(_ifaceStateCacheMutex); + _ifaceStateCache.erase(iface); + } + + bool GnomeNetworkManagerEvents::getInterfaceStateCache(const std::string& iface, InterfaceStateInfo& out) + { + std::lock_guard lock(_ifaceStateCacheMutex); + auto it = _ifaceStateCache.find(iface); + if (it == _ifaceStateCache.end() || !it->second.present) + return false; + out = it->second; + return true; + } + static void primaryConnectionCb(NMClient *client, GParamSpec *param, NMEvents *nmEvents) { NMActiveConnection *primaryConn; @@ -276,8 +320,11 @@ namespace WPEFramework return; NMDeviceState deviceState; deviceState = nm_device_get_state(device); - std::string ifname = nm_device_get_iface(device); + const char* iface = nm_device_get_iface(device); + if (!iface) return; + std::string ifname = iface; NMDeviceStateReason reason = nm_device_get_state_reason(device); + updateInterfaceStateCache(ifname, deviceState, nm_device_get_hw_address(device)); if(ifname == nmUtils::wlanIface()) { if(!NM_IS_DEVICE_WIFI(device)) { @@ -469,7 +516,9 @@ namespace WPEFramework { if( ((device != NULL) && NM_IS_DEVICE(device)) ) { - std::string ifname = nm_device_get_iface(device); + const char* iface = nm_device_get_iface(device); + if (!iface) return; + std::string ifname = iface; if(ifname == nmUtils::wlanIface()) { GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_ADDED, nmUtils::wlanIface()); NMLOG_INFO("WIFI device added: %s", ifname.c_str()); @@ -482,6 +531,7 @@ namespace WPEFramework /* ip events added only for eth0 and wlan0 */ if(ifname == nmUtils::ethIface() || ifname == nmUtils::wlanIface()) { + GnomeNetworkManagerEvents::updateInterfaceStateCache(ifname, nm_device_get_state(device), nm_device_get_hw_address(device)); g_signal_connect(device, "notify::" NM_DEVICE_STATE, G_CALLBACK(GnomeNetworkManagerEvents::deviceStateChangeCb), nmEvents); g_signal_connect(device, "notify::ip4-config", G_CALLBACK(ip4ConfigChangedCb), nmEvents); g_signal_connect(device, "notify::ip6-config", G_CALLBACK(ip6ConfigChangedCb), nmEvents); @@ -529,7 +579,9 @@ namespace WPEFramework { if( ((device != NULL) && NM_IS_DEVICE(device)) ) { - std::string ifname = nm_device_get_iface(device); + const char* iface = nm_device_get_iface(device); + if (!iface) return; + std::string ifname = iface; if(ifname == nmUtils::wlanIface()) { GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_REMOVED, nmUtils::wlanIface()); NMLOG_INFO("WIFI device removed: %s", ifname.c_str()); @@ -542,6 +594,10 @@ namespace WPEFramework return; // not a tracked interface } + /* Device is gone: drop it from the cache so the reads omit it + (matches the original live-device-list behaviour). */ + GnomeNetworkManagerEvents::removeInterfaceStateCache(ifname); + /* Disconnect all device-level signals (state, ip4/ip6-config changes). */ g_signal_handlers_disconnect_by_data(device, nmEvents); @@ -637,10 +693,13 @@ namespace WPEFramework NMDevice *device = NM_DEVICE(g_ptr_array_index(devices, count)); if( ((device != NULL) && NM_IS_DEVICE(device)) ) { - std::string ifname = nm_device_get_iface(device); + const char* iface = nm_device_get_iface(device); + if (!iface) continue; + std::string ifname = iface; if((ifname == nmUtils::ethIface()) || (ifname == nmUtils::wlanIface())) { NMDeviceState devState = nm_device_get_state(device); + GnomeNetworkManagerEvents::updateInterfaceStateCache(ifname, devState, nm_device_get_hw_address(device)); if(devState > NM_DEVICE_STATE_DISCONNECTED && devState <= NM_DEVICE_STATE_ACTIVATED) { diff --git a/plugin/gnome/NetworkManagerGnomeEvents.h b/plugin/gnome/NetworkManagerGnomeEvents.h index bf282de78..2bc31f9b1 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.h +++ b/plugin/gnome/NetworkManagerGnomeEvents.h @@ -43,12 +43,29 @@ namespace WPEFramework { public: + /* Gnome-owned cache of live NM device state, kept out of the + backend-agnostic NetworkManagerImplementation (holds a libnm type). */ + struct InterfaceStateInfo { + NMDeviceState state = NM_DEVICE_STATE_UNKNOWN; + std::string mac; + bool present = false; + }; + static void onInterfaceStateChangeCb(uint8_t newState, std::string iface); // ReportInterfaceStateChange static void onActiveInterfaceChangeCb(std::string newInterface); // ReportActiveInterfaceChange static void onAvailableSSIDsCb(NMDeviceWifi *wifiDevice, GParamSpec *pspec, gpointer userData); // ReportAvailableSSIDs static void onWIFIStateChanged(uint8_t state); // ReportWiFiStateChange static void deviceStateChangeCb(NMDevice *device, GParamSpec *pspec, NMEvents *nmEvents); + /* Interface-state cache: sole writer is the event monitor; readers + (GetAvailableInterfaces / GetInterfaceState) are pure. */ + static void updateInterfaceStateCache(const std::string& iface, NMDeviceState state, const char* mac); + static void removeInterfaceStateCache(const std::string& iface); + static bool getInterfaceStateCache(const std::string& iface, InterfaceStateInfo& out); + /* Canonical enabled/connected definitions shared by both read APIs. */ + static bool isInterfaceStateEnabled(NMDeviceState state); + static bool isInterfaceStateConnected(NMDeviceState state); + public: static GnomeNetworkManagerEvents* getInstance(); bool startNetworkMangerEventMonitor(); diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index 30d065e87..65b2fc4ba 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -22,6 +22,7 @@ #include "NetworkManagerGnomeUtils.h" #include #include +#include using namespace WPEFramework; using namespace WPEFramework::Plugin; using namespace std; @@ -347,77 +348,37 @@ namespace WPEFramework std::vector interfaceList; std::string wifiname = nmUtils::wlanIface(), ethname = nmUtils::ethIface(); - if(m_nmContext == nullptr) { - NMLOG_FATAL("NMContext is null"); - return Core::ERROR_GENERAL; - } - - NMClient *client = createProxyClient(m_nmContext); - if (client == nullptr) { - NMLOG_FATAL("Failed to create NMClient for GetAvailableInterfaces"); - return Core::ERROR_GENERAL; - } + const auto tEntry = std::chrono::steady_clock::now(); - GPtrArray *devices = const_cast(nm_client_get_devices(client)); - if (devices == NULL) { - NMLOG_ERROR("Failed to get device list."); - deleteProxyClient(client); - return rc; - } - - for (guint j = 0; j < devices->len; j++) + /* Serve from the event-maintained cache; no per-call NMClient. An interface + * absent from the cache (not yet reported by NM) is simply omitted; an empty + * list is a valid result (ground reality), not an error. */ + for (int i = 0; i < 2; ++i) { - NMDevice *device = NM_DEVICE(devices->pdata[j]); - if(device != NULL) - { - const char* ifacePtr = nm_device_get_iface(device); - if(ifacePtr == nullptr) - continue; - std::string ifaceStr = ifacePtr; - if(ifaceStr == wifiname || ifaceStr == ethname) // only wifi and ethenet taking - { - NMDeviceState deviceState = NM_DEVICE_STATE_UNKNOWN; - Exchange::INetworkManager::InterfaceDetails interface{}; - const char* macAddr = nm_device_get_hw_address(device); - if(macAddr != nullptr) { - interface.mac = macAddr; - } - deviceState = nm_device_get_state(device); - interface.enabled = (deviceState >= NM_DEVICE_STATE_UNAVAILABLE)? true : false; - if(deviceState > NM_DEVICE_STATE_DISCONNECTED && deviceState < NM_DEVICE_STATE_DEACTIVATING) - interface.connected = true; - else - interface.connected = false; + const std::string& ifname = (i == 0) ? ethname : wifiname; + GnomeNetworkManagerEvents::InterfaceStateInfo info; + if (!GnomeNetworkManagerEvents::getInterfaceStateCache(ifname, info)) + continue; - if(ifaceStr == wifiname) { - interface.type = INTERFACE_TYPE_WIFI; - interface.name = wifiname; - m_wlanConnected.store(interface.connected); - m_wlanEnabled.store(interface.enabled); - } - else if(ifaceStr == ethname) { - interface.type = INTERFACE_TYPE_ETHERNET; - interface.name = ethname; - m_ethConnected.store(interface.connected); - m_ethEnabled.store(interface.enabled); - } + Exchange::INetworkManager::InterfaceDetails interface{}; + interface.type = (i == 0) ? INTERFACE_TYPE_ETHERNET : INTERFACE_TYPE_WIFI; + interface.name = ifname; + interface.mac = info.mac; + interface.enabled = GnomeNetworkManagerEvents::isInterfaceStateEnabled(info.state); + interface.connected = GnomeNetworkManagerEvents::isInterfaceStateConnected(info.state); - interfaceList.push_back(interface); - rc = Core::ERROR_NONE; - } - } + interfaceList.push_back(interface); } - deleteProxyClient(client); - - if (rc != Core::ERROR_NONE) - return rc; - using Implementation = RPC::IteratorType; interfacesItr = Core::Service::Create(interfaceList); if(interfacesItr == nullptr) { return Core::ERROR_GENERAL; } + rc = Core::ERROR_NONE; + + NMLOG_DEBUG("[PERF] GetAvailableInterfaces (impl) total %lld us", + static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - tEntry).count())); return rc; } #if 0 @@ -640,65 +601,29 @@ namespace WPEFramework uint32_t NetworkManagerImplementation::GetInterfaceState(const string& interface/* @in */, bool& isEnabled /* @out */) { isEnabled = false; - bool isIfaceFound = false; std::string wifiname = nmUtils::wlanIface(), ethname = nmUtils::ethIface(); + const auto tEntry = std::chrono::steady_clock::now(); + if(interface.empty() || (wifiname != interface && ethname != interface)) { NMLOG_ERROR("interface: %s; not valied", interface.c_str()!=nullptr? interface.c_str():"empty"); return Core::ERROR_GENERAL; } - if(m_nmContext == nullptr) + /* Serve from the event-maintained cache; no per-call NMClient. */ + GnomeNetworkManagerEvents::InterfaceStateInfo info; + if (!GnomeNetworkManagerEvents::getInterfaceStateCache(interface, info)) { - NMLOG_WARNING("NMContext is null"); - return Core::ERROR_RPC_CALL_FAILED; - } - - NMClient *client = createProxyClient(m_nmContext); - if (client == nullptr) { - NMLOG_ERROR("Failed to create NMClient for GetInterfaceState"); - return Core::ERROR_RPC_CALL_FAILED; - } - - GPtrArray *devices = const_cast(nm_client_get_devices(client)); - - if (devices == NULL) { - NMLOG_ERROR("Failed to get device list."); - deleteProxyClient(client); + NMLOG_WARNING("%s : state not known yet", interface.c_str()); return Core::ERROR_GENERAL; } - for (guint j = 0; j < devices->len; j++) - { - NMDevice *device = NM_DEVICE(devices->pdata[j]); - if(device != NULL) - { - const char* iface = nm_device_get_iface(device); - if(iface != NULL) - { - std::string ifaceStr; - ifaceStr.assign(iface); - NMDeviceState deviceState = NM_DEVICE_STATE_UNKNOWN; - if(ifaceStr == interface) - { - isIfaceFound = true; - deviceState = nm_device_get_state(device); - isEnabled = (deviceState > NM_DEVICE_STATE_UNAVAILABLE) ? true : false; - NMLOG_INFO("%s : %s", ifaceStr.c_str(), isEnabled?"enabled":"disabled"); - break; - } - } - } - } - - deleteProxyClient(client); - - if(isIfaceFound) - return Core::ERROR_NONE; - else - NMLOG_ERROR("%s : not found", interface.c_str()); - return Core::ERROR_GENERAL; + isEnabled = GnomeNetworkManagerEvents::isInterfaceStateEnabled(info.state); + NMLOG_INFO("%s : %s", interface.c_str(), isEnabled?"enabled":"disabled"); + NMLOG_DEBUG("[PERF] GetInterfaceState (impl) total %lld us", + static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - tEntry).count())); + return Core::ERROR_NONE; } /* @brief Get IP Address Of the Interface */ diff --git a/tests/l2Test/libnm/l2_test_libnmproxy.cpp b/tests/l2Test/libnm/l2_test_libnmproxy.cpp index 0a3dad69d..a102c7eac 100644 --- a/tests/l2Test/libnm/l2_test_libnmproxy.cpp +++ b/tests/l2Test/libnm/l2_test_libnmproxy.cpp @@ -35,6 +35,7 @@ #include "NetworkManagerImplementation.h" #include "NetworkManagerLogger.h" #include "NetworkManager.h" +#include "NetworkManagerGnomeEvents.h" #include using namespace WPEFramework; @@ -150,6 +151,11 @@ class NetworkManagerTest : public ::testing::Test { std::cerr << "Failed to create /etc/device.properties file." << std::endl; } } + + /* Interface-state cache is a process-global static; reset the known + interfaces so each test starts from a deterministic empty state. */ + Plugin::GnomeNetworkManagerEvents::removeInterfaceStateCache("eth0"); + Plugin::GnomeNetworkManagerEvents::removeInterfaceStateCache("wlan0"); } virtual ~NetworkManagerTest() override @@ -353,59 +359,37 @@ TEST_F(NetworkManagerTest, GetPrimaryInterface_empty) TEST_F(NetworkManagerTest, GetInterfaceState_Failed) { - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_devices(::testing::_)) - .WillRepeatedly(::testing::Return(nullptr)); - + /* wlan0 not present in the event cache -> state unknown -> failure */ EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetInterfaceState"), _T("{\"interface\":\"wlan0\"}"), response)); EXPECT_EQ(response, _T("{\"success\":false}")); } TEST_F(NetworkManagerTest, GetInterfaceState_WifiEth) { - GPtrArray* fakeDevices = g_ptr_array_new(); - - NMDevice *deviceDummy = static_cast(g_object_new(NM_TYPE_DEVICE_ETHERNET, NULL)); - g_ptr_array_add(fakeDevices, deviceDummy); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_devices(::testing::_)) - .WillRepeatedly(::testing::Return(fakeDevices)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) - .WillOnce(::testing::Return("wlan0")) - .WillOnce(::testing::Return("eth0")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); // disabled + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("wlan0", NM_DEVICE_STATE_ACTIVATED, "66:77:88:99:AA:BB"); + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("eth0", NM_DEVICE_STATE_UNMANAGED, "00:11:22:33:44:55"); // disabled EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetInterfaceState"), _T("{\"interface\":\"wlan0\"}"), response)); EXPECT_EQ(response, _T("{\"enabled\":true,\"success\":true}")); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetInterfaceState"), _T("{\"interface\":\"eth0\"}"), response)); EXPECT_EQ(response, _T("{\"enabled\":false,\"success\":true}")); - - g_object_unref(deviceDummy); - g_ptr_array_free(fakeDevices, TRUE); } TEST_F(NetworkManagerTest, GetInterfaceState_WifiEthNotFound) { - GPtrArray* fakeDevices = g_ptr_array_new(); - - NMDevice *deviceDummy = static_cast(g_object_new(NM_TYPE_DEVICE_ETHERNET, NULL)); - g_ptr_array_add(fakeDevices, deviceDummy); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_devices(::testing::_)) - .WillRepeatedly(::testing::Return(fakeDevices)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) - .WillOnce(::testing::Return("wlan1")); + /* Only eth0 known to the cache; querying wlan0 -> not found -> failure */ + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("eth0", NM_DEVICE_STATE_ACTIVATED, "00:11:22:33:44:55"); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetInterfaceState"), _T("{\"interface\":\"wlan0\"}"), response)); EXPECT_EQ(response, _T("{\"success\":false}")); +} - g_object_unref(deviceDummy); - g_ptr_array_free(fakeDevices, TRUE); +TEST_F(NetworkManagerTest, GetInterfaceState_emptyInterface) +{ + /* Empty interface name is rejected before any cache lookup */ + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetInterfaceState"), _T("{\"interface\":\"\"}"), response)); + EXPECT_EQ(response, _T("{\"success\":false}")); } TEST_F(NetworkManagerTest, GetInterfaceState_unknown) @@ -421,38 +405,19 @@ TEST_F(NetworkManagerTest, GetInterfaceState_unknown) EXPECT_EQ(isEnabled, false); } -TEST_F(NetworkManagerTest, GetAvailableInterfaces_DevicesNull) +TEST_F(NetworkManagerTest, GetAvailableInterfaces_emptyCache) { - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_devices(::testing::_)) - .WillOnce(::testing::Return(nullptr)); + /* No interfaces reported by NM yet -> empty list is a valid (successful) result */ EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetAvailableInterfaces"), _T(""), response)); - EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("eth0") == std::string::npos); + EXPECT_TRUE(response.find("wlan0") == std::string::npos); } TEST_F(NetworkManagerTest, GetAvailableInterfaces_Enabled) { - GPtrArray* fakeDevices = g_ptr_array_new(); - NMDevice *ethDevice = static_cast(g_object_new(NM_TYPE_DEVICE_ETHERNET, NULL)); - NMDevice* wifiDevice = static_cast(g_object_new(NM_TYPE_DEVICE_WIFI, NULL)); - g_ptr_array_add(fakeDevices, wifiDevice); - g_ptr_array_add(fakeDevices, ethDevice); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_devices(::testing::_)) - .WillOnce(::testing::Return(fakeDevices)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(ethDevice)) - .WillOnce(::testing::Return("eth0")); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(wifiDevice)) - .WillOnce(::testing::Return("wlan0")); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_hw_address(ethDevice)) - .WillOnce(::testing::Return("00:11:22:33:44:55")); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_hw_address(wifiDevice)) - .WillOnce(::testing::Return("66:77:88:99:AA:BB")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(ethDevice)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(wifiDevice)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNAVAILABLE)); + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("eth0", NM_DEVICE_STATE_ACTIVATED, "00:11:22:33:44:55"); + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("wlan0", NM_DEVICE_STATE_UNAVAILABLE, "66:77:88:99:AA:BB"); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetAvailableInterfaces"), _T(""), response)); EXPECT_TRUE(response.find("wlan0") != std::string::npos); @@ -460,40 +425,16 @@ TEST_F(NetworkManagerTest, GetAvailableInterfaces_Enabled) std::string expectedResponse = _T("{\"interfaces\":[") - _T("{\"type\":\"WIFI\",\"name\":\"wlan0\",\"mac\":\"66:77:88:99:AA:BB\",\"enabled\":true,\"connected\":false},") - _T("{\"type\":\"ETHERNET\",\"name\":\"eth0\",\"mac\":\"00:11:22:33:44:55\",\"enabled\":true,\"connected\":true}") + _T("{\"type\":\"ETHERNET\",\"name\":\"eth0\",\"mac\":\"00:11:22:33:44:55\",\"enabled\":true,\"connected\":true},") + _T("{\"type\":\"WIFI\",\"name\":\"wlan0\",\"mac\":\"66:77:88:99:AA:BB\",\"enabled\":true,\"connected\":false}") _T("],\"success\":true}"); EXPECT_EQ(response, expectedResponse); - - g_object_unref(ethDevice); - g_object_unref(wifiDevice); - g_ptr_array_free(fakeDevices, TRUE); } TEST_F(NetworkManagerTest, GetAvailableInterfaces_disabled) { - GPtrArray* fakeDevices = g_ptr_array_new(); - NMDevice *ethDevice = static_cast(g_object_new(NM_TYPE_DEVICE_ETHERNET, NULL)); - NMDevice* wifiDevice = static_cast(g_object_new(NM_TYPE_DEVICE_WIFI, NULL)); - g_ptr_array_add(fakeDevices, wifiDevice); - g_ptr_array_add(fakeDevices, ethDevice); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_devices(::testing::_)) - .WillOnce(::testing::Return(fakeDevices)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(ethDevice)) - .WillOnce(::testing::Return("eth0")); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(wifiDevice)) - .WillOnce(::testing::Return("wlan0")); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_hw_address(ethDevice)) - .WillOnce(::testing::Return("00:11:22:33:44:55")); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_hw_address(wifiDevice)) - .WillOnce(::testing::Return("66:77:88:99:AA:BB")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(ethDevice)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(wifiDevice)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("eth0", NM_DEVICE_STATE_UNMANAGED, "00:11:22:33:44:55"); + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("wlan0", NM_DEVICE_STATE_UNMANAGED, "66:77:88:99:AA:BB"); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetAvailableInterfaces"), _T(""), response)); EXPECT_TRUE(response.find("wlan0") != std::string::npos); @@ -501,14 +442,25 @@ TEST_F(NetworkManagerTest, GetAvailableInterfaces_disabled) std::string expectedResponse = _T("{\"interfaces\":[") - _T("{\"type\":\"WIFI\",\"name\":\"wlan0\",\"mac\":\"66:77:88:99:AA:BB\",\"enabled\":false,\"connected\":false},") - _T("{\"type\":\"ETHERNET\",\"name\":\"eth0\",\"mac\":\"00:11:22:33:44:55\",\"enabled\":false,\"connected\":false}") + _T("{\"type\":\"ETHERNET\",\"name\":\"eth0\",\"mac\":\"00:11:22:33:44:55\",\"enabled\":false,\"connected\":false},") + _T("{\"type\":\"WIFI\",\"name\":\"wlan0\",\"mac\":\"66:77:88:99:AA:BB\",\"enabled\":false,\"connected\":false}") _T("],\"success\":true}"); EXPECT_EQ(response, expectedResponse); +} - g_object_unref(ethDevice); - g_object_unref(wifiDevice); - g_ptr_array_free(fakeDevices, TRUE); +TEST_F(NetworkManagerTest, GetAvailableInterfaces_onlyEthernet) +{ + /* Only eth0 reported by NM; wlan0 (absent from cache) is omitted from the list */ + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("eth0", NM_DEVICE_STATE_ACTIVATED, "00:11:22:33:44:55"); + + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetAvailableInterfaces"), _T(""), response)); + EXPECT_TRUE(response.find("wlan0") == std::string::npos); + + std::string expectedResponse = + _T("{\"interfaces\":[") + _T("{\"type\":\"ETHERNET\",\"name\":\"eth0\",\"mac\":\"00:11:22:33:44:55\",\"enabled\":true,\"connected\":true}") + _T("],\"success\":true}"); + EXPECT_EQ(response, expectedResponse); } TEST_F(NetworkManagerTest, GetIPSettings_unknown_iface) diff --git a/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp b/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp index b550b3d6f..52d2a1399 100644 --- a/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp +++ b/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp @@ -33,6 +33,7 @@ #include "NetworkManagerImplementation.h" #include "NetworkManagerLogger.h" #include "NetworkManager.h" +#include "NetworkManagerGnomeEvents.h" #include using namespace WPEFramework; @@ -123,6 +124,10 @@ class NetworkManagerInitTest : public ::testing::Test { virtual void SetUp() override { + /* Interface-state cache is a process-global static shared across suites; + clear the known interfaces so platform_init failure yields an empty cache. */ + Plugin::GnomeNetworkManagerEvents::removeInterfaceStateCache("eth0"); + Plugin::GnomeNetworkManagerEvents::removeInterfaceStateCache("wlan0"); } virtual ~NetworkManagerInitTest() override @@ -149,7 +154,10 @@ TEST_F(NetworkManagerInitTest, platformInit) NetworkManagerLogger::SetLevel(static_cast(NetworkManagerLogger::DEBUG_LEVEL)); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetAvailableInterfaces"), _T(""), response)); - EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); + /* platform_init failed (no NMClient) -> event cache never populated -> empty list, still success */ + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("eth0") == std::string::npos); + EXPECT_TRUE(response.find("wlan0") == std::string::npos); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetPrimaryInterface"), _T(""), response)); EXPECT_EQ(response, _T("{\"interface\":\"\",\"success\":true}")); From b70b39b5f32fcd6c31a908d0bb95de6166db3ce3 Mon Sep 17 00:00:00 2001 From: Karunakaran A Date: Sat, 22 Aug 2026 11:27:48 -0400 Subject: [PATCH 2/2] Release of 3.7.0 Release of 3.7.0 --- CHANGELOG.md | 4 ++++ CMakeLists.txt | 2 +- definition/NetworkManager.json | 2 +- docs/NetworkManagerPlugin.md | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 851e39f72..fe546edd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ All notable changes to this RDK Service will be documented in this file. * Changes in CHANGELOG should be updated when commits are added to the main or release branches. There should be one CHANGELOG entry per JIRA Ticket. This is not enforced on sprint branches since there could be multiple changes for the same JIRA ticket during development. +## [3.7.0] - 2026-08-21 +### Changed +- Implemented a caching logic about the status of the interface & update based on events + ## [3.6.0] - 2026-08-11 ### Fixed - Fixed the issue with connecting to a SSID that is not present in scan list diff --git a/CMakeLists.txt b/CMakeLists.txt index 724fb37e9..f02326d7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") set(VERSION_MAJOR 3) -set(VERSION_MINOR 6) +set(VERSION_MINOR 7) set(VERSION_PATCH 0) add_compile_definitions(NETWORKMANAGER_MAJOR_VERSION=${VERSION_MAJOR}) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 2922b9835..014a649f5 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -8,7 +8,7 @@ "status": "production", "description": "A Unified `NetworkManager` plugin that allows you to manage Ethernet and Wifi interfaces on the device.", "sourcelocation": "https://github.com/rdkcentral/networkmanager/blob/main/definition/NetworkManager.json", - "version": "3.6.0" + "version": "3.7.0" }, "definitions": { "success": { diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index fd6d97dcf..74341a26b 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -2,7 +2,7 @@ # NetworkManager Plugin -**Version: 3.6.0** +**Version: 3.7.0** **Status: :black_circle::black_circle::black_circle:** @@ -23,7 +23,7 @@ org.rdk.NetworkManager interface for Thunder framework. ## Scope -This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 3.6.0). It includes detailed specification about its methods provided and notifications sent. +This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 3.7.0). It includes detailed specification about its methods provided and notifications sent. ## Case Sensitivity