diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 1dd1f29be2..1d8ecc8362 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -11,6 +11,9 @@ env: THUNDER_REF: "39c82df24c7b4a4a10663cf527a3b289e7c1ceae" INTERFACES_REF: "8d3bd0ddd187c6104543699041ec53e1e186d49a" + # Refs for 22Q4_sprint + THUNDER_REF: "a87b48eca1e76c3e6f03d689e6302b1fb090b52c" + INTERFACES_REF: "f61d710cc51628819d0fd80b8cc65e55eeec12b4" jobs: unit-tests: diff --git a/CMakeLists.txt b/CMakeLists.txt index 0edcbaa7bb..2925a485cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,10 @@ if(PLUGIN_DISPLAYSETTINGS) add_subdirectory(DisplaySettings) endif() +if(PLUGIN_LGIDISPLAYSETTINGS) + add_subdirectory(LgiDisplaySettings) +endif() + if(PLUGIN_REMOTEACTIONMAPPING) add_subdirectory(RemoteActionMapping) endif() @@ -74,6 +78,10 @@ if(PLUGIN_SYSTEMSERVICES) add_subdirectory(SystemServices) endif() +if(PLUGIN_LGISYSTEMSERVICES) + add_subdirectory(LgiSystemServices) +endif() + if(PLUGIN_MAINTENANCEMANAGER) add_subdirectory(MaintenanceManager) endif() @@ -82,6 +90,10 @@ if(PLUGIN_HOMENETWORKING) add_subdirectory(HomeNetworking) endif() +if(PLUGIN_LGINETWORK) + add_subdirectory(LgiNetwork) +endif() + if(PLUGIN_NETWORK) add_subdirectory(Network) endif() @@ -171,6 +183,10 @@ if(PLUGIN_HDMICECSINK) add_subdirectory(HdmiCecSink) endif() +if(PLUGIN_LGIHDMICEC) + add_subdirectory(LgiHdmiCec) +endif() + if(PLUGIN_LOCATIONSYNC) add_subdirectory(LocationSync) endif() @@ -303,6 +319,10 @@ if(PLUGIN_TEXTTOSPEECH) add_subdirectory(TextToSpeech) endif() +if(PLUGIN_LGITEXTTOSPEECH) + add_subdirectory(LgiTextToSpeech) +endif() + if(PLUGIN_SYSTEMAUDIOPLAYER) add_subdirectory(SystemAudioPlayer) endif() @@ -331,6 +351,10 @@ if(PLUGIN_WEBBRIDGE) add_subdirectory(WebBridge) endif() +if(PLUGIN_LGIHDCPPROFILE) + add_subdirectory(LgiHdcpProfile) +endif() + if(PLUGIN_LINEARPLAYBACKCONTROL) add_subdirectory(LinearPlaybackControl) endif() diff --git a/DeviceIdentification/Implementation/Broadcom/Broadcom.cpp b/DeviceIdentification/Implementation/Broadcom/Broadcom.cpp index f915abfb9d..e8d4cf5198 100644 --- a/DeviceIdentification/Implementation/Broadcom/Broadcom.cpp +++ b/DeviceIdentification/Implementation/Broadcom/Broadcom.cpp @@ -28,7 +28,7 @@ namespace WPEFramework { namespace Plugin { class DeviceImplementation : public PluginHost::ISubSystem::IIdentifier { - static constexpr const TCHAR* PlatformFile = _T("/proc/brcm/platform"); + static constexpr const TCHAR* PlatformFile = _T("/tmp/device_identification.txt"); public: DeviceImplementation() diff --git a/DeviceInfo/CMakeLists.txt b/DeviceInfo/CMakeLists.txt index a1c880ae06..c8fdb0ccaf 100644 --- a/DeviceInfo/CMakeLists.txt +++ b/DeviceInfo/CMakeLists.txt @@ -22,6 +22,7 @@ set(PLUGIN_DEVICEINFO_MODE "Off" CACHE STRING "Controls if the plugin should run set(PLUGIN_DEVICEINFO_STARTUPORDER "" CACHE STRING "Start-up order for DeviceInfo plugin") find_package(${NAMESPACE}Plugins REQUIRED) +find_package(${NAMESPACE}DSManagerPlugin REQUIRED) find_package(${NAMESPACE}Definitions REQUIRED) find_package(CompileSettingsDebug CONFIG REQUIRED) find_package(RFC) @@ -57,6 +58,7 @@ target_link_libraries(${MODULE_NAME} PRIVATE CompileSettingsDebug::CompileSettingsDebug ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${NAMESPACE}DSManagerPlugin::${NAMESPACE}DSManagerPlugin ${NAMESPACE}Definitions::${NAMESPACE}Definitions ${RFC_LIBRARIES} ${DS_LIBRARIES} diff --git a/DeviceInfo/Implementation/DeviceAudioCapabilities.cpp b/DeviceInfo/Implementation/DeviceAudioCapabilities.cpp index 36e805c719..56b939d89e 100644 --- a/DeviceInfo/Implementation/DeviceAudioCapabilities.cpp +++ b/DeviceInfo/Implementation/DeviceAudioCapabilities.cpp @@ -14,15 +14,6 @@ namespace Plugin { DeviceAudioCapabilities::DeviceAudioCapabilities() { Utils::IARM::init(); - - try { - device::Manager::Initialize(); - } catch (const device::Exception& e) { - TRACE(Trace::Fatal, (_T("Exception caught %s"), e.what())); - } catch (const std::exception& e) { - TRACE(Trace::Fatal, (_T("Exception caught %s"), e.what())); - } catch (...) { - } } uint32_t DeviceAudioCapabilities::SupportedAudioPorts(RPC::IStringIterator*& supportedAudioPorts) const diff --git a/DeviceInfo/Implementation/DeviceAudioCapabilities.h b/DeviceInfo/Implementation/DeviceAudioCapabilities.h index e34bc07e7a..b3e59e56d2 100644 --- a/DeviceInfo/Implementation/DeviceAudioCapabilities.h +++ b/DeviceInfo/Implementation/DeviceAudioCapabilities.h @@ -7,9 +7,11 @@ #include #endif /* USE_THUNDER_R4 */ +#include "dsmanagerplugin/DSManagerPlugin.h" + namespace WPEFramework { namespace Plugin { - class DeviceAudioCapabilities : public Exchange::IDeviceAudioCapabilities { + class DeviceAudioCapabilities : public Exchange::IDeviceAudioCapabilities, public DSManagerPlugin { private: DeviceAudioCapabilities(const DeviceAudioCapabilities&) = delete; DeviceAudioCapabilities& operator=(const DeviceAudioCapabilities&) = delete; diff --git a/DeviceInfo/Implementation/DeviceVideoCapabilities.cpp b/DeviceInfo/Implementation/DeviceVideoCapabilities.cpp index fd3b0e50f9..7238fb189d 100644 --- a/DeviceInfo/Implementation/DeviceVideoCapabilities.cpp +++ b/DeviceInfo/Implementation/DeviceVideoCapabilities.cpp @@ -15,15 +15,6 @@ namespace Plugin { DeviceVideoCapabilities::DeviceVideoCapabilities() { Utils::IARM::init(); - - try { - device::Manager::Initialize(); - } catch (const device::Exception& e) { - TRACE(Trace::Fatal, (_T("Exception caught %s"), e.what())); - } catch (const std::exception& e) { - TRACE(Trace::Fatal, (_T("Exception caught %s"), e.what())); - } catch (...) { - } } uint32_t DeviceVideoCapabilities::SupportedVideoDisplays(RPC::IStringIterator*& supportedVideoDisplays) const diff --git a/DeviceInfo/Implementation/DeviceVideoCapabilities.h b/DeviceInfo/Implementation/DeviceVideoCapabilities.h index 116cc365d9..2f14a51959 100644 --- a/DeviceInfo/Implementation/DeviceVideoCapabilities.h +++ b/DeviceInfo/Implementation/DeviceVideoCapabilities.h @@ -7,9 +7,11 @@ #include #endif /* USE_THUNDER_R4 */ +#include "dsmanagerplugin/DSManagerPlugin.h" + namespace WPEFramework { namespace Plugin { - class DeviceVideoCapabilities : public Exchange::IDeviceVideoCapabilities { + class DeviceVideoCapabilities : public Exchange::IDeviceVideoCapabilities, public DSManagerPlugin { private: DeviceVideoCapabilities(const DeviceVideoCapabilities&) = delete; DeviceVideoCapabilities& operator=(const DeviceVideoCapabilities&) = delete; diff --git a/DisplayInfo/CMakeLists.txt b/DisplayInfo/CMakeLists.txt index 346c0987b1..bc37a9dfe3 100644 --- a/DisplayInfo/CMakeLists.txt +++ b/DisplayInfo/CMakeLists.txt @@ -33,6 +33,7 @@ set(PLUGIN_DISPLAYINFO_STARTUPORDER "" CACHE STRING "Automatically start Display set(PLUGIN_DISPLAYINFO_MODE "Off" CACHE STRING "Set DisplayInfo plugin mode, controls if the plugin should run in its own process, in process or remote.") find_package(${NAMESPACE}Plugins REQUIRED) +find_package(${NAMESPACE}DSManagerPlugin REQUIRED) find_package(${NAMESPACE}Definitions REQUIRED) find_package(CompileSettingsDebug CONFIG REQUIRED) find_package(BCM_HOST QUIET) @@ -58,6 +59,7 @@ target_link_libraries(${MODULE_NAME} PRIVATE CompileSettingsDebug::CompileSettingsDebug ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${NAMESPACE}DSManagerPlugin::${NAMESPACE}DSManagerPlugin ${NAMESPACE}Definitions::${NAMESPACE}Definitions) if (PLUGIN_DISPLAYINFO_BCM_VERSION_MAJOR) diff --git a/DisplayInfo/DeviceSettings/PlatformImplementation.cpp b/DisplayInfo/DeviceSettings/PlatformImplementation.cpp index 41ba06be45..1e11a1029a 100644 --- a/DisplayInfo/DeviceSettings/PlatformImplementation.cpp +++ b/DisplayInfo/DeviceSettings/PlatformImplementation.cpp @@ -38,6 +38,7 @@ #include "libIBus.h" #include "libIBusDaemon.h" #include "dsMgr.h" +#include "dsmanagerplugin/DSManagerPlugin.h" #define EDID_MAX_HORIZONTAL_SIZE 21 #define EDID_MAX_VERTICAL_SIZE 22 @@ -49,7 +50,8 @@ class DisplayInfoImplementation : public Exchange::IGraphicsProperties, public Exchange::IConnectionProperties, public Exchange::IHDRProperties, - public Exchange::IDisplayProperties { + public Exchange::IDisplayProperties, + public DSManagerPlugin { private: using HdrteratorImplementation = RPC::IteratorType; using ColorimetryIteratorImplementation = RPC::IteratorType; @@ -65,12 +67,10 @@ class DisplayInfoImplementation : IARM_CHECK( IARM_Bus_RegisterEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_RES_POSTCHANGE, ResolutionChange) ); //TODO: this is probably per process so we either need to be running in our own process or be carefull no other plugin is calling it - device::Manager::Initialize(); - TRACE(Trace::Information, (_T("device::Manager::Initialize success"))); } catch(...) { - TRACE(Trace::Error, (_T("device::Manager::Initialize failed"))); + TRACE(Trace::Error, (_T("Initialize failed"))); } } @@ -216,7 +216,8 @@ class DisplayInfoImplementation : if (ret == Core::ERROR_NONE) { uint32_t edidLen = edidVec.size(); - unsigned char* edidbytes = new unsigned char [edidLen]; + std::unique_ptr edidbytes_ {new unsigned char [edidLen]}; + unsigned char *edidbytes = edidbytes_.get(); std::copy(edidVec.begin(), edidVec.end(), edidbytes); if (edid_parser::EDID_Verify(edidbytes, edidLen) == edid_parser::EDID_STATUS_OK) { @@ -230,7 +231,6 @@ class DisplayInfoImplementation : TRACE(Trace::Information, (_T("EDID Verification failed"))); ret = Core::ERROR_GENERAL; } - delete edidbytes; } else { @@ -306,24 +306,32 @@ class DisplayInfoImplementation : uint32_t WidthInCentimeters(uint8_t& width /* @out */) const override { - int ret = Core::ERROR_NONE; - vector edidVec; - ret = GetEdidBytes(edidVec); - if (Core::ERROR_NONE == ret) + try { std::string strVideoPort = device::Host::getInstance().getDefaultVideoPortName(); - if(edidVec.size() > EDID_MAX_VERTICAL_SIZE) - { - width = edidVec[EDID_MAX_HORIZONTAL_SIZE]; - TRACE(Trace::Information, (_T("Width in cm = %d"), width)); - } - else + ::device::VideoOutputPort vPort = ::device::Host::getInstance().getVideoOutputPort(strVideoPort.c_str()); + if (vPort.isDisplayConnected()) { - TRACE(Trace::Information, (_T("Failed to get Display Size!"))); - ret = Core::ERROR_GENERAL; + std::vector edidVec; + + vPort.getDisplay().getEDIDBytes(edidVec); + + if(edidVec.size() > EDID_MAX_HORIZONTAL_SIZE) + { + width = edidVec[EDID_MAX_HORIZONTAL_SIZE]; + TRACE(Trace::Information, (_T("Width in cm = %d"), width)); + } + else + { + TRACE(Trace::Information, (_T("Failed to get Display Size!"))); + } } } - return ret; + catch (const device::Exception& err) + { + TRACE(Trace::Error, (_T("Exception during DeviceSetting library call. code = %d message = %s"), err.getCode(), err.what())); + } + return (Core::ERROR_NONE); } uint32_t HeightInCentimeters(uint8_t& height /* @out */) const override @@ -582,7 +590,8 @@ class DisplayInfoImplementation : if (ret == Core::ERROR_NONE) { uint32_t edidLen = edidVec.size(); - unsigned char* edidbytes = new unsigned char [edidLen]; + std::unique_ptr edidbytes_ {new unsigned char [edidLen]}; + unsigned char *edidbytes = edidbytes_.get(); std::copy(edidVec.begin(), edidVec.end(), edidbytes); if (edid_parser::EDID_Verify(edidbytes, edidLen) == edid_parser::EDID_STATUS_OK) { @@ -605,7 +614,6 @@ class DisplayInfoImplementation : TRACE(Trace::Error, (_T("EDID Verification failed"))); ret = Core::ERROR_GENERAL; } - delete edidbytes; } else { diff --git a/DisplayInfo/DisplayInfo.h b/DisplayInfo/DisplayInfo.h index 4c24e3cf90..f881d2e382 100644 --- a/DisplayInfo/DisplayInfo.h +++ b/DisplayInfo/DisplayInfo.h @@ -65,7 +65,6 @@ namespace Plugin { private: DisplayInfo& _parent; - Exchange::IConnectionProperties* _client; }; public: diff --git a/DisplaySettings/CMakeLists.txt b/DisplaySettings/CMakeLists.txt index eb61307f32..6e42d4a75a 100644 --- a/DisplaySettings/CMakeLists.txt +++ b/DisplaySettings/CMakeLists.txt @@ -22,6 +22,8 @@ set(PLUGIN_DISPLAYSETTINGS_AUTOSTART "false" CACHE STRING "Automatically start D set(PLUGIN_DISPLAYSETTINGS_STARTUPORDER "" CACHE STRING "To configure startup order of DisplaySettings plugin") find_package(${NAMESPACE}Plugins REQUIRED) +find_package(${NAMESPACE}DSManagerPlugin REQUIRED) + add_library(${MODULE_NAME} SHARED DisplaySettings.cpp @@ -41,7 +43,7 @@ if (DS_FOUND) add_definitions(-DDS_FOUND) target_include_directories(${MODULE_NAME} PRIVATE ${IARMBUS_INCLUDE_DIRS}) target_include_directories(${MODULE_NAME} PRIVATE ${DS_INCLUDE_DIRS}) - target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${IARMBUS_LIBRARIES} ${DS_LIBRARIES} "-ltr181api") + target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${IARMBUS_LIBRARIES} ${DS_LIBRARIES} "-ltr181api" ${NAMESPACE}DSManagerPlugin::${NAMESPACE}DSManagerPlugin) else (DS_FOUND) target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins) endif(DS_FOUND) diff --git a/DisplaySettings/DisplaySettings.cpp b/DisplaySettings/DisplaySettings.cpp index d40033eda5..97eb390af0 100644 --- a/DisplaySettings/DisplaySettings.cpp +++ b/DisplaySettings/DisplaySettings.cpp @@ -259,6 +259,9 @@ namespace WPEFramework { registerMethodLockedApi("setZoomSetting", &DisplaySettings::setZoomSetting, this); registerMethodLockedApi("getCurrentResolution", &DisplaySettings::getCurrentResolution, this); registerMethodLockedApi("setCurrentResolution", &DisplaySettings::setCurrentResolution, this); + registerMethodLockedApi("setCurrentResolutionOwner", &DisplaySettings::setCurrentResolutionOwner, this); + registerMethodLockedApi("getEnableVideoPort", &DisplaySettings::getEnableVideoPort, this); + registerMethodLockedApi("setEnableVideoPort", &DisplaySettings::setEnableVideoPort, this); registerMethodLockedApi("getSoundMode", &DisplaySettings::getSoundMode, this); registerMethodLockedApi("setSoundMode", &DisplaySettings::setSoundMode, this); registerMethodLockedApi("readEDID", &DisplaySettings::readEDID, this); @@ -619,16 +622,6 @@ namespace WPEFramework { { m_isPwrMgr2RFCEnabled = true; } - try - { - //TODO(MROLLINS) this is probably per process so we either need to be running in our own process or be carefull no other plugin is calling it - device::Manager::Initialize(); - LOGINFO("device::Manager::Initialize success"); - } - catch(...) - { - LOGINFO("device::Manager::Initialize failed"); - } } void DisplaySettings::DeinitializeIARM() @@ -653,19 +646,6 @@ namespace WPEFramework { IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_DSMGR_NAME, IARM_BUS_DSMGR_EVENT_AUDIO_PRIMARY_LANGUAGE_CHANGED, dsSettingsChangeEventHandler) ); IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_DSMGR_NAME, IARM_BUS_DSMGR_EVENT_AUDIO_SECONDARY_LANGUAGE_CHANGED, dsSettingsChangeEventHandler) ); } - - try - { - //TODO(MROLLINS) this is probably per process so we either need to be running in our own process or be carefull no other plugin is calling it - //No need to call device::Manager::DeInitialize for individual plugin. As it is a singleton instance and shared among all wpeframework plugins - //Expecting DisplaySettings will be alive for complete run time of wpeframework - device::Manager::DeInitialize(); - LOGINFO("device::Manager::DeInitialize success"); - } - catch(...) - { - LOGINFO("device::Manager::DeInitialize failed"); - } } void DisplaySettings::ResolutionPreChange(const char *owner, IARM_EventId_t eventId, void *data, size_t len) @@ -1116,11 +1096,23 @@ namespace WPEFramework { if(tvResolutions & dsTV_RESOLUTION_1080i)supportedTvResolutions.emplace_back("1080i"); if(tvResolutions & dsTV_RESOLUTION_1080p)supportedTvResolutions.emplace_back("1080p"); if(tvResolutions & dsTV_RESOLUTION_1080p24)supportedTvResolutions.emplace_back("1080p24"); - if(tvResolutions & dsTV_RESOLUTION_1080i25)supportedTvResolutions.emplace_back("1080i25"); +#ifdef PLATFORM_BROADCOM + // additional mapping introduced by Broadcom in: https://code.rdkcentral.com/r/plugins/gitiles/collaboration/soc/broadcom/yocto_oe/layers/meta-rdk-broadcom-next/+/refs/tags/6.0.0_RELEASE/meta-brcm-refboard/meta-rdk-video/recipes-extended/devicesettings-hal-headers/files/SGMM393-104_TvResolutions_adding_more_resolutions.patch + if(tvResolutions & dsTV_RESOLUTION_1080p25)supportedTvResolutions.emplace_back("1080p25"); +#else + // HAL interface provided by Broadcom does not contain such resolution, dsTV_RESOLUTION_1080i25, removed by patch + // reference: https://code.rdkcentral.com/r/plugins/gitiles/collaboration/soc/broadcom/yocto_oe/layers/meta-rdk-broadcom-next/+/refs/tags/6.0.0_RELEASE/meta-brcm-refboard/meta-rdk-video/recipes-extended/devicesettings-hal-headers/files/SGMM393-104_TvResolutions_adding_more_resolutions.patch#9 + if(tvResolutions & dsTV_RESOLUTION_1080i25)supportedTvResolutions.emplace_back("1080i25"); +#endif if(tvResolutions & dsTV_RESOLUTION_1080p30)supportedTvResolutions.emplace_back("1080p30"); if(tvResolutions & dsTV_RESOLUTION_1080i50)supportedTvResolutions.emplace_back("1080i50"); if(tvResolutions & dsTV_RESOLUTION_1080p50)supportedTvResolutions.emplace_back("1080p50"); if(tvResolutions & dsTV_RESOLUTION_1080p60)supportedTvResolutions.emplace_back("1080p60"); +#ifdef PLATFORM_BROADCOM + // additional mapping introduced by Broadcom in: https://code.rdkcentral.com/r/plugins/gitiles/collaboration/soc/broadcom/yocto_oe/layers/meta-rdk-broadcom-next/+/refs/tags/6.0.0_RELEASE/meta-brcm-refboard/meta-rdk-video/recipes-extended/devicesettings-hal-headers/files/SGMM393-104_TvResolutions_adding_more_resolutions.patch + if(tvResolutions & dsTV_RESOLUTION_2160p24)supportedTvResolutions.emplace_back("2160p24"); + if(tvResolutions & dsTV_RESOLUTION_2160p25)supportedTvResolutions.emplace_back("2160p25"); +#endif if(tvResolutions & dsTV_RESOLUTION_2160p30)supportedTvResolutions.emplace_back("2160p30"); if(tvResolutions & dsTV_RESOLUTION_2160p50)supportedTvResolutions.emplace_back("2160p50"); if(tvResolutions & dsTV_RESOLUTION_2160p60)supportedTvResolutions.emplace_back("2160p60"); @@ -1351,7 +1343,7 @@ namespace WPEFramework { bool hasPersist = parameters.HasLabel("persist"); bool persist = hasPersist ? parameters["persist"].Boolean() : true; if (!hasPersist) LOGINFO("persist: true"); - + bool isIgnoreEdidArg = parameters.HasLabel("ignoreEdid"); bool isIgnoreEdid = isIgnoreEdidArg ? parameters["ignoreEdid"].Boolean() : false; if (!isIgnoreEdidArg) LOGINFO("isIgnoreEdid: false"); else LOGINFO("isIgnoreEdid: %d", isIgnoreEdid); @@ -1360,7 +1352,17 @@ namespace WPEFramework { try { device::VideoOutputPort &vPort = device::Host::getInstance().getVideoOutputPort(videoDisplay); - vPort.setResolution(resolution, persist, isIgnoreEdid); + + if (parameters.HasLabel("owner")) { + string owner = parameters["owner"].String(); + if (parameters.HasLabel("timeout")) { + uint32_t timeout = parameters["timeout"].Number(); + vPort.setResolutionOwner(owner.c_str(), timeout); + } + vPort.setResolutionByOwner(resolution, owner.c_str()); + } else { + vPort.setResolution(resolution, persist, isIgnoreEdid); + } } catch (const device::Exception& err) { @@ -1370,6 +1372,88 @@ namespace WPEFramework { returnResponse(success); } + uint32_t DisplaySettings::getEnableVideoPort (const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + bool success = true; + + returnIfParamNotFound(parameters, "videoDisplay"); + string videoDisplay = parameters["videoDisplay"].String(); + + try + { + device::VideoOutputPort &vPort = device::Host::getInstance().getVideoOutputPort(videoDisplay); + bool isEnabled; + + isEnabled = vPort.isEnabled(); + response["enabled"] = isEnabled; + } + catch (const device::Exception& err) + { + LOG_DEVICE_EXCEPTION1(videoDisplay); + success = false; + } + + returnResponse(success); + } + + uint32_t DisplaySettings::setEnableVideoPort (const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + bool success = true; + + + returnIfParamNotFound(parameters, "videoDisplay"); + returnIfParamNotFound(parameters, "enabled"); + + string videoDisplay = parameters["videoDisplay"].String(); + bool isEnabled = parameters["enabled"].Boolean(); + + try + { + device::VideoOutputPort &vPort = device::Host::getInstance().getVideoOutputPort(videoDisplay); + if (isEnabled) { + vPort.enable(); + } else { + vPort.disable(); + } + } + catch (const device::Exception& err) + { + LOG_DEVICE_EXCEPTION1(videoDisplay); + success = false; + } + + returnResponse(success); + } + + + uint32_t DisplaySettings::setCurrentResolutionOwner(const JsonObject& parameters, JsonObject& response) + { //sample servicemanager response: + LOGINFOMETHOD(); + returnIfParamNotFound(parameters, "videoDisplay"); + returnIfParamNotFound(parameters, "owner"); + returnIfParamNotFound(parameters, "timeout"); + + string videoDisplay = parameters["videoDisplay"].String(); + string owner = parameters["owner"].String(); + uint32_t timeout = parameters["timeout"].Number(); + + bool success = true; + try + { + device::VideoOutputPort &vPort = device::Host::getInstance().getVideoOutputPort(videoDisplay); + vPort.setResolutionOwner(owner.c_str(), timeout); + } + catch (const device::Exception& err) + { + LOG_DEVICE_EXCEPTION2(videoDisplay, owner); + success = false; + } + returnResponse(success); + } + + uint32_t DisplaySettings::getSoundMode(const JsonObject& parameters, JsonObject& response) { //sample servicemanager response:{"success":true,"soundMode":"AUTO (Dolby Digital 5.1)"} LOGINFOMETHOD(); @@ -2221,7 +2305,6 @@ namespace WPEFramework { device::Host::getInstance().getCurrentAudioFormat(audioFormat); LOGINFO("current audio format: %d \n", audioFormat); audioFormatToString(audioFormat, response); - success = true; } catch (const device::Exception& err) { @@ -2826,7 +2909,6 @@ namespace WPEFramework { { device::AudioOutputPort aPort = device::Host::getInstance().getAudioOutputPort(audioPort); aPort.setGain(newGain); - success= true; } catch (const device::Exception& err) { @@ -2900,7 +2982,6 @@ namespace WPEFramework { params["volumeLevel"] = (int)level; sendNotify("volumeLevelChanged", params); } - success= true; } catch (const device::Exception& err) { diff --git a/DisplaySettings/DisplaySettings.h b/DisplaySettings/DisplaySettings.h index 9435506ecd..31e1a4aba2 100644 --- a/DisplaySettings/DisplaySettings.h +++ b/DisplaySettings/DisplaySettings.h @@ -28,6 +28,7 @@ #include "irMgr.h" #include "pwrMgr.h" #include "rfcapi.h" +#include "dsmanagerplugin/DSManagerPlugin.h" namespace WPEFramework { @@ -45,7 +46,7 @@ namespace WPEFramework { // As the registration/unregistration of notifications is realized by the class PluginHost::JSONRPC, // this class exposes a public method called, Notify(), using this methods, all subscribed clients // will receive a JSONRPC message as a notification, in case this method is called. - class DisplaySettings : public PluginHost::IPlugin, public PluginHost::JSONRPC { + class DisplaySettings : public PluginHost::IPlugin, public PluginHost::JSONRPC, public DSManagerPlugin { private: typedef Core::JSON::String JString; typedef Core::JSON::ArrayType JStringArray; @@ -69,6 +70,9 @@ namespace WPEFramework { uint32_t setZoomSetting(const JsonObject& parameters, JsonObject& response); uint32_t getCurrentResolution(const JsonObject& parameters, JsonObject& response); uint32_t setCurrentResolution(const JsonObject& parameters, JsonObject& response); + uint32_t setCurrentResolutionOwner(const JsonObject& parameters, JsonObject& response); + uint32_t getEnableVideoPort (const JsonObject& parameters, JsonObject& response); + uint32_t setEnableVideoPort (const JsonObject& parameters, JsonObject& response); uint32_t getSoundMode(const JsonObject& parameters, JsonObject& response); uint32_t setSoundMode(const JsonObject& parameters, JsonObject& response); uint32_t readEDID(const JsonObject& parameters, JsonObject& response); diff --git a/HdcpProfile/CMakeLists.txt b/HdcpProfile/CMakeLists.txt index 7899ba9b61..9cc5bb7eec 100644 --- a/HdcpProfile/CMakeLists.txt +++ b/HdcpProfile/CMakeLists.txt @@ -22,6 +22,7 @@ set(PLUGIN_HDCPPROFILE_AUTOSTART "false" CACHE STRING "Automatically start HdcpP set(PLUGIN_HDCPPROFILE_STARTUPORDER "" CACHE STRING "To configure startup order of HdcpProfile plugin") find_package(${NAMESPACE}Plugins REQUIRED) +find_package(${NAMESPACE}DSManagerPlugin REQUIRED) if (USE_THUNDER_R4) find_package(${NAMESPACE}COM REQUIRED) else () @@ -54,7 +55,7 @@ target_include_directories(${MODULE_NAME} PRIVATE ../helpers) set_source_files_properties(HdcpProfile.cpp PROPERTIES COMPILE_FLAGS "-fexceptions") -target_link_libraries(${MODULE_NAME} PUBLIC ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${IARMBUS_LIBRARIES} ${DS_LIBRARIES} ) +target_link_libraries(${MODULE_NAME} PUBLIC ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${IARMBUS_LIBRARIES} ${DS_LIBRARIES} ${NAMESPACE}DSManagerPlugin::${NAMESPACE}DSManagerPlugin) install(TARGETS ${MODULE_NAME} DESTINATION lib/${STORAGE_DIRECTORY}/plugins) diff --git a/HdcpProfile/HdcpProfile.cpp b/HdcpProfile/HdcpProfile.cpp index 0686c17039..175aa55e6c 100644 --- a/HdcpProfile/HdcpProfile.cpp +++ b/HdcpProfile/HdcpProfile.cpp @@ -31,6 +31,8 @@ #include "UtilsJsonRpc.h" #include "UtilsIarm.h" +#include "UtilsSynchroIarm.hpp" + #define HDMI_HOT_PLUG_EVENT_CONNECTED 0 #define HDMI_HOT_PLUG_EVENT_DISCONNECTED 1 @@ -82,7 +84,6 @@ namespace WPEFramework { HdcpProfile::_instance = this; InitializeIARM(); - device::Manager::Initialize(); return (string()); } @@ -99,8 +100,8 @@ namespace WPEFramework Utils::IARM::init(); IARM_Result_t res; - IARM_CHECK( IARM_Bus_RegisterEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG, dsHdmiEventHandler) ); - IARM_CHECK( IARM_Bus_RegisterEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDCP_STATUS, dsHdmiEventHandler) ); + IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG, dsHdmiEventHandler) ); + IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDCP_STATUS, dsHdmiEventHandler) ); } void HdcpProfile::DeinitializeIARM() @@ -108,15 +109,15 @@ namespace WPEFramework if (Utils::IARM::isConnected()) { IARM_Result_t res; - IARM_CHECK( IARM_Bus_RemoveEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG, dsHdmiEventHandler) ); - IARM_CHECK( IARM_Bus_RemoveEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDCP_STATUS, dsHdmiEventHandler) ); + IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG, dsHdmiEventHandler) ); + IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDCP_STATUS, dsHdmiEventHandler) ); } } void HdcpProfile::RegisterAll() { - Register(_T(HDCP_PROFILE_METHOD_GET_HDCP_STATUS), &HdcpProfile::getHDCPStatusWrapper, this); - Register(_T(HDCP_PROFILE_METHOD_GET_SETTOP_HDCP_SUPPORT), &HdcpProfile::getSettopHDCPSupportWrapper, this); + Utils::Synchro::RegisterLockedApi(_T(HDCP_PROFILE_METHOD_GET_HDCP_STATUS), &HdcpProfile::getHDCPStatusWrapper, this); + Utils::Synchro::RegisterLockedApi(_T(HDCP_PROFILE_METHOD_GET_SETTOP_HDCP_SUPPORT), &HdcpProfile::getSettopHDCPSupportWrapper, this); } void HdcpProfile::UnregisterAll() diff --git a/HdcpProfile/HdcpProfile.h b/HdcpProfile/HdcpProfile.h index da5000d11a..3b6bd1a1ae 100644 --- a/HdcpProfile/HdcpProfile.h +++ b/HdcpProfile/HdcpProfile.h @@ -22,6 +22,7 @@ #include "libIBus.h" #include "Module.h" +#include "dsmanagerplugin/DSManagerPlugin.h" namespace WPEFramework { @@ -39,7 +40,7 @@ namespace WPEFramework { // As the registration/unregistration of notifications is realized by the class PluginHost::JSONRPC, // this class exposes a public method called, Notify(), using this methods, all subscribed clients // will receive a JSONRPC message as a notification, in case this method is called. - class HdcpProfile : public PluginHost::IPlugin, public PluginHost::JSONRPC { + class HdcpProfile : public PluginHost::IPlugin, public PluginHost::JSONRPC, public DSManagerPlugin { private: // We do not allow this plugin to be copied !! diff --git a/HdmiCec/HdmiCec.cpp b/HdmiCec/HdmiCec.cpp index e5b08310f2..b6b811cfa3 100644 --- a/HdmiCec/HdmiCec.cpp +++ b/HdmiCec/HdmiCec.cpp @@ -45,11 +45,13 @@ #define HDMICEC_METHOD_GET_CEC_ADDRESSES "getCECAddresses" #define HDMICEC_METHOD_SEND_MESSAGE "sendMessage" #define HDMICEC_METHOD_GET_ACTIVE_SOURCE_STATUS "getActiveSourceStatus" +#define HDMICEC_METHOD_TRIGGER_ACTION "triggerAction" #define HDMICEC_EVENT_ON_DEVICES_CHANGED "onDevicesChanged" #define HDMICEC_EVENT_ON_MESSAGE "onMessage" #define HDMICEC_EVENT_ON_HDMI_HOT_PLUG "onHdmiHotPlug" #define HDMICEC_EVENT_ON_CEC_ADDRESS_CHANGE "cecAddressesChanged" +#define HDMICEC_EVENT_ON_STANDBY_MSG_RECEIVED "standbyMessageReceived" #define PHYSICAL_ADDR_CHANGED 1 #define LOGICAL_ADDR_CHANGED 2 @@ -62,17 +64,19 @@ #define API_VERSION_NUMBER_PATCH 11 enum { - HDMICEC_EVENT_DEVICE_ADDED=0, - HDMICEC_EVENT_DEVICE_REMOVED, - HDMICEC_EVENT_DEVICE_INFO_UPDATED, - HDMICEC_EVENT_ACTIVE_SOURCE_STATUS_UPDATED, + HDMICEC_EVENT_DEVICE_ADDED=0, + HDMICEC_EVENT_DEVICE_REMOVED, + HDMICEC_EVENT_DEVICE_INFO_UPDATED, + HDMICEC_EVENT_ACTIVE_SOURCE_STATUS_UPDATED, + HDMICEC_EVENT_STANDBY_MESSAGE_RECEIVED, }; static const char *eventString[] = { - "onDeviceAdded", - "onDeviceRemoved", - "onDeviceInfoUpdated", - "onActiveSourceStatusUpdated" + "onDeviceAdded", + "onDeviceRemoved", + "onDeviceInfoUpdated", + "onActiveSourceStatusUpdated", + "standbyMessageReceived" }; static bool isDeviceActiveSource = false; @@ -82,7 +86,7 @@ static bool isDeviceActiveSource = false; #elif defined(HAS_PERSISTENT_IN_FLASH) #define CEC_SETTING_ENABLED_FILE "/opt/persistent/ds/cecData.json" #else -#define CEC_SETTING_ENABLED_FILE "/opt/ds/cecData.json" +#define CEC_SETTING_ENABLED_FILE "/etc/WPEFramework/plugins/HdmiCecConfig.json" #endif #define CEC_SETTING_ENABLED "cecEnabled" @@ -189,6 +193,9 @@ namespace WPEFramework Utils::Synchro::RegisterLockedApi(HDMICEC_METHOD_SEND_MESSAGE, &HdmiCec::sendMessageWrapper, this); Utils::Synchro::RegisterLockedApi(HDMICEC_METHOD_GET_ACTIVE_SOURCE_STATUS, &HdmiCec::getActiveSourceStatus, this); Utils::Synchro::RegisterLockedApi("getDeviceList", &HdmiCec::getDeviceList, this); +#ifdef LGI_CUSTOM_IMPL + Utils::Synchro::RegisterLockedApi(HDMICEC_METHOD_TRIGGER_ACTION, &HdmiCec::triggerActionWrapper, this); +#endif physicalAddress = 0x0F0F0F0F; @@ -196,6 +203,9 @@ namespace WPEFramework logicalAddress = 0xFF; loadSettings(); +#ifdef LGI_CUSTOM_IMPL + isDeviceActiveSource = checkActiveSource(); +#endif if (cecSettingEnabled) { setEnabled(cecSettingEnabled); @@ -242,10 +252,15 @@ namespace WPEFramework if (Utils::IARM::init()) { IARM_Result_t res; +#ifndef LGI_CUSTOM_IMPL //IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_CECHOST_NAME, IARM_BUS_CECHost_EVENT_DEVICESTATUSCHANGE,cecDeviceStatusEventHandler) ); // It didn't do anything in original service IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_CECMGR_NAME, IARM_BUS_CECMGR_EVENT_DAEMON_INITIALIZED,cecMgrEventHandler) ); IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_CECMGR_NAME, IARM_BUS_CECMGR_EVENT_STATUS_UPDATED,cecMgrEventHandler) ); IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG, dsHdmiEventHandler) ); +#else + IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_CECHOST_NAME, IARM_BUS_CECHost_EVENT_ACTIVESTATUSCHANGE,cecActiveSourceEventHandler) ); + IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_CECHOST_NAME, IARM_BUS_CECHost_EVENT_STANDBYREQUESTRECEIVED, cecStandbyEventHandler) ); +#endif } } @@ -254,10 +269,15 @@ namespace WPEFramework if (Utils::IARM::isConnected()) { IARM_Result_t res; - //IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_CECHOST_NAME, IARM_BUS_CECHost_EVENT_DEVICESTATUSCHANGE, cecDeviceStatusEventHandler) ); +#ifndef LGI_CUSTOM_IMPL + //IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_CECHOST_NAME, IARM_BUS_CECHost_EVENT_DEVICESTATUSCHANGE, cecDeviceStatusEventHandler) ); IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_CECMGR_NAME, IARM_BUS_CECMGR_EVENT_DAEMON_INITIALIZED,cecMgrEventHandler) ); IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_CECMGR_NAME, IARM_BUS_CECMGR_EVENT_STATUS_UPDATED,cecMgrEventHandler) ); IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG, dsHdmiEventHandler) ); +#else + IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_CECHOST_NAME, IARM_BUS_CECHost_EVENT_ACTIVESTATUSCHANGE,cecActiveSourceEventHandler) ); + IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_CECHOST_NAME, IARM_BUS_CECHost_EVENT_STANDBYREQUESTRECEIVED, cecStandbyEventHandler) ); +#endif } } @@ -278,11 +298,8 @@ namespace WPEFramework case IARM_BUS_CECMGR_EVENT_STATUS_UPDATED: { IARM_Bus_CECMgr_Status_Updated_Param_t *evtData = new IARM_Bus_CECMgr_Status_Updated_Param_t; - if(evtData) - { - memcpy(evtData,data,sizeof(IARM_Bus_CECMgr_Status_Updated_Param_t)); - HdmiCec::_instance->cecStatusUpdated(evtData); - } + memcpy(evtData,data,sizeof(IARM_Bus_CECMgr_Status_Updated_Param_t)); + HdmiCec::_instance->cecStatusUpdated(evtData); } break; default: @@ -304,7 +321,9 @@ namespace WPEFramework LOGINFO("Received IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG event data:%d \r\n", hdmi_hotplug_event); HdmiCec::_instance->onHdmiHotPlug(hdmi_hotplug_event); //Trigger CEC device poll here +#ifndef LGI_CUSTOM_IMPL pthread_cond_signal(&(_instance->m_condSig)); +#endif } } @@ -459,9 +478,10 @@ namespace WPEFramework return; } +#ifndef LGI_CUSTOM_IMPL char c; IARM_Result_t retVal = IARM_RESULT_SUCCESS; - retVal = IARM_Bus_Call_with_IPCTimeout(IARM_BUS_CECMGR_NAME, IARM_BUS_CECMGR_API_isAvailable, (void *)&c, sizeof(c), 1000); + retVal = IARM_Bus_Call_with_IPCTimeout(IARM_BUS_CECMGR_NAME, IARM_BUS_CECMGR_API_isAvailable, (void *)&c, sizeof(c), 15000); if(retVal != IARM_RESULT_SUCCESS) { LOGINFO("CECMGR is not available. Failed to enable HdmiCec Plugin"); cecEnableStatus = false; @@ -521,6 +541,7 @@ namespace WPEFramework } } +#endif cecEnableStatus = true; return; } @@ -534,7 +555,7 @@ namespace WPEFramework LOGWARN("CEC Already Disabled "); return; } - +#ifndef LGI_CUSTOM_IMPL if (smConnection != NULL) { LOGWARN("Stop Thread %p", smConnection ); @@ -579,7 +600,9 @@ namespace WPEFramework { libcecInitStatus--; } - +#else + cecEnableStatus = false; +#endif return; } @@ -651,14 +674,6 @@ namespace WPEFramework std::string HdmiCec::getName() { - //SVCLOG_WARN("%s \r\n",__FUNCTION__); - IARM_Result_t ret = IARM_RESULT_INVALID_STATE; - if (ret != IARM_RESULT_SUCCESS) - { - LOGWARN("getName :: IARM_BUS_CEC_HOST_GetOSDName failed "); - return "STB"; - } - return "STB"; } @@ -731,7 +746,7 @@ namespace WPEFramework void HdmiCec::sendMessage(std::string message) { LOGINFO("sendMessage "); - +#ifndef LGI_CUSTOM_IMPL if(true == cecEnableStatus) { std::vector buf; @@ -757,6 +772,7 @@ namespace WPEFramework else LOGWARN("cecEnableStatus=false"); return; +#endif } void HdmiCec::sendActiveSourceEvent() @@ -767,6 +783,14 @@ namespace WPEFramework sendNotify(eventString[HDMICEC_EVENT_ACTIVE_SOURCE_STATUS_UPDATED], params); } + void HdmiCec::sendStandbyMessageReceived(int logicalAddress) + { + JsonObject params; + params["logicalAddress"] = logicalAddress; + LOGWARN("sendStandbyMessageReceived logicalAddress: %d", logicalAddress); + sendNotify(eventString[HDMICEC_EVENT_STANDBY_MESSAGE_RECEIVED], params); + } + void HdmiCec::cecAddressesChanged(int changeStatus) { JsonObject params; @@ -806,6 +830,7 @@ namespace WPEFramework if(length >=2) { +#ifndef LGI_CUSTOM_IMPL if(input_frameBuf[1] == ROUTING_CHANGE || input_frameBuf[1] == ROUTING_INFORMATION || input_frameBuf[1] == ACTIVE_SOURCE || input_frameBuf[1] == SET_STREAM_PATH) { int paIndex = (input_frameBuf[1]==ROUTING_CHANGE) ? 4: 2; @@ -819,6 +844,7 @@ namespace WPEFramework HdmiCec::_instance->sendActiveSourceEvent(); LOGINFO("Active Source Event : Device Physical Address :%x Physical Address from message :%x isDeviceActiveSource status :%d ",physicalAddress,tempPhyAddres,isDeviceActiveSource); } +#endif } std::vector buf; @@ -847,7 +873,9 @@ namespace WPEFramework { //sample servicemanager response: LOGINFOMETHOD(); //Trigger CEC device poll here +#ifndef LGI_CUSTOM_IMPL pthread_cond_signal(&(_instance->m_condSig)); +#endif bool success = true; response["numberofdevices"] = HdmiCec::_instance->m_numberOfDevices; @@ -1049,6 +1077,7 @@ namespace WPEFramework requestOsdName (newDevlogicalAddress); } +#ifndef LGI_CUSTOM_IMPL void HdmiCec::threadRun() { if(!HdmiCec::_instance) @@ -1059,18 +1088,21 @@ namespace WPEFramework int i = 0; pthread_mutex_lock(&(_instance->m_lock));//pthread_cond_wait should be mutex protected. //pthread_cond_wait will unlock the mutex and perfoms wait for the condition. while (!_instance->m_pollThreadExit) { - bool isActivateUpdateThread = false; - LOGINFO("Starting cec device polling"); - for(i=0; i< LogicalAddress::UNREGISTERED; i++ ) { - bool isConnected = _instance->pingDeviceUpdateList(i); - if (isConnected){ - isActivateUpdateThread = isConnected; - } - } - if (isActivateUpdateThread){ - //i any of devices is connected activate thread update check - pthread_cond_signal(&(_instance->m_condSigUpdate)); - } + { + Utils::Synchro::LockApiGuard lockGuard; + bool isActivateUpdateThread = false; + LOGINFO("Starting cec device polling"); + for(i=0; i< LogicalAddress::UNREGISTERED; i++ ) { + bool isConnected = _instance->pingDeviceUpdateList(i); + if (isConnected){ + isActivateUpdateThread = isConnected; + } + } + if (isActivateUpdateThread){ + //i any of devices is connected activate thread update check + pthread_cond_signal(&(_instance->m_condSigUpdate)); + } + } //Wait for mutex signal here to continue the worker thread again. while (!_instance->m_pollThreadExit && ETIMEDOUT == pthread_cond_timedwait(&(_instance->m_condSig), &(_instance->m_lock), &hundred_ms)); @@ -1092,6 +1124,8 @@ namespace WPEFramework //Wait for mutex signal here to continue the worker thread again. while (!_instance->m_updateThreadExit && ETIMEDOUT == pthread_cond_timedwait(&(_instance->m_condSigUpdate), &(_instance->m_lockUpdate), &hundred_ms)); if (_instance->m_updateThreadExit) break; + Utils::Synchro::LockApiGuard lockGuard; + LOGINFO("Starting cec device update check"); for(i=0; ((i< LogicalAddress::UNREGISTERED)&&(!_instance->m_updateThreadExit)); i++ ) { //If details are not updated. update now. @@ -1104,10 +1138,12 @@ namespace WPEFramework if (!HdmiCec::_instance->deviceList[i].m_isOSDNameUpdated){ iCounter = 0; + lockGuard.unlock(); while ((!_instance->m_updateThreadExit) && (iCounter < (2*10))) { //sleep for 2sec. usleep (100 * 1000); //sleep for 100 milli sec iCounter ++; } + lockGuard.lock(); HdmiCec::_instance->requestOsdName (i); retry = true; } @@ -1116,11 +1152,14 @@ namespace WPEFramework } if (!HdmiCec::_instance->deviceList[i].m_isVendorIDUpdated){ + Utils::Synchro::UnlockApiGuard ulockGuard; iCounter = 0; + lockGuard.unlock(); while ((!_instance->m_updateThreadExit) && (iCounter < (2*10))) { //sleep for 1sec. usleep (100 * 1000); //sleep for 100 milli sec iCounter ++; } + lockGuard.lock(); HdmiCec::_instance->requestVendorID (i); retry = true; @@ -1136,6 +1175,115 @@ namespace WPEFramework pthread_mutex_unlock(&(_instance->m_lockUpdate)); LOGINFO("%s: Thread exited", __FUNCTION__); } +#endif + +#ifdef LGI_CUSTOM_IMPL + bool HdmiCec::checkActiveSource() + { + bool result = false; + IARM_Bus_CECHost_GetActiveStatus_Param_t param; + IARM_Result_t res; + res = IARM_Bus_Call(IARM_BUS_CECHOST_NAME, IARM_BUS_CEC_HOST_GetActiveStatus, (void *)¶m, sizeof(param)); + if (res == IARM_RESULT_SUCCESS) + { + result = param.isActiveStatus; + LOGERR("IARM_BUS_CEC_HOST_GetActiveStatus result: %d", result); + } + else + { + LOGERR("IARM_BUS_CEC_HOST_GetActiveStatus failed"); + } + return result; + } + + void HdmiCec::updateActiveSource(bool activeSource) + { + LOGINFO("updateActiveSource newState=%d lastState=%d", activeSource, isDeviceActiveSource); + if (activeSource != isDeviceActiveSource) + { + isDeviceActiveSource = activeSource; + sendActiveSourceEvent(); + } + } + + void HdmiCec::cecActiveSourceEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len) + { + LOGINFO("owner=%s eventId=%d", owner, eventId); + if(!HdmiCec::_instance) + { + return; + } + + if(!strcmp(owner, IARM_BUS_CECHOST_NAME)) + { + if (IARM_BUS_CECHost_EVENT_ACTIVESTATUSCHANGE == eventId) + { + _IARM_Bus_CECHost_ActiveStatusChanged_EventData_t *eventData = (_IARM_Bus_CECHost_ActiveStatusChanged_EventData_t *)data; + LOGINFO("eventData->isActiveStatus=%d", eventData->isActiveStatus); + HdmiCec::_instance->updateActiveSource(eventData->isActiveStatus); + } + } + } + + void HdmiCec::cecStandbyEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len) + { + LOGINFO("owner=%s eventId=%d", owner, eventId); + if(!HdmiCec::_instance) + { + return; + } + + if(!strcmp(owner, IARM_BUS_CECHOST_NAME)) + { + if (IARM_BUS_CECHost_EVENT_STANDBYREQUESTRECEIVED == eventId) + { + IARM_Bus_CECHost_StandbyRequestReceived_t *eventData = (IARM_Bus_CECHost_StandbyRequestReceived_t *)data; + LOGINFO("eventData->from=%d", eventData->from); + HdmiCec::_instance->sendStandbyMessageReceived(eventData->from); + } + } + } + + uint32_t HdmiCec::triggerActionWrapper(const JsonObject ¶meters, JsonObject &response) + { + LOGINFO(); + + const char *parameterName = "actionName"; + returnIfStringParamNotFound(parameters, parameterName); + + string actionName; + getStringParameter(parameterName, actionName); + + LOGINFO("CEC: %s Triggering action[%s]\n", __FUNCTION__, actionName.c_str()); + + IARM_Bus_CECHost_TriggerAction_Param_t param; + strncpy(param.name, + actionName.c_str(), + sizeof(param.name)); + param.name[sizeof(param.name) - 1] = '\0'; + param.destination = 0x0F; + + const IARM_Result_t ret = IARM_Bus_Call(IARM_BUS_CECHOST_NAME, + IARM_BUS_CEC_HOST_TriggerAction, + static_cast(¶m), sizeof(param)); + + if (IARM_RESULT_SUCCESS != ret) + { + LOGERR("CEC: ERROR - %s CALL[%s], ACTION[%s] failed result %d\n", + __FUNCTION__, + IARM_BUS_CEC_HOST_TriggerAction, + actionName.c_str(), + ret); + returnResponse(false); + } + + LOGINFO("CEC: SUCCESS - %s CALL[%s], ACTION[%s]\n", __FUNCTION__, IARM_BUS_CEC_HOST_TriggerAction, + actionName.c_str()); + + returnResponse(true); + } + +#endif } // namespace Plugin } // namespace WPEFramework diff --git a/HdmiCec/HdmiCec.h b/HdmiCec/HdmiCec.h index 359f56f7f4..ceb13c50d3 100644 --- a/HdmiCec/HdmiCec.h +++ b/HdmiCec/HdmiCec.h @@ -54,8 +54,8 @@ namespace WPEFramework { LogicalAddress m_logicalAddress; VendorID m_vendorID; OSDName m_osdName; - // - short m_deviceInfoStatus; + // + int16_t m_deviceInfoStatus; bool m_isOSDNameUpdated; bool m_isVendorIDUpdated; @@ -143,6 +143,7 @@ namespace WPEFramework { void sendUnencryptMsg(unsigned char* msg, int size); void sendDeviceUpdateInfo(const int logicalAddress); void sendActiveSourceEvent(); + void sendStandbyMessageReceived(const int logicalAddress); void process (const ActiveSource &msg, const Header &header); void process (const ImageViewOn &msg, const Header &header); @@ -161,10 +162,12 @@ namespace WPEFramework { public: static HdmiCec* _instance; CECDeviceInfo deviceList[16]; +#ifndef LGI_CUSTOM_IMPL pthread_cond_t m_condSig; pthread_mutex_t m_lock; pthread_cond_t m_condSigUpdate; pthread_mutex_t m_lockUpdate; +#endif private: std::string logicalAddressDeviceType; unsigned int logicalAddress; @@ -173,10 +176,12 @@ namespace WPEFramework { bool cecEnableStatus; Connection *smConnection; int m_numberOfDevices; +#ifndef LGI_CUSTOM_IMPL std::atomic_bool m_pollThreadExit; Utils::ThreadRAII m_pollThread; std::atomic_bool m_updateThreadExit; Utils::ThreadRAII m_UpdateThread; +#endif const void InitializeIARM(); void DeinitializeIARM(); @@ -215,9 +220,18 @@ namespace WPEFramework { void notify(const CECFrame &in) const; void onMessage(const char *message); +#ifndef LGI_CUSTOM_IMPL static void threadRun(); static void threadUpdateCheck(); - +#endif + +#ifdef LGI_CUSTOM_IMPL + bool checkActiveSource(); + static void cecActiveSourceEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len); + static void cecStandbyEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len); + void updateActiveSource(bool activeSource); + uint32_t triggerActionWrapper(const JsonObject& parameters, JsonObject& response); +#endif }; } // namespace Plugin } // namespace WPEFramework diff --git a/HdmiCec/HdmiCec.json b/HdmiCec/HdmiCec.json index 418ccc6d12..e8b8f31ee2 100644 --- a/HdmiCec/HdmiCec.json +++ b/HdmiCec/HdmiCec.json @@ -23,6 +23,11 @@ "example": "255, 255, 255, 255" } }, + "actionName":{ + "summary": "Action name: POWER_ON or POWER_OFF.", + "type": "string", + "example": "POWER_ON" + }, "message":{ "summary": "The message is a base64 encoded byte array of the raw CEC bytes. The CEC message includes the device ID for the intended destination.", "type": "string", @@ -204,7 +209,24 @@ "result": { "$ref": "#/common/result" } - } + }, + "triggerAction":{ + "summary": "Triggers actions like POWER_ON or POWER_OFF", + "params": { + "type":"object", + "properties": { + "actionName":{ + "$ref": "#/definitions/actionName" + } + }, + "required": [ + "actionName" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, }, "events": { "cecAddressesChanged":{ @@ -296,6 +318,20 @@ "message" ] } + }, + "standbyMessageReceived":{ + "summary": "Triggered when the source device changes status to `STANDBY`", + "params": { + "type":"object", + "properties": { + "logicalAddress": { + "$ref": "#/definitions/logicalAddress" + } + }, + "required": [ + "logicalAddress" + ] + } } } } diff --git a/LgiDisplaySettings/CMakeLists.txt b/LgiDisplaySettings/CMakeLists.txt new file mode 100644 index 0000000000..73ca283348 --- /dev/null +++ b/LgiDisplaySettings/CMakeLists.txt @@ -0,0 +1,59 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# Copyright 2021 Liberty Global Service B.V. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(PLUGIN_NAME LgiDisplaySettings) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) + +find_package(${NAMESPACE}Plugins REQUIRED) + +add_library(${MODULE_NAME} SHARED + ../DisplaySettings/DisplaySettings.cpp + LgiDisplaySettings.cpp + Module.cpp) + +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +target_include_directories(${MODULE_NAME} PRIVATE ../helpers ../DisplaySettings ./) + +add_compile_definitions(USE_LGI_DEVICESETTINGS_IMPLEMENTATION) + +find_package(CURL) +if (CURL_FOUND) + include_directories(${CURL_INCLUDE_DIRS}) + target_link_libraries(${MODULE_NAME} PRIVATE ${CURL_LIBRARIES}) +else (CURL_FOUND) + message ("Curl/libcurl required.") +endif (CURL_FOUND) + +find_package(DS) +if (DS_FOUND) + find_package(IARMBus) + add_definitions(-DDS_FOUND) + target_include_directories(${MODULE_NAME} PRIVATE ${IARMBUS_INCLUDE_DIRS}) + target_include_directories(${MODULE_NAME} PRIVATE ${DS_INCLUDE_DIRS}) + target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${IARMBUS_LIBRARIES} ${DS_LIBRARIES}) +else (DS_FOUND) + target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins) +endif(DS_FOUND) + +install(TARGETS ${MODULE_NAME} + DESTINATION lib/${STORAGE_DIRECTORY}/plugins) + +write_config(${PLUGIN_NAME}) diff --git a/LgiDisplaySettings/LgiDisplaySettings.config b/LgiDisplaySettings/LgiDisplaySettings.config new file mode 100644 index 0000000000..1294ae9d00 --- /dev/null +++ b/LgiDisplaySettings/LgiDisplaySettings.config @@ -0,0 +1,12 @@ +set (autostart ${PLUGIN_LGIDISPLAYSETTINGS_AUTOSTART}) +set (preconditions Platform) +set (callsign "com.lgi.rdk.DisplaySettings") +map() + key(root) + map() + kv(outofprocess ${PLUGIN_LGIDISPLAYSETTINGS_OUTOFPROCESS}) + end() +end() + +ans(configuration) + diff --git a/LgiDisplaySettings/LgiDisplaySettings.cpp b/LgiDisplaySettings/LgiDisplaySettings.cpp new file mode 100644 index 0000000000..13ac4b5481 --- /dev/null +++ b/LgiDisplaySettings/LgiDisplaySettings.cpp @@ -0,0 +1,360 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2021 RDK Management +* Copyright 2021 Liberty Global Service B.V. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "LgiDisplaySettings.h" +#include "UtilsSynchro.hpp" +#include +#include "host.hpp" +#include "exception.hpp" +#include "videoOutputPort.hpp" +#include "videoOutputPortType.hpp" +#include "videoOutputPortConfig.hpp" +#include "videoResolution.hpp" +#include "audioOutputPort.hpp" +#include "audioOutputPortType.hpp" +#include "audioOutputPortConfig.hpp" +#include "dsError.h" + +using namespace std; + +namespace WPEFramework { + + namespace Plugin { + + SERVICE_REGISTRATION(LgiDisplaySettings, 1, 0); + + extern void setResponseArray(JsonObject& response, const char* key, const vector& items); + + static bool parseQBool(const std::string& str) + { + // https://doc.qt.io/qt-5/qvariant.html#toBool: + // 'Returns true if (...) lower-case content is not one of the following: empty, "0" or "false"; otherwise returns false.' + string lowercase_string{}; + std::transform(str.begin(), + str.end(), + std::back_inserter(lowercase_string), + ::tolower); + return !(lowercase_string.empty() || lowercase_string == "false" || lowercase_string == "0"); + } + + LgiDisplaySettings::LgiDisplaySettings() : DisplaySettings() + { + Utils::Synchro::RegisterLockedApi("setOutputFrameRatePreference", &LgiDisplaySettings::setOutputFrameRatePreference, this); + Utils::Synchro::RegisterLockedApi("setAudioProcessingHint", &LgiDisplaySettings::setAudioProcessingHint, this); + Utils::Synchro::RegisterLockedApi("getAudioOutputEncoding", &LgiDisplaySettings::getAudioOutputEncoding, this); + Utils::Synchro::RegisterLockedApi("getFollowColorSpace", &LgiDisplaySettings::getFollowColorSpace, this); + Utils::Synchro::RegisterLockedApi("setFollowColorSpace", &LgiDisplaySettings::setFollowColorSpace, this); + Utils::Synchro::RegisterLockedApi("getPreferredOutputColorSpace", &LgiDisplaySettings::getPreferredOutputColorSpace, this); + Utils::Synchro::RegisterLockedApi("setPreferredOutputColorSpace", &LgiDisplaySettings::setPreferredOutputColorSpace, this); + Utils::Synchro::RegisterLockedApi("getHDRGfxColorSpace", &LgiDisplaySettings::getHDRGfxColorSpace, this); + Utils::Synchro::RegisterLockedApi("setHDRGfxColorSpace", &LgiDisplaySettings::setHDRGfxColorSpace, this); + } + + uint32_t LgiDisplaySettings::setOutputFrameRatePreference(const JsonObject& parameters, JsonObject& response) + { + // servicemanager params: (const bool followContent); + LOGINFOMETHOD(); + bool success = false; + returnIfParamNotFound(parameters, "followContent"); + const string videoDisplay = parameters.HasLabel("videoDisplay") ? parameters["videoDisplay"].String() : "HDMI0"; + const bool followContent = parseQBool(parameters["followContent"].String()); + + try + { + device::VideoOutputPort &vPort = device::Host::getInstance().getVideoOutputPort(videoDisplay); + vPort.setOutputFrameRatePreference(followContent); + success = true; + } + catch (const device::Exception& err) + { + LOG_DEVICE_EXCEPTION0(); + } + + returnResponse(success); + } + + uint32_t LgiDisplaySettings::setAudioProcessingHint(const JsonObject& parameters, JsonObject& response) + { + // servicemanager params: (QString audioPort, QString audioMode, QString audioDelayMs); + LOGINFOMETHOD(); + bool success = false; + returnIfParamNotFound(parameters, "audioMode"); + const string audioPort = parameters.HasLabel("audioPort") ? parameters["audioPort"].String() : "HDMI0"; + + int64_t _delayMs = -1; + getDefaultNumberParameter("audioDelayMs", _delayMs, -1); + if (_delayMs >= 0 && _delayMs <= std::numeric_limits::max()) + { + const string audioMode = parameters["audioMode"].String(); + const uint32_t delayMs = _delayMs; + try + { + device::AudioOutputPort &aPort = device::AudioOutputPortConfig::getInstance().getPort(audioPort); + aPort.setAudioDelayHint(delayMs, audioMode); + success = true; + } + catch(const device::Exception& err) + { + LOG_DEVICE_EXCEPTION0(); + } + } + else + { + LOGWARN("setAudioProcessingHint: audioDelayMs value %lld out of uint32_t bounds; not executed", _delayMs); + } + + returnResponse(success); + } + + uint32_t LgiDisplaySettings::getAudioOutputEncoding(const JsonObject& parameters, JsonObject& response) + { + // servicemanager params: (QString audioPort); + LOGINFOMETHOD(); + bool success = false; + + const string audioPort = parameters.HasLabel("audioPort") ? parameters["audioPort"].String() : "HDMI0"; + + try + { + device::AudioOutputPort &aPort = device::AudioOutputPortConfig::getInstance().getPort(audioPort); + const device::AudioEncoding &aEnc = aPort.getEncoding(); + response["encoding"] = aEnc.getName(); + success = true; + } + catch(const device::Exception& err) + { + LOG_DEVICE_EXCEPTION0(); + } + + returnResponse(success); + } + + uint32_t LgiDisplaySettings::getFollowColorSpace(const JsonObject& parameters, JsonObject& response) + { + // servicemanager params: (QString videoDisplay) const; + LOGINFOMETHOD(); + bool success = false; + const string videoDisplay = parameters.HasLabel("videoDisplay") ? parameters["videoDisplay"].String() : "HDMI0"; + + try + { + device::VideoOutputPort &vPort = device::Host::getInstance().getVideoOutputPort(videoDisplay); + response["followColorSpace"] = vPort.getFollowColorSpace(); + success = true; + } + catch(const device::Exception& err) + { + LOG_DEVICE_EXCEPTION0(); + } + + returnResponse(success); + } + + uint32_t LgiDisplaySettings::setFollowColorSpace(const JsonObject& parameters, JsonObject& response) + { + // servicemanager params: (QString videoDisplay, bool followCOlorSpace); + LOGINFOMETHOD(); + bool success = false; + const string videoDisplay = parameters.HasLabel("videoDisplay") ? parameters["videoDisplay"].String() : "HDMI0"; + returnIfParamNotFound(parameters, "followColorSpace"); + + const bool followColorSpace = parseQBool(parameters["followColorSpace"].String()); + + try + { + device::VideoOutputPort &vPort = device::Host::getInstance().getVideoOutputPort(videoDisplay); + vPort.setFollowColorSpace(followColorSpace); + success = true; + } + catch(const device::Exception& err) + { + LOG_DEVICE_EXCEPTION0(); + } + + returnResponse(success); + } + + uint32_t LgiDisplaySettings::getPreferredOutputColorSpace(const JsonObject& parameters, JsonObject& response) + { + // servicemanager params: (const QString videoDisplay); + LOGINFOMETHOD(); + bool success = false; + const string videoDisplay = parameters.HasLabel("videoDisplay") ? parameters["videoDisplay"].String() : "HDMI0"; + + try + { + std::string result; + device::VideoOutputPort &vPort = device::Host::getInstance().getVideoOutputPort(videoDisplay); + if (vPort.getPreferredOutputColorSpace(result)) + { + // the result is comma-separated list of values, like 'BT2020_NCL,BT2020_CL,BT709' + string colorSpace; + stringstream ss {result}; + vector colorSpaces; + while (getline(ss, colorSpace, ',')) { + colorSpaces.push_back(colorSpace); + } + setResponseArray(response, "preferredOutputColorSpaces", colorSpaces); + success = true; + } + } + catch (const device::Exception& err) + { + LOG_DEVICE_EXCEPTION0(); + } + + returnResponse(success); + } + + uint32_t LgiDisplaySettings::setPreferredOutputColorSpace(const JsonObject& parameters, JsonObject& response) + { + // servicemanager params: (const QString videoDisplay, const QString colorSpaces); + // here, we expect colorSpaces to be array + LOGINFOMETHOD(); + bool success = false; + const string videoDisplay = parameters.HasLabel("videoDisplay") ? parameters["videoDisplay"].String() : "HDMI0"; + + returnIfParamNotFound(parameters, "colorSpaces"); + auto colorSpacesArr = parameters["colorSpaces"].Array(); + auto colorSpaces = colorSpacesArr.Elements(); + + try + { + stringstream strColorSpaces; + bool first = true; + while (colorSpaces.Next()) + { + if (!first) strColorSpaces << ","; + first = false; + strColorSpaces << colorSpaces.Current().String(); + } + device::VideoOutputPort &vPort = device::Host::getInstance().getVideoOutputPort(videoDisplay); + success = vPort.setPreferredOutputColorSpace(strColorSpaces.str()); + } + catch (const device::Exception& err) + { + LOG_DEVICE_EXCEPTION0(); + } + + returnResponse(success); + } + + uint32_t LgiDisplaySettings::getHDRGfxColorSpace(const JsonObject& parameters, JsonObject& response) + { + // servicemanager params: (QString videoPort, int &y, int &cr, int &cb); + LOGINFOMETHOD(); + bool success = false; + const string videoDisplay = parameters.HasLabel("videoDisplay") ? parameters["videoDisplay"].String() : "HDMI0"; + + try { + int16_t y, cr, cb; + device::VideoOutputPort vPort = device::Host::getInstance().getVideoOutputPort(videoDisplay); + + vPort.getType().getInstance(device::VideoOutputPortType::kHDMI).GetHDRGfxColorSpace(&y, &cr, &cb); + + // without some cast, JSON module will end up serializing weird incorrect stuff + // there is probably some Thunder bug regarding handling int16_t in JSON.h + response["y"] = int32_t(y); + response["cr"] = int32_t(cr); + response["cb"] = int32_t(cb); + + success = true; + } + catch(const device::Exception& err) + { + LOG_DEVICE_EXCEPTION0(); + } + + returnResponse(success); + } + + uint32_t LgiDisplaySettings::setHDRGfxColorSpace(const JsonObject& parameters, JsonObject& response) + { + // servicemanager params: (QString videoPort, int y, int cr, int cb); + LOGINFOMETHOD(); + bool success = false; + const string videoDisplay = parameters.HasLabel("videoDisplay") ? parameters["videoDisplay"].String() : "HDMI0"; + + returnIfParamNotFound(parameters, "y"); + returnIfParamNotFound(parameters, "cr"); + returnIfParamNotFound(parameters, "cb"); + + int64_t y, cr, cb; + getDefaultNumberParameter("y", y, std::numeric_limits::max()); + getDefaultNumberParameter("cr", cr, std::numeric_limits::max()); + getDefaultNumberParameter("cb", cb, std::numeric_limits::max()); + + if (y < std::numeric_limits::min() || y > std::numeric_limits::max() + || cr < std::numeric_limits::min() || cr > std::numeric_limits::max() + || cb < std::numeric_limits::min() || cb > std::numeric_limits::max()) + { + LOGWARN("setHDRGfxColorSpace: some of values: y=%lld cr=%lld cb=%lld out of int16_t bounds; not executed", y,cr,cb); + } + else + { + int16_t _y = y, _cr = cr, _cb = cb; + try + { + device::VideoOutputPort vPort = device::Host::getInstance().getVideoOutputPort(videoDisplay); + vPort.getType().getInstance(device::VideoOutputPortType::kHDMI).SetHDRGfxColorSpace(&_y, &_cr, &_cb); + success = true; + } + catch(const device::Exception& err) + { + LOG_DEVICE_EXCEPTION0(); + } + } + + returnResponse(success); + } + + uint32_t LgiDisplaySettings::getBassEnhancer(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + bool success = true; + string audioPort = parameters.HasLabel("audioPort") ? parameters["audioPort"].String() : "HDMI0"; + dsSurroundVirtualizer_t virtualizer; + try + { + device::AudioOutputPort aPort = device::Host::getInstance().getAudioOutputPort(audioPort); + if (aPort.isConnected()) + { + virtualizer = aPort.getSurroundVirtualizer(); + response["enable"] = virtualizer.mode ? true : false ; + response["boost"] = virtualizer.boost; + + } + else + { + LOGERR("aport is not connected!"); + success = false; + } + } + catch (const device::Exception& err) + { + LOG_DEVICE_EXCEPTION1(audioPort); + success = false; + response["enable"] = false; + } + returnResponse(success); + } + + } // namespace Plugin +} // namespace WPEFramework diff --git a/LgiDisplaySettings/LgiDisplaySettings.h b/LgiDisplaySettings/LgiDisplaySettings.h new file mode 100644 index 0000000000..5824297a65 --- /dev/null +++ b/LgiDisplaySettings/LgiDisplaySettings.h @@ -0,0 +1,59 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* Copyright 2021 Liberty Global Service B.V. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "DisplaySettings.h" +#include "UtilsJsonRpc.h" + +namespace WPEFramework { + namespace Plugin { + // all LGI extensions are implemented here + class LgiDisplaySettings : public DisplaySettings + { + public: + LgiDisplaySettings(); + virtual ~LgiDisplaySettings() + { + } + + uint32_t setOutputFrameRatePreference(const JsonObject& parameters, JsonObject& response); // args: (videoDisplay, followContent) + uint32_t setAudioProcessingHint(const JsonObject& parameters, JsonObject& response); // args: (audioPort, audioMode, audioDelayMs) + uint32_t getAudioOutputEncoding(const JsonObject& parameters, JsonObject& response); //args: (audioPort) + uint32_t getFollowColorSpace(const JsonObject& parameters, JsonObject& response); // args: (videoDisplay) + uint32_t setFollowColorSpace(const JsonObject& parameters, JsonObject& response); // args: (videoDisplay,followColorSpace) + uint32_t getPreferredOutputColorSpace(const JsonObject& parameters, JsonObject& response); // args: (videoDisplay) + uint32_t setPreferredOutputColorSpace(const JsonObject& parameters, JsonObject& response); // args: (videoDisplay,colorSpaces: array of colorspace string) + uint32_t getHDRGfxColorSpace(const JsonObject& parameters, JsonObject& response); // args: (videoDisplay) + uint32_t setHDRGfxColorSpace(const JsonObject& parameters, JsonObject& response); // args: (videoDisplay,y,cr,cb) + + protected: + virtual bool getDefaultActiveInput() const + { + return false; + } + + virtual bool isFollowSoundModeSupported() const + { + return true; + } + + virtual uint32_t getBassEnhancer(const JsonObject& parameters, JsonObject& response); + }; + } //Plugin +} //WPEFramework diff --git a/LgiDisplaySettings/LgiDisplaySettings.json b/LgiDisplaySettings/LgiDisplaySettings.json new file mode 100644 index 0000000000..73e812088f --- /dev/null +++ b/LgiDisplaySettings/LgiDisplaySettings.json @@ -0,0 +1,991 @@ +{ + "$schema": "../../Thunder/Tools/JsonGenerator/schemas/interface.schema.json", + "jsonrpc": "2.0", + "info": { + "title": "LgiDisplaySettings API", + "class": "com.lgi.rdk.DisplaySettings", + "description": "The `DisplaySetting` plugin provides an interface for display information such as current video resolution, supported video displays, zoom setting, sound mode, and much more." + }, + "definitions": { + "audioDelay": { + "summary": "Delay (in ms) on the selected audio port", + "type": "string", + "example": "0" + }, + "audioDelayOffset": { + "summary": "Delay offset (in ms) on the selected audio port", + "type": "string", + "example": "0" + }, + "audioPort0": { + "summary": "Audio port name. An error returns if no port is specified.", + "type": "string", + "example": "HDMI0" + }, + "connectedVideoDisplays": { + "summary": "A string [] of connected video display port names", + "type": "array", + "items": { + "type":"string", + "example": "HDMI0" + } + }, + "EDID": { + "summary": "A base64 encoded byte array string representing the EDID", + "type": "string", + "example": "AP///////wAQrMLQVEJTMQUdAQOANR546q11qVRNnSYPUFSlSwCBALMA0QBxT6lAgYDRwAEBVl4AoKCgKVAwIDUADighAAAaAAAA/wBNWTNORDkxVjFTQlQKAAAA/ABERUxMIFAyNDE4RAogAAAA/QAxVh1xHAAKICAgICAgARsCAxuxUJAFBAMCBxYBBhESFRMUHyBlAwwAEAACOoAYcTgtQFgsRQAOKCEAAB4BHYAYcRwWIFgsJQAOKCEAAJ6/FgCggDgTQDAgOgAOKCEAABp+OQCggDgfQDAgOgAOKCEAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2A" + }, + "resolution": { + "summary": "Video display resolution", + "type": "string", + "example": "1080p" + }, + "soundMode": { + "summary": "Sound mode. Possible values: `AUTO (Dolby Digital Plus)`, `AUTO (Dolby Digital 5.1)`, `AUTO (Stereo)`, `MONO`, `STEREO`, `SURROUND`, PASSTHRU.", + "type":"string", + "example":"STEREO" + }, + "standards": { + "summary": "A string [] of HDR capabilities. Possible values: `none`, `HDR10`, `Dolby Vision`, `Technicolor Prime`", + "type": "array", + "items": { + "type": "string", + "example": "none" + } + }, + "supportsHDR": { + "summary": "Indicates support for HDR", + "type":"boolean", + "example": true + }, + "videoDisplay": { + "summary": "Video display port name. The default port is `HDMI0` if no port is specified", + "default": "HDMI0", + "type": "string", + "example": "HDMI0" + }, + "videoDisplayType": { + "summary": "Type of video display (port)", + "type": "string", + "example": "HDMI0" + }, + "zoomSetting": { + "summary": "Zoom setting. Possible values: `FULL`, `NONE,` `Letterbox 16x9`, `Letterbox 14x9`, `CCO`, `PanScan`, `Letterbox 2.21 on 4x3`, `Letterbox 2.21 on 16x9`, `Platform`, `Zoom 16x9`, `Pillarbox 4x3`, `Widescreen 4x3`", + "type": "string", + "example": "FULL" + }, + "result": { + "type":"object", + "properties": { + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "success" + ] + }, + "success": { + "summary": "Whether the request succeeded", + "type": "boolean", + "example": "true" + } + }, + "methods": { + "getActiveInput":{ + "summary": "Returns `true` if the STB HDMI output is currently connected to the active input of the sink device (determined by `RxSense`). If the STB does not support `RxSense`, this API always returns `true`. Specifically: \n`true` \n* STB is connected to the TV's active Input, or \n* Unable to determine if STB is connected to the TV's active input or not (because STB does not support `RxSense`) \n \n`false` \n* STB is not connected to the TV's active input, or \n* TV is OFF", + "params": { + "type":"object", + "properties": { + "videoDisplay": { + "$ref": "#/definitions/videoDisplay" + } + }, + "required": [ + "videoDisplay" + ] + }, + "result": { + "type":"object", + "properties": { + "activeInput": { + "summary": "Determines whether selected input is active or not", + "type": "boolean", + "example": true + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "activeInput", + "success" + ] + } + }, + "getAudioDelay":{ + "summary": "Returns the audio delay (in ms) on the selected audio port. If the `audioPort` argument is not specified, it will browse all ports (checking HDMI0 first). If there is no display connected, then it defaults to `HDMI0`.", + "params": { + "type":"object", + "properties": { + "audioPort": { + "$ref": "#/definitions/audioPort0" + } + }, + "required": [ + "audioPort" + ] + }, + "result": { + "type":"object", + "properties": { + "audioDelay": { + "$ref": "#/definitions/audioDelay" + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "audioDelay", + "success" + ] + } + }, + "getAudioDelayOffset":{ + "summary": "Returns the audio delay offset (in ms) on the selected audio port. If the `audioPort` argument is not specified, it will browse all ports (checking HDMI0 first). If there is no display connected, then it defaults to `HDMI0`.", + "params": { + "type":"object", + "properties": { + "audioPort": { + "$ref": "#/definitions/audioPort0" + } + }, + "required": [ + "audioPort" + ] + }, + "result": { + "type":"object", + "properties": { + "audioDelayOffset": { + "$ref": "#/definitions/audioDelayOffset" + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "audioDelayOffset", + "success" + ] + } + }, + "getConnectedAudioPorts":{ + "summary": "Returns connected audio output ports (a subset of the ports supported on the device). SPDIF port is always considered connected. HDMI port may or may not be connected.", + "result": { + "type":"object", + "properties": { + "connectedAudioPorts": { + "summary": "A string [] of connected audio port names", + "type": "array", + "items": { + "type": "string", + "example": "HDMI0" + } + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "connectedAudioPorts", + "success" + ] + } + }, + "getConnectedVideoDisplays":{ + "summary": "Returns connected video displays", + "result": { + "type": "object", + "properties": { + "connectedVideoDisplays": { + "$ref": "#/definitions/connectedVideoDisplays" + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "connectedVideoDisplays", + "success" + ] + } + }, + "getCurrentOutputSettings":{ + "summary": "Returns current output settings", + "result": { + "type":"object", + "properties": { + "colorSpace": { + "summary": "The color space. Possible values: `0` (dsDISPLAY_COLORSPACE_UNKNOWN), `1` (sDISPLAY_COLORSPACE_RGB, `2` (dsDISPLAY_COLORSPACE_YCbCr422), `3` (dsDISPLAY_COLORSPACE_YCbCr444), `4` (dsDISPLAY_COLORSPACE_YCbCr420), `5` (dsDISPLAY_COLORSPACE_AUTO).", + "type": "integer", + "example": 5 + }, + "colorDepth": { + "summary": "The color depth. The value that is returned from `dsGetCurrentOutputSettings`", + "type": "integer", + "example": 0 + }, + "matrixCoefficients": { + "summary": "matrix coefficients. Possible values: `0` (dsDISPLAY_MATRIXCOEFFICIENT_UNKNOWN), `1` (dsDISPLAY_MATRIXCOEFFICIENT_BT_709), `2` (dsDISPLAY_MATRIXCOEFFICIENT_BT_470_2_BG), `3` (dsDISPLAY_MATRIXCOEFFICIENT_SMPTE_170M), `4` (dsDISPLAY_MATRIXCOEFFICIENT_XvYCC_709), `5` (dsDISPLAY_MATRIXCOEFFICIENT_eXvYCC_601), `6` (dsDISPLAY_MATRIXCOEFFICIENT_BT_2020_NCL), `7` (dsDISPLAY_MATRIXCOEFFICIENT_BT_2020_CL)", + "type": "integer", + "example": 0 + }, + "videoEOTF": { + "summary": "HDR standard. Possible values: `0x0` (dsHDRSTANDARD_NONE), `0x01` (dsHDRSTANDARD_HDR10), `0x02` (dsHDRSTANDARD_HLG), `0x04` (dsHDRSTANDARD_DolbyVision), `0x08` (dsHDRSTANDARD_TechnicolorPrime), `0x80` (dsHDRSTANDARD_Invalid)", + "type": "integer", + "example": 0 + }, + "quantizationRange": { + "summary": "The supported quantization range", + "type": "integer", + "example": 235 + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "colorSpace", + "colorDepth", + "matrixCoefficients", + "videoEOTF", + "success" + ] + } + }, + "getCurrentResolution":{ + "summary": "Returns the current resolution on the selected video display port", + "params": { + "type":"object", + "properties": { + "videoDisplay": { + "$ref": "#/definitions/videoDisplay" + } + }, + "required":[ + "videoDisplay" + ] + }, + "result": { + "type":"object", + "properties": { + "resolution": { + "$ref": "#/definitions/resolution" + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "resolution", + "success" + ] + } + }, + "getSettopHDRSupport":{ + "summary": "Returns an HDR support object (list of standards that the STB supports)", + "result": { + "type":"object", + "properties": { + "standards": { + "$ref": "#/definitions/standards" + }, + "supportsHDR": { + "$ref": "#/definitions/supportsHDR" + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "standards", + "supportsHDR", + "success" + ] + } + }, + "getSoundMode":{ + "summary": "Returns the sound mode for the incoming video display. If the argument is `Null` or empty (although not recommended), this returns the output mode of all connected ports, whichever is connected, while giving priority to the HDMI port. If the video display is not connected, then it returns `Stereo` as a safe default", + "params": { + "type": "object", + "properties": { + "audioPort": { + "$ref": "#/definitions/audioPort0" + } + }, + "required": [ + "audioPort" + ] + }, + "result": { + "type":"object", + "properties": { + "soundMode": { + "$ref": "#/definitions/soundMode" + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "soundMode", + "success" + ] + } + }, + "getSupportedAudioPorts": { + "summary": "Returns all audio ports supported on the device (all ports that are physically present)", + "result": { + "type":"object", + "properties": { + "supportedAudioPorts": { + "summary": "A string [] of supported audio ports", + "type": "array", + "items": { + "type": "string", + "example": "HDMI0" + } + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "supportedAudioPorts", + "success" + ] + } + }, + "getSupportedResolutions":{ + "summary": "Returns supported resolutions on the selected video display port (both TV and STB) by its name.", + "params": { + "type": "object", + "properties": { + "videoDisplay":{ + "$ref": "#/definitions/videoDisplay" + } + }, + "required": [ + "videoDisplay" + ] + }, + "result": { + "type":"object", + "properties": { + "supportedResolutions":{ + "summary": "A string array of supported resolutions on the selected video display port", + "type": "array", + "items": { + "type": "string", + "example": "1080p60" + } + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "supportedResolutions", + "success" + ] + } + }, + "getSupportedVideoDisplays":{ + "summary": "Returns all video ports supported on the device (all ports that are physically present)", + "result": { + "type":"object", + "properties": { + "supportedVideoDisplays": { + "summary": "a string [] of supported video display ports", + "type":"array", + "items": { + "type": "string", + "example": "HDMI0" + } + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "supportedVideoDisplays", + "success" + ] + } + }, + "getZoomSetting": { + "summary": "Returns the zoom setting value", + "result": { + "type":"object", + "properties": { + "zoomSetting": { + "$ref": "#/definitions/zoomSetting" + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "zoomSetting", + "success" + ] + } + }, + "readEDID":{ + "summary": "Reads the EDID from the connected HDMI (output) device. Returns a key of `EDID` with a value of the base64 encoded byte array string representing the EDID.", + "result": { + "type":"object", + "properties": { + "EDID": { + "$ref": "#/definitions/EDID" + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "EDID", + "success" + ] + } + }, + "setAudioDelay":{ + "summary": "Sets the audio delay (in ms) on the selected audio port. If the `audioPort` argument is not specified, it will browse all ports (checking HDMI0 first). If there is no display connected, then it defaults to `HDMI0`.", + "params": { + "type":"object", + "properties": { + "audioPort": { + "$ref": "#/definitions/audioPort0" + }, + "audioDelay": { + "$ref": "#/definitions/audioDelay" + } + }, + "required": [ + "audioPort", + "audioDelay" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "setAudioDelayOffset":{ + "summary": "Sets the audio delay offset (in ms) on the selected audio port. If the `audioPort` argument is not specified, it will browse all ports (checking HDMI0 first). If there is no display connected, then it defaults to `HDMI0`.", + "params": { + "type":"object", + "properties": { + "audioPort": { + "$ref": "#/definitions/audioPort0" + }, + "audioDelayOffset": { + "$ref": "#/definitions/audioDelayOffset" + } + }, + "required": [ + "audioPort", + "audioDelayOffset" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "setCurrentResolution":{ + "summary": "Sets the current resolution", + "params": { + "type": "object", + "properties": { + "videoDisplay": { + "$ref": "#/definitions/videoDisplay" + }, + "resolution": { + "$ref": "#/definitions/resolution" + } + }, + "required": [ + "videoDisplay", + "resolution" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "setScartParameter":{ + "summary": "Sets SCART parameters. \n \nPossible values: \n| **Parameter** | **ParameterData** | \n| `aspect_ratio` | `4x3` or `16x9` | \n| `tv_startup` | `on` or `off` | \n| `rgb` | `on` (disables cvbs) | \n| `cvbs` | `on` (disables rgb) | \n| `macrovision` | not implemented | \n| `cgms` | `disabled`, `copyNever`, `copyOnce`, `copyFreely`, or `copyNoMore` | \n| `port` | `on` or `off` |", + "params": { + "type":"object", + "properties": { + "scartParameter": { + "summary": "SCART parameter name", + "type": "string", + "example": "aspect_ratio" + }, + "scartParameterData": { + "summary": "SCART parameter data", + "type": "string", + "example": "4x3" + } + }, + "required": [ + "scartParameter", + "scartParameterDelay" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "setSoundMode":{ + "summary": "Sets the current sound mode for the corresponding video display. If the `audioPort` argument value is missing or empty all ports are set.", + "params": { + "type": "object", + "properties": { + "audioPort": { + "$ref": "#/definitions/audioPort0" + }, + "soundMode": { + "$ref": "#/definitions/soundMode" + }, + "persist": { + "summary": "persists the sound mode", + "type": "boolean", + "example": true + } + }, + "required": [ + "audioPort", + "soundMode", + "persist" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "setZoomSetting": { + "summary": "Sets the current zoom value", + "params": { + "type":"object", + "properties":{ + "zoomSetting": { + "$ref": "#/definitions/zoomSetting" + } + }, + "required":[ + "zoomSetting" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "setOutputFrameRatePreference": { + "summary": "Sets the current output frame rate follow content preference", + "params": { + "type":"object", + "properties":{ + "videoDisplay": { + "$ref": "#/definitions/videoDisplay" + }, + "followContent": { + "summary": "should follow content frame rate", + "type": "boolean", + "example": true + } + }, + "required":[ + "followContent" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "setAudioProcessingHint": { + "summary": "Sets audio processing hint", + "params": { + "type":"object", + "properties":{ + "audioPort": { + "summary": "Audio port ID", + "type": "string", + "example": "HDMI0" + }, + "audioMode": { + "summary": "Audio mode - dd/pcm", + "type": "string", + "example": "pcm" + }, + "audioDelayMs": { + "summary": "audio delay [ms]", + "type": "integer", + "example": 50 + } + }, + "required":[ + "audioMode" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "getAudioOutputEncoding": { + "summary": "gets current audio output encoding", + "params": { + "type":"object", + "properties":{ + "audioPort": { + "summary": "Audio port ID", + "type": "string", + "example": "HDMI0" + } + }, + "required":[ + ] + }, + "result": { + "type":"object", + "properties": { + "encoding": { + "summary": "audio encoding type", + "type": "string", + "example": "PCM" + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "encoding", + "success" + ] + } + }, + "getFollowColorSpace": { + "summary": "get current follow colorspace setting", + "params": { + "type":"object", + "properties":{ + "videoDisplay": { + "$ref": "#/definitions/videoDisplay" + } + }, + "required":[ + ] + }, + "result": { + "type":"object", + "properties": { + "followColorSpace": { + "summary": "should follow colorspace", + "type": "boolean", + "example": true + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "followColorSpace", + "success" + ] + } + }, + "setFollowColorSpace": { + "summary": "set current follow colorspace setting", + "params": { + "type":"object", + "properties":{ + "videoDisplay": { + "$ref": "#/definitions/videoDisplay" + }, + "followColorSpace": { + "summary": "should follow color space", + "type": "boolean", + "example": true + } + }, + "required":[ + "followColorSpace" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "getPreferredOutputColorSpace": { + "summary": "get current preferred output colorspace", + "params": { + "type":"object", + "properties":{ + "videoDisplay": { + "$ref": "#/definitions/videoDisplay" + } + }, + "required":[ + ] + }, + "result": { + "type":"object", + "properties": { + "preferredOutputColorSpaces": { + "summary": "A string [] of preferred colorspaces", + "type": "array", + "items": { + "type":"string", + "example": "BT2020_NCL" + } + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "preferredOutputColorSpaces", + "success" + ] + } + }, + "setPreferredOutputColorSpace": { + "summary": "set current preferred output colorspace", + "params": { + "type":"object", + "properties": { + "videoDisplay": { + "$ref": "#/definitions/videoDisplay" + }, + "colorSpaces": { + "summary": "A string [] of preferred colorspaces", + "type": "array", + "items": { + "type":"string", + "example": "BT2020_NCL" + } + } + }, + "required": [ + "colorSpaces" + ] + }, + "result": { + "type":"object", + "properties": { + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "success" + ] + } + }, + "getHDRGfxColorSpace": { + "summary": "Gets current HDR gfx colorspace values", + "params": { + "type":"object", + "properties":{ + "videoDisplay": { + "$ref": "#/definitions/videoDisplay" + } + }, + "required":[ + ] + }, + "result": { + "type":"object", + "properties": { + "y": { + "summary": "y value", + "type": "integer", + "example": 0 + }, + "cr": { + "summary": "cr value", + "type": "integer", + "example": 0 + }, + "cb": { + "summary": "cb value", + "type": "integer", + "example": 0 + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "y", + "cr", + "cb", + "success" + ] + } + }, + "setHDRGfxColorSpace": { + "summary": "Sets current HDR gfx colorspace values", + "params": { + "type":"object", + "properties":{ + "videoDisplay": { + "$ref": "#/definitions/videoDisplay" + }, + "y": { + "summary": "y value", + "type": "integer", + "example": 0 + }, + "cr": { + "summary": "cr value", + "type": "integer", + "example": 0 + }, + "cb": { + "summary": "cb value", + "type": "integer", + "example": 0 + } + }, + "required":[ + "y", + "cr", + "cb" + ] + }, + "result": { + "type":"object", + "properties": { + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "success" + ] + } + } + }, + "events": { + "activeInputChanged":{ + "summary": "Triggered on active input change (RxSense)", + "params": { + "type":"object", + "properties": { + "activeInput": { + "summary": "`true` = RXSENSE_ON, `false` = RXSENSE_OFF", + "type": "boolean", + "example": true + } + }, + "required": [ + "activeInput" + ] + } + }, + "connectedAudioPortUpdated":{ + "summary": "Triggered when the connected audio port is updated", + "params": { + "type":"object", + "properties": { + "HotpluggedAudioPort": { + "summary": "Audio port ID for which the connection status has changed. Possible audio port IDs are (`HDMI_ARC0`, `HEADPHONE0`, etc).", + "type": "string", + "example": "HDMI_ARC0" + }, + "isConnected": { + "summary": "Current connection status of the audio port", + "enum": [ + "connected", + "disconnected" + ], + "type": "string", + "example": "connected" + } + }, + "required": [ + "HotPuggedAudioPort", + "isConnected" + ] + } + }, + "connectedVideoDisplaysUpdated":{ + "summary": "Triggered when the connected video display is updated and returns the connected video displays.", + "params": { + "type":"object", + "properties": { + "connectedVideoDisplays": { + "$ref": "#/definitions/connectedVideoDisplays" + } + }, + "required": [ + "connectedVideoDisplays" + ] + } + }, + "resolutionChanged":{ + "summary": "Triggered when the resolution is changed by the user and returns the current resolution", + "params": { + "type":"object", + "properties": { + "width": { + "summary": "Width of the video display", + "type": "integer", + "example": 1920 + }, + "height": { + "summary": "Height of the video display", + "type": "integer", + "example": 1080 + }, + "videoDisplayType": { + "$ref": "#/definitions/videoDisplayType" + }, + "resolution": { + "$ref": "#/definitions/resolution" + } + }, + "required": [ + "width", + "height", + "videoDisplayType", + "resolution" + ] + } + }, + "resolutionPreChange": { + "summary": "Triggered on resolution pre-change" + }, + "zoomSettingUpdated":{ + "summary": "Triggered when the zoom setting changes and returns the zoom setting values for all video display types", + "params": { + "type":"object", + "properties": { + "zoomSetting": { + "$ref": "#/definitions/zoomSetting" + }, + "videoDisplayType": { + "$ref": "#/definitions/videoDisplayType" + } + }, + "required": [ + "zoomSetting", + "videoDisplayType" + ] + } + } + } +} diff --git a/LgiDisplaySettings/LgiDisplaySettingsPlugin.json b/LgiDisplaySettings/LgiDisplaySettingsPlugin.json new file mode 100644 index 0000000000..a7a705a854 --- /dev/null +++ b/LgiDisplaySettings/LgiDisplaySettingsPlugin.json @@ -0,0 +1,14 @@ +{ + "$schema": "../../Thunder/Tools/JsonGenerator/schemas/plugin.schema.json", + "info": { + "title": "LgiDisplaySettings Plugin", + "callsign": "com.lgi.rdk.DisplaySettings", + "locator": "libWPEFrameworkLgiDisplaySettings.so", + "status": "production", + "description": "The `LgiDisplaySettings` plugin provides an interface for display information such as current video resolution, supported video displays, zoom setting, sound mode, and much more.", + "version": "2.0" + }, + "interface": { + "$ref": "LgiDisplaySettings.json#" + } +} diff --git a/LgiDisplaySettings/Module.cpp b/LgiDisplaySettings/Module.cpp new file mode 100644 index 0000000000..ce759b615f --- /dev/null +++ b/LgiDisplaySettings/Module.cpp @@ -0,0 +1,22 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/LgiDisplaySettings/Module.h b/LgiDisplaySettings/Module.h new file mode 100644 index 0000000000..502203ea33 --- /dev/null +++ b/LgiDisplaySettings/Module.h @@ -0,0 +1,30 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* Copyright 2021 Liberty Global Service B.V. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once +#ifndef MODULE_NAME +#define MODULE_NAME LgiDisplaySettings +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/LgiDisplaySettings/README.md b/LgiDisplaySettings/README.md new file mode 100644 index 0000000000..fc278f5430 --- /dev/null +++ b/LgiDisplaySettings/README.md @@ -0,0 +1,19 @@ +----------------- +Build: + +bitbake rdkservice-lgidisplaysettings + +----------------- +Test: + +curl -d '{"jsonrpc":"2.0","id":"3","method": "com.lgi.rdk.DisplaySettings.1.getSupportedResolutions"}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "com.lgi.rdk.DisplaySettings.1.getSupportedTvResolutions"}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "com.lgi.rdk.DisplaySettings.1.getConnectedAudioPorts"}' http://127.0.0.1:9998/jsonrpc; + +curl -d '{"jsonrpc":"2.0","id":"3","method": "com.lgi.rdk.DisplaySettings.1.getSupportedAudioPorts"}' http://127.0.0.1:9998/jsonrpc; + +curl -d '{"jsonrpc":"2.0","id":"3","method": "com.lgi.rdk.DisplaySettings.1.getSupportedAudioModes", "params":{"audioPort":"HDMI0"}}' http://127.0.0.1:9998/jsonrpc; + +curl -d '{"jsonrpc":"2.0","id":"3","method": "com.lgi.rdk.DisplaySettings.1.getSoundMode", "params":{"videoDisplay":"HDMI0"}}' http://127.0.0.1:9998/jsonrpc; diff --git a/LgiDisplaySettings/cmake/FindDS.cmake b/LgiDisplaySettings/cmake/FindDS.cmake new file mode 100644 index 0000000000..67bfa6f0c6 --- /dev/null +++ b/LgiDisplaySettings/cmake/FindDS.cmake @@ -0,0 +1,49 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# - Try to find Display Settings library +# Once done this will define +# DS_FOUND - System has DS +# DS_INCLUDE_DIRS - The DS include directories +# DS_LIBRARIES - The libraries needed to use DS +# DS_FLAGS - The flags needed to use DS +# + +find_package(PkgConfig) + +find_library(DS_LIBRARIES NAMES ds) +find_library(DSHAL_LIBRARIES NAMES dshalcli) +find_path(DS_INCLUDE_DIRS NAMES manager.hpp PATH_SUFFIXES rdk/ds) +find_path(DSHAL_INCLUDE_DIRS NAMES dsTypes.h PATH_SUFFIXES rdk/ds-hal) +find_path(DSRPC_INCLUDE_DIRS NAMES dsMgr.h PATH_SUFFIXES rdk/ds-rpc) + +set(DS_LIBRARIES ${DS_LIBRARIES} ${DSHAL_LIBRARIES}) +set(DS_LIBRARIES ${DS_LIBRARIES} CACHE PATH "Path to DS library") +set(DS_INCLUDE_DIRS ${DS_INCLUDE_DIRS} ${DSHAL_INCLUDE_DIRS} ${DSRPC_INCLUDE_DIRS}) +set(DS_INCLUDE_DIRS ${DS_INCLUDE_DIRS} CACHE PATH "Path to DS include") + + + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(DS DEFAULT_MSG DS_INCLUDE_DIRS DS_LIBRARIES) + +mark_as_advanced( + DS_FOUND + DS_INCLUDE_DIRS + DS_LIBRARIES + DS_LIBRARY_DIRS + DS_FLAGS) diff --git a/LgiDisplaySettings/cmake/FindIARMBus.cmake b/LgiDisplaySettings/cmake/FindIARMBus.cmake new file mode 100644 index 0000000000..9764ced5ea --- /dev/null +++ b/LgiDisplaySettings/cmake/FindIARMBus.cmake @@ -0,0 +1,45 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# - Try to find IARMBus +# Once done this will define +# IARMBUS_FOUND - System has IARMBus +# IARMBUS_INCLUDE_DIRS - The IARMBus include directories +# IARMBUS_LIBRARIES - The libraries needed to use IARMBus +# IARMBUS_FLAGS - The flags needed to use IARMBus +# + +find_package(PkgConfig) + +find_library(IARMBUS_LIBRARIES NAMES IARMBus) +find_path(IARMBUS_INCLUDE_DIRS NAMES libIARM.h PATH_SUFFIXES rdk/iarmbus) +find_path(IARMIR_INCLUDE_DIRS NAMES irMgr.h PATH_SUFFIXES rdk/iarmmgrs/ir) + +set(IARMBUS_LIBRARIES ${IARMBUS_LIBRARIES} CACHE PATH "Path to IARMBus library") +set(IARMBUS_INCLUDE_DIRS ${IARMBUS_INCLUDE_DIRS} ${IARMIR_INCLUDE_DIRS}) +set(IARMBUS_INCLUDE_DIRS ${IARMBUS_INCLUDE_DIRS} ${IARMIR_INCLUDE_DIRS} CACHE PATH "Path to IARMBus include") + + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(IARMBUS DEFAULT_MSG IARMBUS_INCLUDE_DIRS IARMBUS_LIBRARIES) + +mark_as_advanced( + IARMBUS_FOUND + IARMBUS_INCLUDE_DIRS + IARMBUS_LIBRARIES + IARMBUS_LIBRARY_DIRS + IARMBUS_FLAGS) diff --git a/LgiDisplaySettings/doc/LgiDisplaySettingsPlugin.md b/LgiDisplaySettings/doc/LgiDisplaySettingsPlugin.md new file mode 100644 index 0000000000..ed5f30185d --- /dev/null +++ b/LgiDisplaySettings/doc/LgiDisplaySettingsPlugin.md @@ -0,0 +1,4174 @@ + + +# LgiDisplaySettings API + +**Version: 1.0** + +**Status: :black_circle::white_circle::white_circle:** + +com.lgi.rdk.DisplaySettings plugin for Thunder framework. + +### Table of Contents + +- [Introduction](#head.Introduction) +- [Description](#head.Description) +- [Configuration](#head.Configuration) +- [Methods](#head.Methods) +- [Notifications](#head.Notifications) + + +# Introduction + + +## Scope + +This document describes purpose and functionality of the com.lgi.rdk.DisplaySettings plugin. It includes detailed specification about its configuration, methods provided and notifications sent. + + +## Case Sensitivity + +All identifiers of the interfaces described in this document are case-sensitive. Thus, unless stated otherwise, all keywords, entities, properties, relations and actions should be treated as such. + + +## Acronyms, Abbreviations and Terms + +The table below provides and overview of acronyms used in this document and their definitions. + +| Acronym | Description | +| :-------- | :-------- | +| API | Application Programming Interface | +| HTTP | Hypertext Transfer Protocol | +| JSON | JavaScript Object Notation; a data interchange format | +| JSON-RPC | A remote procedure call protocol encoded in JSON | + +The table below provides and overview of terms and abbreviations used in this document and their definitions. + +| Term | Description | +| :-------- | :-------- | +| callsign | The name given to an instance of a plugin. One plugin can be instantiated multiple times, but each instance the instance name, callsign, must be unique. | + + +## References + +| Ref ID | Description | +| :-------- | :-------- | +| [HTTP](http://www.w3.org/Protocols) | HTTP specification | +| [JSON-RPC](https://www.jsonrpc.org/specification) | JSON-RPC 2.0 specification | +| [JSON](http://www.json.org/) | JSON specification | +| [Thunder](https://github.com/WebPlatformForEmbedded/Thunder/blob/master/doc/WPE%20-%20API%20-%20WPEFramework.docx) | Thunder API Reference | + + +# Description + +The `DisplaySetting` plugin provides an interface for display information such as current video resolution, supported video displays, zoom setting, sound mode, and much more. + +The plugin is designed to be loaded and executed within the Thunder framework. For more information about the framework refer to [[Thunder](#ref.Thunder)]. + + +# Configuration + +The table below lists configuration options of the plugin. + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| classname | string | Class name: *com.lgi.rdk.DisplaySettings* | +| autostart | boolean | Determines if the plugin shall be started automatically along with the framework | + + +# Methods + +The following methods are provided by the com.lgi.rdk.DisplaySettings plugin: + +com.lgi.rdk.DisplaySettings interface methods: + +| Method | Description | +| :-------- | :-------- | +| [enableSurroundDecoder](#method.enableSurroundDecoder) | Enables or disables Surround Decoder capability | +| [getActiveInput](#method.getActiveInput) | Returns `true` if the STB HDMI output is currently connected to the active input of the sink device (determined by `RxSense`) | +| [getAudioDelay](#method.getAudioDelay) | Returns the audio delay (in ms) on the selected audio port | +| [getAudioDelayOffset](#method.getAudioDelayOffset) | Returns the audio delay offset (in ms) on the selected audio port | +| [getBassEnhancer](#method.getBassEnhancer) | Returns the current status of the Bass Enhancer settings | +| [getConnectedAudioPorts](#method.getConnectedAudioPorts) | Returns connected audio output ports (a subset of the ports supported on the device) | +| [getConnectedVideoDisplays](#method.getConnectedVideoDisplays) | Returns connected video displays | +| [getCurrentOutputSettings](#method.getCurrentOutputSettings) | Returns current output settings | +| [getCurrentResolution](#method.getCurrentResolution) | Returns the current resolution on the selected video display port | +| [getDefaultResolution](#method.getDefaultResolution) | Gets the default resolution supported by the HDMI0 video output port | +| [getDialogEnhancement](#method.getDialogEnhancement) | Returns the current Dialog Enhancer level (port HDMI0) | +| [getDolbyVolumeMode](#method.getDolbyVolumeMode) | Returns whether Dolby Volume mode is enabled or disabled (audio output port HDMI0) | +| [getDRCMode](#method.getDRCMode) | Returns the current Dynamic Range Control mode | +| [getEnableAudioPort](#method.getEnableAudioPort) | Returns the current status of the specified input audio port | +| [getGain](#method.getGain) | Returns the current gain value | +| [getGraphicEqualizerMode](#method.getGraphicEqualizerMode) | Returns the current Graphic Equalizer Mode setting (port HDMI0) | +| [getIntelligentEqualizerMode](#method.getIntelligentEqualizerMode) | Returns the current Intelligent Equalizer Mode setting (port HDMI0) | +| [getMISteering](#method.getMISteering) | Returns the current status of Media Intelligence Steering settings | +| [getMS12AudioCompression](#method.getMS12AudioCompression) | Returns the current audio compression settings | +| [getMS12AudioProfile](#method.getMS12AudioProfile) | Returns the current MS12 audio profile settings | +| [getMuted](#method.getMuted) | Returns whether audio is muted on a given port | +| [getSettopAudioCapabilities](#method.getSettopAudioCapabilities) | Returns the set-top audio capabilities for the specified audio port | +| [getSettopHDRSupport](#method.getSettopHDRSupport) | Returns an HDR support object (list of standards that the STB supports) | +| [getSettopMS12Capabilities](#method.getSettopMS12Capabilities) | Returns the set-top MS12 audio capabilities for the specified audio port | +| [getSinkAtmosCapability](#method.getSinkAtmosCapability) | Returns the ATMOS capability of the sink (HDMI0) | +| [getSoundMode](#method.getSoundMode) | Returns the sound mode for the incoming video display | +| [getSupportedAudioModes](#method.getSupportedAudioModes) | Returns a list of strings containing the supported audio modes | +| [getSupportedAudioPorts](#method.getSupportedAudioPorts) | Returns all audio ports supported on the device (all ports that are physically present) | +| [getSupportedMS12AudioProfiles](#method.getSupportedMS12AudioProfiles) | Returns list of platform supported MS12 audio profiles for the specified audio port | +| [getSupportedResolutions](#method.getSupportedResolutions) | Returns supported resolutions on the selected video display port (both TV and STB) by its name | +| [getSupportedSettopResolutions](#method.getSupportedSettopResolutions) | Returns supported STB resolutions | +| [getSupportedTvResolutions](#method.getSupportedTvResolutions) | Returns supported TV resolutions on the selected video display port | +| [getSupportedVideoDisplays](#method.getSupportedVideoDisplays) | Returns all video ports supported on the device (all ports that are physically present) | +| [getSurroundVirtualizer](#method.getSurroundVirtualizer) | Returns the current surround virtualizer boost settings | +| [getSurroundVirtualizer2](#method.getSurroundVirtualizer2) | (Version 2) Returns the current surround virtualizer boost settings | +| [getTVHDRCapabilities](#method.getTVHDRCapabilities) | Gets HDR capabilities supported by the TV | +| [getTvHDRSupport](#method.getTvHDRSupport) | Returns an HDR support object (list of standards that the TV supports) | +| [getVideoPortStatusInStandby](#method.getVideoPortStatusInStandby) | Returns video port status in standby mode (failure if the port name is missing) | +| [getVolumeLevel](#method.getVolumeLevel) | Returns the current volume level | +| [getVolumeLeveller](#method.getVolumeLeveller) | Returns the current Volume Leveller setting | +| [getVolumeLeveller2](#method.getVolumeLeveller2) | (Version 2) Returns the current Volume Leveller setting | +| [getZoomSetting](#method.getZoomSetting) | Returns the zoom setting value | +| [isConnectedDeviceRepeater](#method.isConnectedDeviceRepeater) | Indicates whether the device connected to the HDMI0 video output port is an HDCP repeater | +| [isSurroundDecoderEnabled](#method.isSurroundDecoderEnabled) | Returns the current status of Surround Decoder | +| [readEDID](#method.readEDID) | Reads the EDID from the connected HDMI (output) device | +| [readHostEDID](#method.readHostEDID) | Reads the EDID of the host (STB) | +| [setAudioAtmosOutputMode](#method.setAudioAtmosOutputMode) | Sets ATMOS audio output mode (on HDMI0) | +| [setAudioDelay](#method.setAudioDelay) | Sets the audio delay (in ms) on the selected audio port | +| [setAudioDelayOffset](#method.setAudioDelayOffset) | Sets the audio delay offset (in ms) on the selected audio port | +| [setBassEnhancer](#method.setBassEnhancer) | Sets the Bass Enhancer | +| [setCurrentResolution](#method.setCurrentResolution) | Sets the current resolution | +| [setDialogEnhancement](#method.setDialogEnhancement) | Sets the Dialog Enhancer level | +| [setDolbyVolumeMode](#method.setDolbyVolumeMode) | Enables or disables Dolby Volume mode on audio track (audio output port HDMI0) | +| [setDRCMode](#method.setDRCMode) | Sets the Dynamic Range Control (DRC) setting | +| [setEnableAudioPort](#method.setEnableAudioPort) | Enable or disable the specified audio port based on the input audio port ID | +| [setGain](#method.setGain) | Adjusts the gain on a specific port | +| [setGraphicEqualizerMode](#method.setGraphicEqualizerMode) | Sets the Graphic Equalizer Mode | +| [setIntelligentEqualizerMode](#method.setIntelligentEqualizerMode) | Sets the Intelligent Equalizer mode (port HDMI0) | +| [setMISteering](#method.setMISteering) | Enables or Disables Media Intelligent Steering | +| [setMS12AudioCompression](#method.setMS12AudioCompression) | Sets the audio dynamic range compression level (port HDMI0) | +| [setMS12AudioProfile](#method.setMS12AudioProfile) | Sets the selected MS12 audio profile | +| [setMuted](#method.setMuted) | Mutes or unmutes audio on a specific port | +| [setScartParameter](#method.setScartParameter) | Sets SCART parameters | +| [setSoundMode](#method.setSoundMode) | Sets the current sound mode for the corresponding video display | +| [setSurroundVirtualizer](#method.setSurroundVirtualizer) | Sets the Surround Virtualizer boost | +| [setSurroundVirtualizer2](#method.setSurroundVirtualizer2) | (Version 2) Sets the Surround Virtualizer boost | +| [setVideoPortStatusInStandby](#method.setVideoPortStatusInStandby) | Sets the specified video port status to be used in standby mode (failure if the port name is missing) | +| [setVolumeLevel](#method.setVolumeLevel) | Adjusts the Volume Level on a specific port | +| [setVolumeLeveller](#method.setVolumeLeveller) | Sets the Volume Leveller level | +| [setVolumeLeveller2](#method.setVolumeLeveller2) | (Version 2) Sets the Volume Leveller level | +| [setZoomSetting](#method.setZoomSetting) | Sets the current zoom value | +| [setOutputFrameRatePreference](#method.setOutputFrameRatePreference) | Sets the current output frame rate follow content preference | +| [setAudioProcessingHint](#method.setAudioProcessingHint) | Sets audio processing hint | +| [getAudioOutputEncoding](#method.getAudioOutputEncoding) | gets current audio output encoding | +| [getFollowColorSpace](#method.getFollowColorSpace) | get current follow colorspace setting | +| [setFollowColorSpace](#method.setFollowColorSpace) | set current follow colorspace setting | +| [getPreferredOutputColorSpace](#method.getPreferredOutputColorSpace) | get current preferred output colorspace | +| [setPreferredOutputColorSpace](#method.setPreferredOutputColorSpace) | set current preferred output colorspace | +| [getHDRGfxColorSpace](#method.getHDRGfxColorSpace) | Gets current HDR gfx colorspace values | +| [setHDRGfxColorSpace](#method.setHDRGfxColorSpace) | Sets current HDR gfx colorspace values | + + + +## *enableSurroundDecoder method* + +Enables or disables Surround Decoder capability. The Surround Decoder is an upmixer that takes stereo music content, or surround-encoded two-channel movie content, and creates a high-quality multichannel upmix. If the Surround Decoder is enabled, two-channel signals and 5.1-channel signals are upmixed to 5.1.2. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | +| params.surroundDecoderEnable | boolean | Whether Surround Decoder is is enabled (`true`) or disabled (`false`) | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.enableSurroundDecoder", + "params": { + "audioPort": "SPEAKER0", + "surroundDecoderEnable": true + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *getActiveInput method* + +Returns `true` if the STB HDMI output is currently connected to the active input of the sink device (determined by `RxSense`). If the STB does not support `RxSense`, this API always returns `true`. Specifically: +`true` +* STB is connected to the TV's active Input, or +* Unable to determine if STB is connected to the TV's active input or not (because STB does not support `RxSense`) + +`false` +* STB is not connected to the TV's active input, or +* TV is OFF. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.videoDisplay | string | Video display port name. The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.activeInput | boolean | Determines whether selected input is active or not | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getActiveInput", + "params": { + "videoDisplay": "HDMI0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "activeInput": true, + "success": true + } +} +``` + + +## *getAudioDelay method* + +Returns the audio delay (in ms) on the selected audio port. If the `audioPort` argument is not specified, it will browse all ports (checking HDMI0 first). If there is no display connected, then it defaults to `HDMI0`. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.audioPort | string | Audio port name. An error returns if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.audioDelay | string | Delay (in ms) on the selected audio port | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getAudioDelay", + "params": { + "audioPort": "HDMI0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "audioDelay": "0", + "success": true + } +} +``` + + +## *getAudioDelayOffset method* + +Returns the audio delay offset (in ms) on the selected audio port. If the `audioPort` argument is not specified, it will browse all ports (checking HDMI0 first). If there is no display connected, then it defaults to `HDMI0`. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.audioPort | string | Audio port name. An error returns if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.audioDelayOffset | string | Delay offset (in ms) on the selected audio port | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getAudioDelayOffset", + "params": { + "audioPort": "HDMI0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "audioDelayOffset": "0", + "success": true + } +} +``` + + +## *getBassEnhancer method* + +Returns the current status of the Bass Enhancer settings. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.enable | boolean | `true` if Bass Enhancer is enabled, otherwise `false` | +| result.bassBoost | integer | Value between 0 and 100, where 0 means no bass boost (disabled) and 100 means max bass boost | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getBassEnhancer", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "enable": true, + "bassBoost": 50, + "success": true + } +} +``` + + +## *getConnectedAudioPorts method* + +Returns connected audio output ports (a subset of the ports supported on the device). SPDIF port is always considered connected. HDMI port may or may not be connected. + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.connectedAudioPorts | array | A string [] of connected audio port names | +| result.connectedAudioPorts[#] | string | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getConnectedAudioPorts" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "connectedAudioPorts": [ + "HDMI0" + ], + "success": true + } +} +``` + + +## *getConnectedVideoDisplays method* + +Returns connected video displays. + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.connectedVideoDisplays | array | A string [] of connected video display port names | +| result.connectedVideoDisplays[#] | string | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getConnectedVideoDisplays" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "connectedVideoDisplays": [ + "HDMI0" + ], + "success": true + } +} +``` + + +## *getCurrentOutputSettings method* + +Returns current output settings. + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.colorSpace | integer | The color space. Possible values: `0` (dsDISPLAY_COLORSPACE_UNKNOWN), `1` (sDISPLAY_COLORSPACE_RGB, `2` (dsDISPLAY_COLORSPACE_YCbCr422), `3` (dsDISPLAY_COLORSPACE_YCbCr444), `4` (dsDISPLAY_COLORSPACE_YCbCr420), `5` (dsDISPLAY_COLORSPACE_AUTO) | +| result.colorDepth | integer | The color depth. The value that is returned from `dsGetCurrentOutputSettings` | +| result.matrixCoefficients | integer | matrix coefficients. Possible values: `0` (dsDISPLAY_MATRIXCOEFFICIENT_UNKNOWN), `1` (dsDISPLAY_MATRIXCOEFFICIENT_BT_709), `2` (dsDISPLAY_MATRIXCOEFFICIENT_BT_470_2_BG), `3` (dsDISPLAY_MATRIXCOEFFICIENT_SMPTE_170M), `4` (dsDISPLAY_MATRIXCOEFFICIENT_XvYCC_709), `5` (dsDISPLAY_MATRIXCOEFFICIENT_eXvYCC_601), `6` (dsDISPLAY_MATRIXCOEFFICIENT_BT_2020_NCL), `7` (dsDISPLAY_MATRIXCOEFFICIENT_BT_2020_CL) | +| result.videoEOTF | integer | HDR standard. Possible values: `0x0` (dsHDRSTANDARD_NONE), `0x01` (dsHDRSTANDARD_HDR10), `0x02` (dsHDRSTANDARD_HLG), `0x04` (dsHDRSTANDARD_DolbyVision), `0x08` (dsHDRSTANDARD_TechnicolorPrime), `0x80` (dsHDRSTANDARD_Invalid) | +| result?.quantizationRange | integer | *(optional)* The supported quantization range | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getCurrentOutputSettings" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "colorSpace": 5, + "colorDepth": 0, + "matrixCoefficients": 0, + "videoEOTF": 0, + "quantizationRange": 235, + "success": true + } +} +``` + + +## *getCurrentResolution method* + +Returns the current resolution on the selected video display port. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.videoDisplay | string | Video display port name. The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.resolution | string | Video display resolution | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getCurrentResolution", + "params": { + "videoDisplay": "HDMI0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "resolution": "1080p", + "success": true + } +} +``` + + +## *getDefaultResolution method* + +Gets the default resolution supported by the HDMI0 video output port. + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.defaultResolution | string | Default resolution supported by the HDMI0 video output port | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getDefaultResolution" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "defaultResolution": "720p", + "success": true + } +} +``` + + +## *getDialogEnhancement method* + +Returns the current Dialog Enhancer level (port HDMI0). + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.enable | boolean | `true` if Dialog Enhancer Mode is enabled, otherwise `false` | +| result.enhancerlevel | integer | Value between 0 and 16, where 0 means no enhancement and 16 means maximum enhancement | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getDialogEnhancement" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "enable": false, + "enhancerlevel": 0, + "success": true + } +} +``` + + +## *getDolbyVolumeMode method* + +Returns whether Dolby Volume mode is enabled or disabled (audio output port HDMI0). + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.dolbyVolumeMode | boolean | Whether Dolby Volume mode is enabled (`true`) or disabled (`false`) | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getDolbyVolumeMode" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "dolbyVolumeMode": true, + "success": true + } +} +``` + + +## *getDRCMode method* + +Returns the current Dynamic Range Control mode. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.DRCMode | string | The DRC Mode value: either `line` or `RF` | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getDRCMode", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "DRCMode": "line", + "success": true + } +} +``` + + +## *getEnableAudioPort method* + + Returns the current status of the specified input audio port. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.audioPort | string | Audio port name. An error returns if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.enable | boolean | `true` if the audio port is enabled, otherwise `false` | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getEnableAudioPort", + "params": { + "audioPort": "HDMI0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "enable": true, + "success": true + } +} +``` + + +## *getGain method* + +Returns the current gain value. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.gain | number | Value between 0 and 100, where 0 means no gain and 100 means maximum gain | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getGain", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "gain": 10.0, + "success": true + } +} +``` + + +## *getGraphicEqualizerMode method* + +Returns the current Graphic Equalizer Mode setting (port HDMI0). + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.enable | boolean | `true` if Graphic Equalizer Mode is enabled, otherwise `false` | +| result.mode | integer | Graphic Equalizer mode (`0` = mode not set or in case of error, `1` = open, `2` = rich, `3` = focused) | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getGraphicEqualizerMode", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "enable": true, + "mode": 2, + "success": true + } +} +``` + + +## *getIntelligentEqualizerMode method* + +Returns the current Intelligent Equalizer Mode setting (port HDMI0). + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.enable | boolean | `true` if Intelligent Equalizer Mode is enabled, otherwise `false` | +| result.mode | integer | Intelligent Equalizer mode (`0` = off, `1` = open, `2` = rich, `3` = focused) | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getIntelligentEqualizerMode" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "enable": true, + "mode": 2, + "success": true + } +} +``` + + +## *getMISteering method* + +Returns the current status of Media Intelligence Steering settings. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result?.MISteeringEnable | boolean | *(optional)* Whether Media Intelligence Steering is enabled (`true`) or disabled (`false`) | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getMISteering", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "MISteeringEnable": true, + "success": true + } +} +``` + + +## *getMS12AudioCompression method* + +Returns the current audio compression settings. + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.enable | boolean | `true` if audio compression is enabled, otherwise `false` | +| result.compressionLevel | integer | Value between 0 and 10, where 0 means no compression and 10 means maximum compression | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getMS12AudioCompression" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "enable": true, + "compressionLevel": 5, + "success": true + } +} +``` + + +## *getMS12AudioProfile method* + +Returns the current MS12 audio profile settings. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.ms12AudioProfile | string | An MS12 audio profile name from `getSupportedMS12AudioProfile` | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getMS12AudioProfile", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "ms12AudioProfile": "Game", + "success": true + } +} +``` + + +## *getMuted method* + +Returns whether audio is muted on a given port. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.muted | boolean | mute or unmute audio | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getMuted", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "muted": true, + "success": true + } +} +``` + + +## *getSettopAudioCapabilities method* + +Returns the set-top audio capabilities for the specified audio port. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.audioCapabilities | array | A string [] of audio capabilities | +| result.audioCapabilities[#] | string | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getSettopAudioCapabilities", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "audioCapabilities": [ + "DOLBY DIGITAL" + ], + "success": true + } +} +``` + + +## *getSettopHDRSupport method* + +Returns an HDR support object (list of standards that the STB supports). + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.standards | array | A string [] of HDR capabilities. Possible values: `none`, `HDR10`, `Dolby Vision`, `Technicolor Prime` | +| result.standards[#] | string | | +| result.supportsHDR | boolean | Indicates support for HDR | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getSettopHDRSupport" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "standards": [ + "none" + ], + "supportsHDR": true, + "success": true + } +} +``` + + +## *getSettopMS12Capabilities method* + +Returns the set-top MS12 audio capabilities for the specified audio port. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.ms12Capabilities | array | A string [] of MS12 audio capabilities | +| result.ms12Capabilities[#] | string | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getSettopMS12Capabilities", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "ms12Capabilities": [ + "Dolby Volume" + ], + "success": true + } +} +``` + + +## *getSinkAtmosCapability method* + +Returns the ATMOS capability of the sink (HDMI0). + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result?.atmos_capability | integer | *(optional)* ATMOS Capability (`0` = off, `1` = open, `2` = rich, `3` = focused) | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getSinkAtmosCapability" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "atmos_capability": 2, + "success": true + } +} +``` + + +## *getSoundMode method* + +Returns the sound mode for the incoming video display. If the argument is `Null` or empty (although not recommended), this returns the output mode of all connected ports, whichever is connected, while giving priority to the HDMI port. If the video display is not connected, then it returns `Stereo` as a safe default. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.audioPort | string | Audio port name. An error returns if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.soundMode | string | Sound mode. Possible values: `AUTO (Dolby Digital Plus)`, `AUTO (Dolby Digital 5.1)`, `AUTO (Stereo)`, `MONO`, `STEREO`, `SURROUND`, PASSTHRU | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getSoundMode", + "params": { + "audioPort": "HDMI0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "soundMode": "STEREO", + "success": true + } +} +``` + + +## *getSupportedAudioModes method* + +Returns a list of strings containing the supported audio modes. If `Null` or empty, this returns the supported audio modes of the audio processor (regardless of the the output port). +If a port name is specified, this returns the audio output modes supported by the connected sink device (EDID based). If the port is not connected, the return value is same as if `Null` is specified as the parameter. +For **Auto** mode in DS5, this API has the following extra specification: +* For HDMI port, if connected, this API returns `Stereo` mode and `Auto` mode; +* For HDMI port, if not connected, this API returns `Stereo` mode and `Dolby Digital 5.1` mode; +* For SPDIF and HDMI ARC port, this API always returns `Surround` mode, `Stereo` mode, and `Dolby Digital 5.1` Mode; +* When `AUTO` mode is returned, it includes in parenthesis the best sound mode that the STB can output and the connected sink device can support, in the format of `AUTO` _(`Best Format`)_. For example, if the connected device supports surround, the auto mode string will be `AUTO (Dolby Digital 5.1)`. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.audioPort | string | Audio port name. An error returns if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result?.supportedAudioModes | array | *(optional)* A string [] of supported audio modes | +| result?.supportedAudioModes[#] | string | *(optional)* | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getSupportedAudioModes", + "params": { + "audioPort": "HDMI0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "supportedAudioModes": [ + "STEREO" + ], + "success": true + } +} +``` + + +## *getSupportedAudioPorts method* + +Returns all audio ports supported on the device (all ports that are physically present). + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.supportedAudioPorts | array | A string [] of supported audio ports | +| result.supportedAudioPorts[#] | string | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getSupportedAudioPorts" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "supportedAudioPorts": [ + "HDMI0" + ], + "success": true + } +} +``` + + +## *getSupportedMS12AudioProfiles method* + +Returns list of platform supported MS12 audio profiles for the specified audio port. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.supportedMS12AudioProfiles | array | A string [] of MS12 audio profiles | +| result.supportedMS12AudioProfiles[#] | string | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getSupportedMS12AudioProfiles", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "supportedMS12AudioProfiles": [ + "Movie" + ], + "success": true + } +} +``` + + +## *getSupportedResolutions method* + +Returns supported resolutions on the selected video display port (both TV and STB) by its name. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.videoDisplay | string | Video display port name. The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.supportedResolutions | array | A string array of supported resolutions on the selected video display port | +| result.supportedResolutions[#] | string | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getSupportedResolutions", + "params": { + "videoDisplay": "HDMI0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "supportedResolutions": [ + "1080p60" + ], + "success": true + } +} +``` + + +## *getSupportedSettopResolutions method* + +Returns supported STB resolutions. + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.supportedSettopResolutions | array | A string array of supported STB resolutions | +| result.supportedSettopResolutions[#] | string | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getSupportedSettopResolutions" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "supportedSettopResolutions": [ + "1080p60" + ], + "success": true + } +} +``` + + +## *getSupportedTvResolutions method* + +Returns supported TV resolutions on the selected video display port. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.videoDisplay | string | Video display port name. The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.supportedTvResolutions | array | A string [] of supported TV resolutions | +| result.supportedTvResolutions[#] | string | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getSupportedTvResolutions", + "params": { + "videoDisplay": "HDMI0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "supportedTvResolutions": [ + "1080p" + ], + "success": true + } +} +``` + + +## *getSupportedVideoDisplays method* + +Returns all video ports supported on the device (all ports that are physically present). + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.supportedVideoDisplays | array | a string [] of supported video display ports | +| result.supportedVideoDisplays[#] | string | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getSupportedVideoDisplays" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "supportedVideoDisplays": [ + "HDMI0" + ], + "success": true + } +} +``` + + +## *getSurroundVirtualizer method* + +Returns the current surround virtualizer boost settings. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.enable | boolean | `true` if Surround Virtualizer is enabled, otherwise `false` | +| result.boost | integer | Value between 0 and 96, where 0 means no boost and 96 means maximum boost | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getSurroundVirtualizer", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "enable": true, + "boost": 90, + "success": true + } +} +``` + + +## *getSurroundVirtualizer2 method* + +(Version 2) Returns the current surround virtualizer boost settings. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.mode | integer | Enables or disables volume leveling (`0` = off, `1` = on, `2` = auto) | +| result.boost | integer | Value between 0 and 96, where 0 means no boost and 96 means maximum boost | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getSurroundVirtualizer2", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "mode": 1, + "boost": 90, + "success": true + } +} +``` + + +## *getTVHDRCapabilities method* + +Gets HDR capabilities supported by the TV. The following values (OR-ed value) are possible: +* 0 - HDRSTANDARD_NONE +* 1 - HDRSTANDARD_HDR10 +* 2 - HDRSTANDARD_HLG +* 4 - HDRSTANDARD_DolbyVision +* 8 - HDRSTANDARD_TechnicolorPrime. + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.capabilities | integer | The OR-ed value of supported HDR standards by the TV | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getTVHDRCapabilities" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "capabilities": 3, + "success": true + } +} +``` + + +## *getTvHDRSupport method* + +Returns an HDR support object (list of standards that the TV supports). + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.standards | array | A string [] of HDR capabilities. Possible values: `none`, `HDR10`, `Dolby Vision`, `Technicolor Prime` | +| result.standards[#] | string | | +| result.supportsHDR | boolean | Indicates support for HDR | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getTvHDRSupport" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "standards": [ + "none" + ], + "supportsHDR": true, + "success": true + } +} +``` + + +## *getVideoPortStatusInStandby method* + +Returns video port status in standby mode (failure if the port name is missing). + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.portName | string | Video port name | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.VideoPortStatusInStandby | boolean | video port status (enabled/disabled) in standby mode | +| result.error_message | string | Error message in case of failure | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getVideoPortStatusInStandby", + "params": { + "portName": "HDMI0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "VideoPortStatusInStandby": true, + "error_message": "internal error", + "success": true + } +} +``` + + +## *getVolumeLevel method* + +Returns the current volume level. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.volumeLevel | number | Value between 0 and 100, where 0 means no level and 100 means maximum level | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getVolumeLevel", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "volumeLevel": 50, + "success": true + } +} +``` + + +## *getVolumeLeveller method* + +Returns the current Volume Leveller setting. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.enable | boolean | `true` if Volume Leveller is enabled, otherwise `false` | +| result.level | integer | Value between 0 and 10, where 0 means no level and 10 means maximum level | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getVolumeLeveller", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "enable": true, + "level": 9, + "success": true + } +} +``` + + +## *getVolumeLeveller2 method* + +(Version 2) Returns the current Volume Leveller setting. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.mode | integer | Enables or disables volume leveling (`0` = off, `1` = on, `2` = auto) | +| result.level | integer | Value between 0 and 10, where 0 means no level and 10 means maximum level | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getVolumeLeveller2", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "mode": 1, + "level": 9, + "success": true + } +} +``` + + +## *getZoomSetting method* + +Returns the zoom setting value. + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.zoomSetting | boolean | Whether the request succeeded | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getZoomSetting" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "zoomSetting": true, + "success": true + } +} +``` + + +## *isConnectedDeviceRepeater method* + +Indicates whether the device connected to the HDMI0 video output port is an HDCP repeater. + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.HdcpRepeater | boolean | `true` if the device is an HDCP repeater otherwise `false` | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.isConnectedDeviceRepeater" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "HdcpRepeater": true, + "success": true + } +} +``` + + +## *isSurroundDecoderEnabled method* + +Returns the current status of Surround Decoder. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.surroundDecoderEnable | boolean | Whether Surround Decoder is is enabled (`true`) or disabled (`false`) | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.isSurroundDecoderEnabled", + "params": { + "audioPort": "SPEAKER0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "surroundDecoderEnable": true, + "success": true + } +} +``` + + +## *readEDID method* + +Reads the EDID from the connected HDMI (output) device. Returns a key of `EDID` with a value of the base64 encoded byte array string representing the EDID. + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.EDID | string | A base64 encoded byte array string representing the EDID | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.readEDID" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "EDID": "AP///////wAQrMLQVEJTMQUdAQOANR546q11qVRNnSYPUFSlSwCBALMA0QBxT6lAgYDRwAEBVl4AoKCgKVAwIDUADighAAAaAAAA/wBNWTNORDkxVjFTQlQKAAAA/ABERUxMIFAyNDE4RAogAAAA/QAxVh1xHAAKICAgICAgARsCAxuxUJAFBAMCBxYBBhESFRMUHyBlAwwAEAACOoAYcTgtQFgsRQAOKCEAAB4BHYAYcRwWIFgsJQAOKCEAAJ6/FgCggDgTQDAgOgAOKCEAABp+OQCggDgfQDAgOgAOKCEAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2A", + "success": true + } +} +``` + + +## *readHostEDID method* + +Reads the EDID of the host (STB). Returns a key of `EDID` with a value of the base64 encoded raw byte array string representing the EDID. + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.EDID | string | A base64 encoded byte array string representing the EDID | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.readHostEDID" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "EDID": "AP///////wAQrMLQVEJTMQUdAQOANR546q11qVRNnSYPUFSlSwCBALMA0QBxT6lAgYDRwAEBVl4AoKCgKVAwIDUADighAAAaAAAA/wBNWTNORDkxVjFTQlQKAAAA/ABERUxMIFAyNDE4RAogAAAA/QAxVh1xHAAKICAgICAgARsCAxuxUJAFBAMCBxYBBhESFRMUHyBlAwwAEAACOoAYcTgtQFgsRQAOKCEAAB4BHYAYcRwWIFgsJQAOKCEAAJ6/FgCggDgTQDAgOgAOKCEAABp+OQCggDgfQDAgOgAOKCEAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2A", + "success": true + } +} +``` + + +## *setAudioAtmosOutputMode method* + +Sets ATMOS audio output mode (on HDMI0). + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.enable | boolean | enable or disable ATMOS audio output mode | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setAudioAtmosOutputMode", + "params": { + "enable": true + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setAudioDelay method* + +Sets the audio delay (in ms) on the selected audio port. If the `audioPort` argument is not specified, it will browse all ports (checking HDMI0 first). If there is no display connected, then it defaults to `HDMI0`. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.audioPort | string | Audio port name. An error returns if no port is specified | +| params.audioDelay | string | Delay (in ms) on the selected audio port | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setAudioDelay", + "params": { + "audioPort": "HDMI0", + "audioDelay": "0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setAudioDelayOffset method* + +Sets the audio delay offset (in ms) on the selected audio port. If the `audioPort` argument is not specified, it will browse all ports (checking HDMI0 first). If there is no display connected, then it defaults to `HDMI0`. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.audioPort | string | Audio port name. An error returns if no port is specified | +| params.audioDelayOffset | string | Delay offset (in ms) on the selected audio port | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setAudioDelayOffset", + "params": { + "audioPort": "HDMI0", + "audioDelayOffset": "0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setBassEnhancer method* + +Sets the Bass Enhancer. Bass Enhancer provides the consumer a single control to apply a fixed bass boost to correct for a lack of bass reproduction in the playback system. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | +| params.bassBoost | integer | Value between 0 and 100, where 0 means no bass boost (disabled) and 100 means max bass boost | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setBassEnhancer", + "params": { + "audioPort": "SPEAKER0", + "bassBoost": 50 + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setCurrentResolution method* + +Sets the current resolution. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.videoDisplay | string | Video display port name. The default port is `HDMI0` if no port is specified | +| params.resolution | string | Video display resolution | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setCurrentResolution", + "params": { + "videoDisplay": "HDMI0", + "resolution": "1080p" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setDialogEnhancement method* + +Sets the Dialog Enhancer level. The method fails if no value is set. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.enhancerlevel | integer | Value between 0 and 16, where 0 means no enhancement and 16 means maximum enhancement | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setDialogEnhancement", + "params": { + "enhancerlevel": 0 + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setDolbyVolumeMode method* + +Enables or disables Dolby Volume mode on audio track (audio output port HDMI0). + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.dolbyVolumeMode | boolean | Whether Dolby Volume mode is enabled (`true`) or disabled (`false`) | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setDolbyVolumeMode", + "params": { + "dolbyVolumeMode": true + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setDRCMode method* + +Sets the Dynamic Range Control (DRC) setting. DRC is a compression control applied to audio to limit the dynamic range to suit a specific listening situation. For default settings, RF mode is preferred for two-channel outputs (television speaker or headphone) and Line mode for multichannel outputs. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | +| params.DRCMode | integer | Value of 0 or 1, where 0 is Line mode and 1 is RF mode | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setDRCMode", + "params": { + "audioPort": "SPEAKER0", + "DRCMode": 1 + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setEnableAudioPort method* + +Enable or disable the specified audio port based on the input audio port ID. This feature provides the consumer with a single user control to enable or disable the specified audio port. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.audioPort | string | Audio port name. An error returns if no port is specified | +| params.enable | boolean | `true` enables the specified audio port. `false` disables the specified audio port | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setEnableAudioPort", + "params": { + "audioPort": "HDMI0", + "enable": true + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setGain method* + +Adjusts the gain on a specific port. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | +| params.gain | number | Value between 0 and 100, where 0 means no gain and 100 means maximum gain | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setGain", + "params": { + "audioPort": "SPEAKER0", + "gain": 10.0 + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setGraphicEqualizerMode method* + +Sets the Graphic Equalizer Mode. The Graphic Equalizer is a multi-band equalizer that allows the end user to customize the sonic qualities of the system. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | +| params.graphicEqualizerMode | integer | Graphic Equalizer mode (`0` = off, `1` = open, `2` = rich, `3` = focused) | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setGraphicEqualizerMode", + "params": { + "audioPort": "SPEAKER0", + "graphicEqualizerMode": 2 + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setIntelligentEqualizerMode method* + +Sets the Intelligent Equalizer mode (port HDMI0). + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.intelligentEqualizerMode | integer | Intelligent Equalizer mode (`0` = unset, `1` = open, `2` = rich, `3` = focused) | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setIntelligentEqualizerMode", + "params": { + "intelligentEqualizerMode": 2 + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setMISteering method* + +Enables or Disables Media Intelligent Steering. Media Intelligence analyzes audio content and steers the Volume Leveler, the Dialogue Enhancer, the Intelligent Equalizer, and the Speaker Virtualizer, based on the type of audio content. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | +| params.MISteeringEnable | boolean | Whether Media Intelligence Steering is enabled (`true`) or disabled (`false`) | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setMISteering", + "params": { + "audioPort": "SPEAKER0", + "MISteeringEnable": true + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setMS12AudioCompression method* + +Sets the audio dynamic range compression level (port HDMI0). + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.compressionLevel | integer | Value between 0 and 10, where 0 means no compression and 10 means maximum compression | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setMS12AudioCompression", + "params": { + "compressionLevel": 5 + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setMS12AudioProfile method* + +Sets the selected MS12 audio profile. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | +| params.ms12AudioProfile | string | An MS12 audio profile name from `getSupportedMS12AudioProfile` | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setMS12AudioProfile", + "params": { + "audioPort": "SPEAKER0", + "ms12AudioProfile": "Game" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setMuted method* + +Mutes or unmutes audio on a specific port. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | +| params.muted | boolean | mute or unmute audio | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setMuted", + "params": { + "audioPort": "SPEAKER0", + "muted": true + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setScartParameter method* + +Sets SCART parameters. + +Possible values: +| **Parameter** | **ParameterData** | +| `aspect_ratio` | `4x3` or `16x9` | +| `tv_startup` | `on` or `off` | +| `rgb` | `on` (disables cvbs) | +| `cvbs` | `on` (disables rgb) | +| `macrovision` | not implemented | +| `cgms` | `disabled`, `copyNever`, `copyOnce`, `copyFreely`, or `copyNoMore` | +| `port` | `on` or `off` |. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.scartParameter | string | SCART parameter name | +| params?.scartParameterData | string | *(optional)* SCART parameter data | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setScartParameter", + "params": { + "scartParameter": "aspect_ratio", + "scartParameterData": "4x3" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setSoundMode method* + +Sets the current sound mode for the corresponding video display. If the `audioPort` argument value is missing or empty all ports are set. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.audioPort | string | Audio port name. An error returns if no port is specified | +| params.soundMode | string | Sound mode. Possible values: `AUTO (Dolby Digital Plus)`, `AUTO (Dolby Digital 5.1)`, `AUTO (Stereo)`, `MONO`, `STEREO`, `SURROUND`, PASSTHRU | +| params.persist | boolean | persists the sound mode | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setSoundMode", + "params": { + "audioPort": "HDMI0", + "soundMode": "STEREO", + "persist": true + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setSurroundVirtualizer method* + +Sets the Surround Virtualizer boost. The Speaker/Surround Virtualizer enables a surround sound signal (including one generated by the Surround Decoder) to be rendered over a device with built-in speakers or headphones. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | +| params.boost | integer | Value between 0 and 96, where 0 means no boost and 96 means maximum boost | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setSurroundVirtualizer", + "params": { + "audioPort": "SPEAKER0", + "boost": 90 + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setSurroundVirtualizer2 method* + +(Version 2) Sets the Surround Virtualizer boost. The Speaker/Surround Virtualizer enables a surround sound signal (including one generated by the Surround Decoder) to be rendered over a device with built-in speakers or headphones. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | +| params.mode | integer | Enables or disables volume leveling (`0` = off, `1` = on, `2` = auto) | +| params.boost | integer | Value between 0 and 96, where 0 means no boost and 96 means maximum boost | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setSurroundVirtualizer2", + "params": { + "audioPort": "SPEAKER0", + "mode": 1, + "boost": 90 + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setVideoPortStatusInStandby method* + +Sets the specified video port status to be used in standby mode (failure if the port name is missing). + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.portName | string | Video port name | +| params.enabled | boolean | Enable video port status to be used in standby mode | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.error_message | string | Error message in case of failure | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setVideoPortStatusInStandby", + "params": { + "portName": "HDMI0", + "enabled": true + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "error_message": "internal error", + "success": true + } +} +``` + + +## *setVolumeLevel method* + +Adjusts the Volume Level on a specific port. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | +| params.volumeLevel | number | Value between 0 and 100, where 0 means no level and 100 means maximum level | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setVolumeLevel", + "params": { + "audioPort": "SPEAKER0", + "volumeLevel": 50 + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setVolumeLeveller method* + +Sets the Volume Leveller level. Volume Leveler is an advanced volume-control solution that maintains consistent playback levels for content from different sources. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | +| params.level | integer | Value between 0 and 10, where 0 means no level and 10 means maximum level | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setVolumeLeveller", + "params": { + "audioPort": "SPEAKER0", + "level": 9 + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setVolumeLeveller2 method* + +(Version 2) Sets the Volume Leveller level. Volume Leveler is an advanced volume-control solution that maintains consistent playback levels for content from different sources. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port name (`HDMI0`, `SPEAKER0`, `SPDIF0`, and so on). The default port is `HDMI0` if no port is specified | +| params.mode | integer | Enables or disables volume leveling (`0` = off, `1` = on, `2` = auto) | +| params.level | integer | Value between 0 and 10, where 0 means no level and 10 means maximum level | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setVolumeLeveller2", + "params": { + "audioPort": "SPEAKER0", + "mode": 1, + "level": 9 + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setZoomSetting method* + +Sets the current zoom value. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.zoomSetting | boolean | Whether the request succeeded | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setZoomSetting", + "params": { + "zoomSetting": true + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setOutputFrameRatePreference method* + +Sets the current output frame rate follow content preference. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.videoDisplay | string | *(optional)* Video display port name. The default port is `HDMI0` if no port is specified | +| params.followContent | boolean | should follow content frame rate | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setOutputFrameRatePreference", + "params": { + "videoDisplay": "HDMI0", + "followContent": true + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setAudioProcessingHint method* + +Sets audio processing hint. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port ID | +| params.audioMode | string | Audio mode - dd/pcm | +| params?.audioDelayMs | integer | *(optional)* audio delay [ms] | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setAudioProcessingHint", + "params": { + "audioPort": "HDMI0", + "audioMode": "pcm", + "audioDelayMs": 50 + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *getAudioOutputEncoding method* + +gets current audio output encoding. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.audioPort | string | *(optional)* Audio port ID | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.encoding | string | audio encoding type | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getAudioOutputEncoding", + "params": { + "audioPort": "HDMI0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "encoding": "PCM", + "success": true + } +} +``` + + +## *getFollowColorSpace method* + +get current follow colorspace setting. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.videoDisplay | string | *(optional)* Video display port name. The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.followColorSpace | boolean | should follow colorspace | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getFollowColorSpace", + "params": { + "videoDisplay": "HDMI0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "followColorSpace": true, + "success": true + } +} +``` + + +## *setFollowColorSpace method* + +set current follow colorspace setting. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.videoDisplay | string | *(optional)* Video display port name. The default port is `HDMI0` if no port is specified | +| params.followColorSpace | boolean | should follow color space | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setFollowColorSpace", + "params": { + "videoDisplay": "HDMI0", + "followColorSpace": true + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *getPreferredOutputColorSpace method* + +get current preferred output colorspace. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.videoDisplay | string | *(optional)* Video display port name. The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.preferredOutputColorSpaces | array | A string [] of preferred colorspaces | +| result.preferredOutputColorSpaces[#] | string | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getPreferredOutputColorSpace", + "params": { + "videoDisplay": "HDMI0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "preferredOutputColorSpaces": [ + "BT2020_NCL" + ], + "success": true + } +} +``` + + +## *setPreferredOutputColorSpace method* + +set current preferred output colorspace. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.videoDisplay | string | *(optional)* Video display port name. The default port is `HDMI0` if no port is specified | +| params.preferredOutputColorSpaces | array | A string [] of preferred colorspaces | +| params.preferredOutputColorSpaces[#] | string | | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setPreferredOutputColorSpace", + "params": { + "videoDisplay": "HDMI0", + "preferredOutputColorSpaces": [ + "BT2020_NCL" + ] + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *getHDRGfxColorSpace method* + +Gets current HDR gfx colorspace values. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.videoDisplay | string | *(optional)* Video display port name. The default port is `HDMI0` if no port is specified | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.y | integer | y value | +| result.cr | integer | cr value | +| result.cb | integer | cb value | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.getHDRGfxColorSpace", + "params": { + "videoDisplay": "HDMI0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "y": 0, + "cr": 0, + "cb": 0, + "success": true + } +} +``` + + +## *setHDRGfxColorSpace method* + +Sets current HDR gfx colorspace values. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.videoDisplay | string | *(optional)* Video display port name. The default port is `HDMI0` if no port is specified | +| params.y | integer | y value | +| params.cr | integer | cr value | +| params.cb | integer | cb value | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "com.lgi.rdk.DisplaySettings.1.setHDRGfxColorSpace", + "params": { + "videoDisplay": "HDMI0", + "y": 0, + "cr": 0, + "cb": 0 + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +# Notifications + +Notifications are autonomous events, triggered by the internals of the implementation, and broadcasted via JSON-RPC to all registered observers. Refer to [[Thunder](#ref.Thunder)] for information on how to register for a notification. + +The following events are provided by the com.lgi.rdk.DisplaySettings plugin: + +com.lgi.rdk.DisplaySettings interface events: + +| Event | Description | +| :-------- | :-------- | +| [activeInputChanged](#event.activeInputChanged) | Triggered on active input change (RxSense) | +| [connectedAudioPortUpdated](#event.connectedAudioPortUpdated) | Triggered when the connected audio port is updated | +| [connectedVideoDisplaysUpdated](#event.connectedVideoDisplaysUpdated) | Triggered when the connected video display is updated and returns the connected video displays | +| [resolutionChanged](#event.resolutionChanged) | Triggered when the resolution is changed by the user and returns the current resolution | +| [resolutionPreChange](#event.resolutionPreChange) | Triggered on resolution pre-change | +| [zoomSettingUpdated](#event.zoomSettingUpdated) | Triggered when the zoom setting changes and returns the zoom setting values for all video display types | + + + +## *activeInputChanged event* + +Triggered on active input change (RxSense). + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.activeInput | boolean | `true` = RXSENSE_ON, `false` = RXSENSE_OFF | + +### Example + +```json +{ + "jsonrpc": "2.0", + "method": "client.events.1.activeInputChanged", + "params": { + "activeInput": true + } +} +``` + + +## *connectedAudioPortUpdated event* + +Triggered when the connected audio port is updated. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params?.HotpluggedAudioPort | string | *(optional)* Audio port ID for which the connection status has changed. Possible audio port IDs are (`HDMI_ARC0`, `HEADPHONE0`, etc) | +| params.isConnected | string | Current connection status of the audio port (must be one of the following: *connected*, *disconnected*) | + +### Example + +```json +{ + "jsonrpc": "2.0", + "method": "client.events.1.connectedAudioPortUpdated", + "params": { + "HotpluggedAudioPort": "HDMI_ARC0", + "isConnected": "connected" + } +} +``` + + +## *connectedVideoDisplaysUpdated event* + +Triggered when the connected video display is updated and returns the connected video displays. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.connectedVideoDisplays | array | A string [] of connected video display port names | +| params.connectedVideoDisplays[#] | string | | + +### Example + +```json +{ + "jsonrpc": "2.0", + "method": "client.events.1.connectedVideoDisplaysUpdated", + "params": { + "connectedVideoDisplays": [ + "HDMI0" + ] + } +} +``` + + +## *resolutionChanged event* + +Triggered when the resolution is changed by the user and returns the current resolution. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.width | integer | Width of the video display | +| params.height | integer | Height of the video display | +| params.videoDisplayType | string | Type of video display (port) | +| params.resolution | string | Video display resolution | + +### Example + +```json +{ + "jsonrpc": "2.0", + "method": "client.events.1.resolutionChanged", + "params": { + "width": 1920, + "height": 1080, + "videoDisplayType": "HDMI0", + "resolution": "1080p" + } +} +``` + + +## *resolutionPreChange event* + +Triggered on resolution pre-change. + +### Parameters + +This event carries no parameters. + +### Example + +```json +{ + "jsonrpc": "2.0", + "method": "client.events.1.resolutionPreChange" +} +``` + + +## *zoomSettingUpdated event* + +Triggered when the zoom setting changes and returns the zoom setting values for all video display types. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.zoomSetting | string | Zoom setting. Possible values: `FULL`, `NONE,` `Letterbox 16x9`, `Letterbox 14x9`, `CCO`, `PanScan`, `Letterbox 2.21 on 4x3`, `Letterbox 2.21 on 16x9`, `Platform`, `Zoom 16x9`, `Pillarbox 4x3`, `Widescreen 4x3` | +| params.videoDisplayType | string | Type of video display (port) | + +### Example + +```json +{ + "jsonrpc": "2.0", + "method": "client.events.1.zoomSettingUpdated", + "params": { + "zoomSetting": "FULL", + "videoDisplayType": "HDMI0" + } +} +``` + diff --git a/LgiDisplaySettings/tr181api.h b/LgiDisplaySettings/tr181api.h new file mode 100644 index 0000000000..6d7cf46072 --- /dev/null +++ b/LgiDisplaySettings/tr181api.h @@ -0,0 +1 @@ +//empty file to avoid patching ../DisplaySettings/DisplaySettings.cpp diff --git a/LgiHdcpProfile/CMakeLists.txt b/LgiHdcpProfile/CMakeLists.txt new file mode 100644 index 0000000000..4445d4dfe1 --- /dev/null +++ b/LgiHdcpProfile/CMakeLists.txt @@ -0,0 +1,55 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# Copyright 2021 Liberty Global Service B.V. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(PLUGIN_NAME LgiHdcpProfile) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) + +find_package(${NAMESPACE}Plugins REQUIRED) + +add_library(${MODULE_NAME} SHARED + LgiHdcpProfile.cpp + ../HdcpProfile/HdcpProfile.cpp + Module.cpp) + +add_definitions(-DMODULE_NAME=${PLUGIN_NAME}) + +find_package(CURL) +if (CURL_FOUND) + include_directories(${CURL_INCLUDE_DIRS}) + target_link_libraries(${MODULE_NAME} PRIVATE ${CURL_LIBRARIES}) +else (CURL_FOUND) + message ("Curl/libcurl required.") +endif (CURL_FOUND) + +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +find_package(DS) +find_package(IARMBus) + +target_include_directories(${MODULE_NAME} PRIVATE ${IARMBUS_INCLUDE_DIRS} ../helpers ../HdcpProfile) +target_include_directories(${MODULE_NAME} PRIVATE ${DS_INCLUDE_DIRS}) + +target_link_libraries(${MODULE_NAME} PUBLIC ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${IARMBUS_LIBRARIES} ${DS_LIBRARIES} ${NAMESPACE}SecurityUtil) + + +install(TARGETS ${MODULE_NAME} + DESTINATION lib/${STORAGE_DIRECTORY}/plugins) + +write_config(${PLUGIN_NAME}) diff --git a/LgiHdcpProfile/LgiHdcpProfile.config b/LgiHdcpProfile/LgiHdcpProfile.config new file mode 100644 index 0000000000..9ca4c49c9e --- /dev/null +++ b/LgiHdcpProfile/LgiHdcpProfile.config @@ -0,0 +1,11 @@ +set (autostart ${PLUGIN_LGIHDCPPROFILE_AUTOSTART}) +set (preconditions Platform) +set (callsign "com.lgi.rdk.HdcpProfile") +map() + key(root) + map() + kv(outofprocess ${PLUGIN_LGIHDCPPROFILE_OUTOFPROCESS}) + end() +end() + +ans(configuration) diff --git a/LgiHdcpProfile/LgiHdcpProfile.cpp b/LgiHdcpProfile/LgiHdcpProfile.cpp new file mode 100644 index 0000000000..b9d2e44513 --- /dev/null +++ b/LgiHdcpProfile/LgiHdcpProfile.cpp @@ -0,0 +1,118 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* Copyright 2021 Liberty Global Service B.V. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include + +#include "LgiHdcpProfile.h" + +#include "videoOutputPort.hpp" +#include "videoOutputPortConfig.hpp" +#include "dsMgr.h" +#include "manager.hpp" +#include "host.hpp" + +#include "UtilsJsonRpc.h" +#include "UtilsSynchro.hpp" + +#define HDCP_PROFILE_METHOD_GET_Rx_HDCP_SUPPORT "getRxHDCPSupportedVersion" + +namespace WPEFramework +{ + namespace Plugin + { + SERVICE_REGISTRATION(LgiHdcpProfile, 1, 0); + static const char* getHdcpReasonStr (int eHDCPEnabledStatus); + + LgiHdcpProfile::LgiHdcpProfile() + : HdcpProfile() + { + Utils::Synchro::RegisterLockedApi(HDCP_PROFILE_METHOD_GET_Rx_HDCP_SUPPORT, &LgiHdcpProfile::getRxHDCPSupportedVersion, this); + } + + LgiHdcpProfile::~LgiHdcpProfile() + { + } + + //Begin methods + uint32_t LgiHdcpProfile::getRxHDCPSupportedVersion(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + + string rxHDCPSupportedVersion; + bool res = false; + bool isConnected = false; + dsHdcpProtocolVersion_t HDCPVersion = dsHDCP_VERSION_1X; + const string defaultVideoDisplay = device::Host::getInstance().getDefaultVideoPortName(); + const string videoDisplay = parameters.HasLabel("videoDisplay") ? parameters["videoDisplay"].String() : defaultVideoDisplay; + + try + { + device::VideoOutputPort vPort = device::VideoOutputPortConfig::getInstance().getPort(videoDisplay); + isConnected = vPort.isDisplayConnected(); + if(isConnected) + { + isConnected = vPort.isActive(); + if(isConnected) + { + HDCPVersion = (dsHdcpProtocolVersion_t)vPort.getRxHDCPSupportedVersion(); + } + } + } + catch (const std::exception e) + { + LOGWARN("DS exception caught: %s\n",e.what()); + isConnected = false; + } + + if(!isConnected) + { + rxHDCPSupportedVersion = "0.0"; + LOGWARN("HDMI not connected; HDCP version unknown\n"); + } + else + { + LOGINFO("HDMI connected, HDCP version %d\n",int(HDCPVersion)); + switch(HDCPVersion) + { + case dsHDCP_VERSION_2X: + rxHDCPSupportedVersion = "2.2"; + res = true; + break; + case dsHDCP_VERSION_1X: + res = true; + rxHDCPSupportedVersion = "1.4"; + break; + default: + LOGWARN("protocol version value '%d' unknown; returning 0.0\n", HDCPVersion); + rxHDCPSupportedVersion = "0.0"; + break; + } + } + + response["supportedRxHDCPVersion"] = rxHDCPSupportedVersion; + returnResponse(res); + } + + //End methods + + + } // namespace Plugin +} // namespace WPEFramework + diff --git a/LgiHdcpProfile/LgiHdcpProfile.h b/LgiHdcpProfile/LgiHdcpProfile.h new file mode 100644 index 0000000000..923c132d7f --- /dev/null +++ b/LgiHdcpProfile/LgiHdcpProfile.h @@ -0,0 +1,64 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* Copyright 2021 Liberty Global Service B.V. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once + +#include "Module.h" +#include "HdcpProfile.h" + +#include "libIBus.h" + +#include "UtilsJsonRpc.h" + +namespace WPEFramework { + + namespace Plugin { + + // This is a server for a JSONRPC communication channel. + // For a plugin to be capable to handle JSONRPC, inherit from PluginHost::JSONRPC. + // By inheriting from this class, the plugin realizes the interface PluginHost::IDispatcher. + // This realization of this interface implements, by default, the following methods on this plugin + // - exists + // - register + // - unregister + // Any other methood to be handled by this plugin can be added can be added by using the + // templated methods Register on the PluginHost::JSONRPC class. + // As the registration/unregistration of notifications is realized by the class PluginHost::JSONRPC, + // this class exposes a public method called, Notify(), using this methods, all subscribed clients + // will receive a JSONRPC message as a notification, in case this method is called. + class LgiHdcpProfile : public HdcpProfile { + private: + + // We do not allow this plugin to be copied !! + LgiHdcpProfile(const LgiHdcpProfile&) = delete; + LgiHdcpProfile& operator=(const LgiHdcpProfile&) = delete; + + //Begin methods + uint32_t getRxHDCPSupportedVersion(const JsonObject& parameters, JsonObject& response); + + //End methods + + public: + LgiHdcpProfile(); + virtual ~LgiHdcpProfile(); + }; + } // namespace Plugin +} // namespace WPEFramework + diff --git a/LgiHdcpProfile/LgiHdcpProfile.json b/LgiHdcpProfile/LgiHdcpProfile.json new file mode 100644 index 0000000000..8f2b19f837 --- /dev/null +++ b/LgiHdcpProfile/LgiHdcpProfile.json @@ -0,0 +1,175 @@ +{ + "$schema": "https://raw.githubusercontent.com/rdkcentral/Thunder/master/Tools/JsonGenerator/schemas/interface.schema.json", + "jsonrpc": "2.0", + "info": { + "title": "HdcpProfile API", + "class": "com.lgi.rdk.HdcpProfile", + "description": "The HdcpProfile plugin provides an interface for HDCP-related data and events." + }, + "definitions": { + "supportedHDCPVersion": { + "summary": "Supported HDCP protocol version by the host device.", + "type": "string", + "example": "2.2" + }, + "supportedRxHDCPVersion": { + "summary": "Supported HDCP protocol version by the receiver device (display).", + "type": "string", + "example": "1.4" + }, + "videoDisplay": { + "summary": "Video display port name. The default port is used if no port is specified", + "type": "string", + "example": "HDMI0" + }, + "HDCPStatus":{ + "summary": "Contains HDCP-related data as separate properties", + "type": "object", + "properties": { + "isConnected": { + "summary": "Indicates whether a display is connected.", + "type": "boolean", + "example": false + }, + "isHDCPCompliant": { + "summary": "Indicates whether the display is HDCP compliant.", + "type": "boolean", + "example": false + }, + "isHDCPEnabled": { + "summary": "Indicates whether content is protected.", + "type": "boolean", + "example": false + }, + "hdcpReason": { + "summary": "The HDCP status reason", + "type": "integer", + "example": 1 + }, + "supportedHDCPVersion": { + "$ref": "#/definitions/supportedHDCPVersion" + }, + "receiverHDCPVersion": { + "summary": "Supported HDCP protocol version by the receiver device (display)", + "type": "string", + "example": "1.4" + }, + "currentHDCPVersion": { + "summary": "Currently used HDCP protocol version", + "type": "string", + "example": "1.4" + } + }, + "required":[ + "isConnected", + "isHDCPCompliant", + "isHDCPEnabled", + "hdcpReason", + "supportedHDCPVersion", + "receiverHDCPVersion", + "currentHDCPVersion" + ] + }, + "result": { + "type":"object", + "properties": { + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "success" + ] + }, + "success": { + "summary": "Whether the request succeeded", + "type": "boolean", + "example": "true" + } + }, + "methods": { + "getHDCPStatus": { + "summary": "Returns HDCP-related data. \n**hdcpReason Argument Values** \n* `0`: HDMI cable is not connected or rx sense status is `off` \n* `1`: If rx device connected with power on state, then this should be the initial status \n* `2`: HDCP success \n* `3`: After multiple retries, the platform should transition to this state \n* `4`: Platform in HDCP process \n* `5`: Expected when `dsEnableVideoPort` is called with enable status `false`", + "result": { + "type":"object", + "properties": { + "HDCPStatus":{ + "$ref": "#/definitions/HDCPStatus" + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "HDCPStatus", + "success" + ] + } + }, + "getSettopHDCPSupport": { + "summary": "Returns which version of HDCP is supported by the STB", + "result":{ + "type":"object", + "properties": { + "supportedHDCPVersion":{ + "$ref": "#/definitions/supportedHDCPVersion" + }, + "isHDCPSupported": { + "description": "Indicates whether HDCP is supported by the STB", + "type": "boolean", + "example": true + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required":[ + "supportedHDCPVersion", + "isHDCPSupported", + "success" + ] + } + }, + "getRxHDCPSupportedVersion": { + "summary": "Returns which version of HDCP is supported by the receiver device (display)", + "params": { + "type":"object", + "properties": { + "videoDisplay": { + "$ref": "#/definitions/videoDisplay" + } + }, + "required": [ + ] + }, + "result":{ + "type":"object", + "properties": { + "supportedRxHDCPVersion":{ + "$ref": "#/definitions/supportedRxHDCPVersion" + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required":[ + "supportedRxHDCPVersion", + "success" + ] + } + } + }, + "events": { + "onDisplayConnectionChanged":{ + "summary":"Triggered if HDMI was connected or disconnected upon receiving `onHdmiOutputHotPlug` event", + "params":{ + "type":"object", + "properties": { + "HDCPStatus":{ + "$ref": "#/definitions/HDCPStatus" + } + } + } + } + } +} diff --git a/LgiHdcpProfile/LgiHdcpProfilePlugin.json b/LgiHdcpProfile/LgiHdcpProfilePlugin.json new file mode 100644 index 0000000000..10a4e0cf99 --- /dev/null +++ b/LgiHdcpProfile/LgiHdcpProfilePlugin.json @@ -0,0 +1,14 @@ +{ + "$schema": "../../Thunder/Tools/JsonGenerator/schemas/plugin.schema.json", + "info": { + "title": "HdcpProfile Plugin", + "callsign": "com.lgi.rdk.HdcpProfile", + "locator": "libWPEFrameworkLgiHdcpProfile.so", + "status": "production", + "description": "The HdcpProfile plugin provides an interface for HDCP-related data and events.", + "version": "1.0" + }, + "interface": { + "$ref": "LgiHdcpProfile.json#" + } +} \ No newline at end of file diff --git a/LgiHdcpProfile/Module.cpp b/LgiHdcpProfile/Module.cpp new file mode 100644 index 0000000000..43183b6d7a --- /dev/null +++ b/LgiHdcpProfile/Module.cpp @@ -0,0 +1,23 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* Copyright 2021 Liberty Global Service B.V. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/LgiHdcpProfile/Module.h b/LgiHdcpProfile/Module.h new file mode 100644 index 0000000000..6898e545ce --- /dev/null +++ b/LgiHdcpProfile/Module.h @@ -0,0 +1,30 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* Copyright 2021 Liberty Global Service B.V. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once +#ifndef MODULE_NAME +#define MODULE_NAME LgiHdcpProfile +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/LgiHdcpProfile/README.md b/LgiHdcpProfile/README.md new file mode 100644 index 0000000000..f9e9cf3e5a --- /dev/null +++ b/LgiHdcpProfile/README.md @@ -0,0 +1,9 @@ +----------------- +Build: + +bitbake wpeframework-service-plugins + +----------------- +Test: + +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"3","method": "com.lgi.rdk.HdcpProfile.1."}' http://127.0.0.1:9998/jsonrpc diff --git a/LgiHdmiCec/CMakeLists.txt b/LgiHdmiCec/CMakeLists.txt new file mode 100644 index 0000000000..632de8ac30 --- /dev/null +++ b/LgiHdmiCec/CMakeLists.txt @@ -0,0 +1,46 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# Copyright 2021 Liberty Global Service B.V. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(PLUGIN_NAME LgiHdmiCec) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) + +find_package(${NAMESPACE}Plugins REQUIRED) + +add_library(${MODULE_NAME} SHARED + LgiHdmiCec.cpp + Module.cpp) + +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +find_package(DS) +find_package(IARMBus) +find_package(CEC) + +target_include_directories(${MODULE_NAME} PRIVATE ${IARMBUS_INCLUDE_DIRS} ../helpers) +target_include_directories(${MODULE_NAME} PRIVATE ${CEC_INCLUDE_DIRS}) +target_include_directories(${MODULE_NAME} PRIVATE ${DS_INCLUDE_DIRS}) + +target_link_libraries(${MODULE_NAME} PUBLIC ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${IARMBUS_LIBRARIES} ${CEC_LIBRARIES} ${DS_LIBRARIES} ${NAMESPACE}SecurityUtil) + + +install(TARGETS ${MODULE_NAME} + DESTINATION lib/${STORAGE_DIRECTORY}/plugins) + +write_config(${PLUGIN_NAME}) diff --git a/LgiHdmiCec/LgiHdmiCec.config b/LgiHdmiCec/LgiHdmiCec.config new file mode 100644 index 0000000000..d49588fdee --- /dev/null +++ b/LgiHdmiCec/LgiHdmiCec.config @@ -0,0 +1,11 @@ +set (autostart ${PLUGIN_LGIHDMICEC_AUTOSTART}) +set (preconditions Platform) +set (callsign "com.lgi.rdk.HdmiCec") +map() + key(root) + map() + kv(outofprocess ${PLUGIN_LGIHDMICEC_OUTOFPROCESS}) + end() +end() + +ans(configuration) diff --git a/LgiHdmiCec/LgiHdmiCec.cpp b/LgiHdmiCec/LgiHdmiCec.cpp new file mode 100644 index 0000000000..c48a485c2c --- /dev/null +++ b/LgiHdmiCec/LgiHdmiCec.cpp @@ -0,0 +1,1282 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* Copyright 2021 Liberty Global Service B.V. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include +#include + +#include "LgiHdmiCec.h" +#include "UtilsIarm.h" +#include "UtilsSynchro.hpp" +#include "UtilsSynchroIarm.hpp" + +#include "ccec/Connection.hpp" +#include "ccec/CECFrame.hpp" +#include "host.hpp" +#include "ccec/host/RDK.hpp" + +#include "ccec/drivers/iarmbus/CecIARMBusMgr.h" + + +#include "dsMgr.h" +#include "dsDisplay.h" +#include "videoOutputPort.hpp" +#include "manager.hpp" + +#include "websocket/URL.h" + +#include "UtilsJsonRpc.h" +#include "UtilssyncPersistFile.h" + +#define HDMICEC_METHOD_SET_ENABLED "setEnabled" +#define HDMICEC_METHOD_GET_ENABLED "getEnabled" +#define HDMICEC_METHOD_GET_CEC_ADDRESSES "getCECAddresses" +#define HDMICEC_METHOD_SEND_MESSAGE "sendMessage" +#define HDMICEC_METHOD_ENABLE_ONE_TOUCH_VIEW "enableOneTouchView" +#define HDMICEC_METHOD_TRIGGER_ACTION "triggerAction" +#define HDMICEC_METHOD_SET_PING_INTERVAL "setPingInterval" +#define HDMICEC_METHOD_GET_CONNECTED_DEVICES "getConnectedDevices" +#define HDMICEC_METHOD_SET_NAME "setName" +#define HDMICEC_METHOD_SET_ONE_TOUCH_VIEW_POLICY "setOneTouchViewPolicy" + +#define HDMICEC_EVENT_ON_DEVICES_CHANGED "onDevicesChanged" +#define HDMICEC_EVENT_ON_MESSAGE "onMessage" +#define HDMICEC_EVENT_ON_HDMI_HOT_PLUG "onHdmiHotPlug" +#define HDMICEC_EVENT_ON_CEC_ADDRESS_CHANGE "cecAddressesChanged" + +#define PHYSICAL_ADDR_CHANGED 1 +#define LOGICAL_ADDR_CHANGED 2 +#define DEV_TYPE_TUNER 1 + +#define HDMI_HOT_PLUG_EVENT_CONNECTED 0 +#define HDMI_HOT_PLUG_EVENT_DISCONNECTED 1 + +#if defined(HAS_PERSISTENT_IN_HDD) +#define CEC_SETTING_ENABLED_FILE "/tmp/mnt/diska3/persistent/ds/cecData.json" +#elif defined(HAS_PERSISTENT_IN_FLASH) +#define CEC_SETTING_ENABLED_FILE "/opt/persistent/ds/cecData.json" +#else +#define CEC_SETTING_ENABLED_FILE "/opt/ds/cecData.json" +#endif + +#define CEC_SETTING_ENABLED "cecEnabled" + +namespace +{ + using namespace WPEFramework; + + inline void requestRescanning(int id) + { + LOGINFO("requestRescanning: id=%d", id); + IARM_Bus_CECHost_ConfigureScan_Param_t param {}; + param.needFullUpdate = true; + param.scanReasonId = id; + + const IARM_Result_t ret = IARM_Bus_Call(IARM_BUS_CECHOST_NAME, + IARM_BUS_CEC_HOST_ConfigureScan, + (void *)¶m,sizeof(param)); + + if (IARM_RESULT_SUCCESS != ret) + { + LOGERR("%s failed result %d", IARM_BUS_CEC_HOST_ConfigureScan, ret); + } + } + inline void setOsdName(const string& name) + { + IARM_Bus_CECHost_SetOSDName_Param_t param {}; + + strncpy((char *)param.name, name.c_str(), sizeof(param.name)); + param.name[sizeof(param.name) - 1] = '\0'; + const IARM_Result_t ret = IARM_Bus_Call(IARM_BUS_CECHOST_NAME, + IARM_BUS_CEC_HOST_SetOSDName, + (void *)¶m, + sizeof(param)); + if (IARM_RESULT_SUCCESS != ret) + { + LOGERR("%s failed result %d", IARM_BUS_CEC_HOST_SetOSDName, ret); + } + } +} + +namespace WPEFramework +{ + namespace Plugin + { + SERVICE_REGISTRATION(LgiHdmiCec, 1, 0); + + LgiHdmiCec* LgiHdmiCec::_instance = nullptr; + + static std::atomic libcecInitStatus{0}; + + LgiHdmiCec::LgiHdmiCec() + : cecSettingEnabled(false),cecEnableStatus(false),smConnection(nullptr), + m_scan_id(0), m_updated(false), m_rescan_in_progress(true), m_system_audio_mode(false) + { + LgiHdmiCec::_instance = this; + + InitializeIARM(); + device::Manager::Initialize(); + + Utils::Synchro::RegisterLockedApi(HDMICEC_METHOD_SET_ENABLED, &LgiHdmiCec::setEnabledWrapper, this); + Utils::Synchro::RegisterLockedApi(HDMICEC_METHOD_GET_ENABLED, &LgiHdmiCec::getEnabledWrapper, this); + Utils::Synchro::RegisterLockedApi(HDMICEC_METHOD_GET_CEC_ADDRESSES, &LgiHdmiCec::getCECAddressesWrapper, this); + Utils::Synchro::RegisterLockedApi(HDMICEC_METHOD_SEND_MESSAGE, &LgiHdmiCec::sendMessageWrapper, this); + Utils::Synchro::RegisterLockedApi(HDMICEC_METHOD_ENABLE_ONE_TOUCH_VIEW, &LgiHdmiCec::enableOneTouchViewWrapper, this); + Utils::Synchro::RegisterLockedApi(HDMICEC_METHOD_TRIGGER_ACTION, &LgiHdmiCec::triggerActionWrapper, this); + Utils::Synchro::RegisterLockedApi(HDMICEC_METHOD_SET_PING_INTERVAL, &LgiHdmiCec::setPingIntervalWrapper, this); + Utils::Synchro::RegisterLockedApi(HDMICEC_METHOD_GET_CONNECTED_DEVICES, &LgiHdmiCec::getConnectedDevicesWrapper, this); + Utils::Synchro::RegisterLockedApi(HDMICEC_METHOD_SET_NAME, &LgiHdmiCec::setNameWrapper, this); + Utils::Synchro::RegisterLockedApi(HDMICEC_METHOD_SET_ONE_TOUCH_VIEW_POLICY, &LgiHdmiCec::setOneTouchViewPolicyWrapper, this); + + physicalAddress = 0x0F0F0F0F; + + logicalAddressDeviceType = "None"; + logicalAddress = 0xFF; + + loadSettings(); + if (cecSettingEnabled) + { + setEnabled(cecSettingEnabled); + requestRescanning(getNextScanId()); + } + else + { + setEnabled(false); + Utils::persistJsonSettings (CEC_SETTING_ENABLED_FILE, CEC_SETTING_ENABLED, JsonValue(false)); + } + } + + LgiHdmiCec::~LgiHdmiCec() + { + } + + void LgiHdmiCec::Deinitialize(PluginHost::IShell* /* service */) + { + CECDisable(); + LgiHdmiCec::_instance = nullptr; + + DeinitializeIARM(); + + } + + const void LgiHdmiCec::InitializeIARM() + { + if (Utils::IARM::init()) + { + IARM_Result_t res; + IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_CECMGR_NAME, IARM_BUS_CECMGR_EVENT_DAEMON_INITIALIZED,cecMgrEventHandler) ); + IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_CECMGR_NAME, IARM_BUS_CECMGR_EVENT_STATUS_UPDATED,cecMgrEventHandler) ); + IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG, dsHdmiEventHandler) ); + IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_CECHOST_NAME, IARM_BUS_CECHost_EVENT_DEVICESTATUSCHANGE, cecHostDeviceStatusChangedEventHandler) ); + IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_CECHOST_NAME, IARM_BUS_CECHost_EVENT_DEVICESTATUSUPDATEEND, cecHostDeviceStatusUpdateEndEventHandler) ); + IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_CECHOST_NAME, IARM_BUS_CECHost_EVENT_CECSTATUSCHANGE, cecHostCecStatusChange) ); + } + } + + void LgiHdmiCec::DeinitializeIARM() + { + if (Utils::IARM::isConnected()) + { + IARM_Result_t res; + IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_CECMGR_NAME, IARM_BUS_CECMGR_EVENT_DAEMON_INITIALIZED, cecMgrEventHandler) ); + IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_CECMGR_NAME, IARM_BUS_CECMGR_EVENT_STATUS_UPDATED, cecMgrEventHandler) ); + IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG, dsHdmiEventHandler) ); + IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_CECHOST_NAME, IARM_BUS_CECHost_EVENT_DEVICESTATUSCHANGE, cecHostDeviceStatusChangedEventHandler) ); + IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_CECHOST_NAME, IARM_BUS_CECHost_EVENT_DEVICESTATUSUPDATEEND, cecHostDeviceStatusUpdateEndEventHandler) ); + IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_CECHOST_NAME, IARM_BUS_CECHost_EVENT_CECSTATUSCHANGE, cecHostCecStatusChange) ); + } + } + + void LgiHdmiCec::cecMgrEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len) + { + if(!LgiHdmiCec::_instance) + return; + + if( !strncmp(owner, IARM_BUS_CECMGR_NAME, strlen(IARM_BUS_CECMGR_NAME) + 1)) + { + switch (eventId) + { + case IARM_BUS_CECMGR_EVENT_DAEMON_INITIALIZED: + { + LgiHdmiCec::_instance->onCECDaemonInit(); + } + break; + case IARM_BUS_CECMGR_EVENT_STATUS_UPDATED: + { + IARM_Bus_CECMgr_Status_Updated_Param_t *evtData = new IARM_Bus_CECMgr_Status_Updated_Param_t; + if(evtData) + { + memcpy(evtData,data,sizeof(IARM_Bus_CECMgr_Status_Updated_Param_t)); + LgiHdmiCec::_instance->cecStatusUpdated(evtData); + } + } + break; + default: + /*Do nothing*/ + break; + } + } + } + + void LgiHdmiCec::dsHdmiEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len) + { + if(!LgiHdmiCec::_instance) + return; + + if (IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG == eventId) + { + IARM_Bus_DSMgr_EventData_t *eventData = (IARM_Bus_DSMgr_EventData_t *)data; + int hdmi_hotplug_event = eventData->data.hdmi_hpd.event; + LOGINFO("Received IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG event data:%d \r\n", hdmi_hotplug_event); + + LgiHdmiCec::_instance->onHdmiHotPlug(hdmi_hotplug_event); + } + } + + void LgiHdmiCec::onCECDaemonInit() + { + if(true == getEnabled()) + { + setEnabled(false); + setEnabled(true); + } + else + { + /*Do nothing as CEC is not already enabled*/ + } + } + + void LgiHdmiCec::cecStatusUpdated(void *evtStatus) + { + IARM_Bus_CECMgr_Status_Updated_Param_t *evtData = (IARM_Bus_CECMgr_Status_Updated_Param_t *)evtStatus; + if(evtData) + { + try{ + getPhysicalAddress(); + + unsigned int logicalAddr = evtData->logicalAddress; + std::string logicalAddrDeviceType = DeviceType(LogicalAddress(evtData->logicalAddress).getType()).toString().c_str(); + + LOGWARN("cecLogicalAddressUpdated: logical address updated: %d , saved : %d ", logicalAddr, logicalAddress); + if (logicalAddr != logicalAddress || logicalAddrDeviceType != logicalAddressDeviceType) + { + logicalAddress = logicalAddr; + logicalAddressDeviceType = logicalAddrDeviceType; + cecAddressesChanged(LOGICAL_ADDR_CHANGED); + } + } + catch (const std::exception& e) + { + LOGWARN("CEC exception caught from cecStatusUpdated: %s", e.what()); + } + + delete evtData; + } + return; + } + + void LgiHdmiCec::onHdmiHotPlug(int connectStatus) + { + LOGINFO("Status %d cecEnableStatus: %d", connectStatus, cecEnableStatus.load()); + { + std::lock_guard guard(m_mutex); + m_devices.clear(); + m_scan_devices.clear(); + m_rescan_in_progress = true; + } + if ((HDMI_HOT_PLUG_EVENT_CONNECTED == connectStatus) && cecEnableStatus) + { + try + { + readAddresses(); + } + catch (const std::exception& e) + { + LOGWARN("CEC addresses not present: %s", e.what()); + } + requestRescanning(getNextScanId()); + } + if (HDMI_HOT_PLUG_EVENT_DISCONNECTED == connectStatus) + { + physicalAddress = 0x0F0F0F0F; + logicalAddress = 0xFF; + logicalAddressDeviceType = "None"; + cecAddressesChanged(PHYSICAL_ADDR_CHANGED); + //avoid race - we can receive update just after hotplug + // and before re-scan + m_updated = false; + + onDevicesChanged(); + } + return; + } + + uint32_t LgiHdmiCec::setEnabledWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + + const char *parameterName = "enabled"; + returnIfBooleanParamNotFound(parameters, parameterName); + bool enabled = false; + getBoolParameter(parameterName, enabled); + + setEnabled(enabled); + returnResponse(true); + } + + uint32_t LgiHdmiCec::getEnabledWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + + response["enabled"] = getEnabled(); + returnResponse(true); + } + + uint32_t LgiHdmiCec::getCECAddressesWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + + response["CECAddresses"] = getCECAddresses(); + + returnResponse(true); + } + + uint32_t LgiHdmiCec::sendMessageWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + + std::string message; + + if (parameters.HasLabel("message")) + { + message = parameters["message"].String(); + } + else + { + returnResponse(false); + } + + sendMessage(message); + returnResponse(true); + } + + uint32_t LgiHdmiCec::enableOneTouchViewWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + + const char *parameterName = "enabled"; + returnIfBooleanParamNotFound(parameters, parameterName); + + IARM_Bus_CECHost_EnableOneTouchView_Param_t param; + getBoolParameter(parameterName, param.enableOneTouchView); + + const IARM_Result_t ret = IARM_Bus_Call(IARM_BUS_CECHOST_NAME, + (char *)IARM_BUS_CEC_HOST_EnableOneTouchView, + (void *)¶m, sizeof(param)); + if (ret != IARM_RESULT_SUCCESS) + { + LOGERR("Enabling one touch view failed."); + returnResponse(false); + } + + LOGINFO("Successfully %s one touch view.", + param.enableOneTouchView ? "enabled": "disabled"); + returnResponse(true); + } + + uint32_t LgiHdmiCec::triggerActionWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + + const char *parameterName = "actionName"; + returnIfStringParamNotFound(parameters, parameterName); + + string actionName; + getStringParameter(parameterName, actionName); + + LOGINFO("CEC: %s Triggering action[%s]\n", __FUNCTION__, actionName.c_str()); + + IARM_Bus_CECHost_TriggerAction_Param_t param; + strncpy(param.name, + actionName.c_str(), + sizeof(param.name)); + param.name[sizeof(param.name) - 1] = '\0'; + param.destination = 0x0F; + + const IARM_Result_t ret = IARM_Bus_Call(IARM_BUS_CECHOST_NAME, + IARM_BUS_CEC_HOST_TriggerAction, + static_cast(¶m), sizeof(param)); + + if (IARM_RESULT_SUCCESS != ret) + { + LOGERR("CEC: ERROR - %s CALL[%s], ACTION[%s] failed result %d\n", + __FUNCTION__, + IARM_BUS_CEC_HOST_TriggerAction, + actionName.c_str(), + ret); + returnResponse(false); + } + + LOGINFO("CEC: SUCCESS - %s CALL[%s], ACTION[%s]\n", __FUNCTION__, IARM_BUS_CEC_HOST_TriggerAction, + actionName.c_str()); + + returnResponse(true); + } + + uint32_t LgiHdmiCec::setPingIntervalWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + + const char *parameterName = "intervalSeconds"; + + returnIfNumberParamNotFound(parameters, parameterName); + + IARM_Bus_CECHost_SetScanInterval_Param_t param; + getNumberParameter(parameterName, param.intervalSeconds); + + const IARM_Result_t ret = IARM_Bus_Call(IARM_BUS_CECHOST_NAME, + IARM_BUS_CEC_HOST_SetScanInterval, + static_cast(¶m), + sizeof(param)); + if (IARM_RESULT_SUCCESS != ret) + { + LOGERR("CEC: ERROR - %s %s failed result %d\n", + __FUNCTION__, IARM_BUS_CEC_HOST_SetScanInterval, ret); + returnResponse(false); + } + LOGINFO("CEC: %s interval %d succeed\n", __FUNCTION__, param.intervalSeconds); + + returnResponse(true); + } + + void LgiHdmiCec::onDevicesChanged() + { + LOGINFO(); + + JsonArray deviceList; + getConnectedDevices(deviceList); + JsonObject parameters; + parameters["devices"] = deviceList; + sendNotify(HDMICEC_EVENT_ON_DEVICES_CHANGED, parameters); + } + + bool LgiHdmiCec::getConnectedDevices(JsonArray &deviceList) + { + LOGINFO(); + bool connected = false; + try + { + device::VideoOutputPort vPort = device::Host::getInstance().getVideoOutputPort("HDMI0"); + connected = vPort.isDisplayConnected(); + } + catch (const std::exception& e) + { + LOGWARN("Checking HDMI0 display connection state failed: %s", e.what()); + } + + if (!connected) + { + LOGINFO("HDMI disconnected - empty devices list"); + return false; + } + + try + { + std::lock_guard guard(m_mutex); + for (m_devices_map_t::iterator itr = m_devices.begin(); itr != m_devices.end();++itr) + { + JsonObject device; + device["vendorId"] = (*itr).second.vendor_id; + device["osdName"] = (*itr).second.osdName; + device["power"] = (*itr).second.power_state; + device["connected"] = (*itr).second.connected; + device["device"] = (*itr).first; + deviceList.Add(device); + LOGINFO("CEC: added device: vendorid '%s' name '%s' power %d conn %d dev '%s'\n", + (*itr).second.vendor_id.c_str(), + (*itr).second.osdName.c_str(), + static_cast((*itr).second.power_state), + static_cast((*itr).second.connected), + (*itr).first.c_str()); + } + } + catch (const std::exception& e) + { + LOGWARN("failed: %s", e.what()); + return false; + } + return true; + } + + uint32_t LgiHdmiCec::getConnectedDevicesWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + + JsonArray deviceList; + + bool retval = false; + + if(cecEnableStatus == true) + { + retval = getConnectedDevices(deviceList); + // force sending onDeviceChanged event on next scan end + m_updated = (m_updated || (deviceList.IsNull() && m_rescan_in_progress)); + } + else + { + LOGWARN("CEC: %s not called - CEC disabled\n", __FUNCTION__); + // ARRISAPOL-2345: we don't treat this case as error + retval = true; + } + response["devices"] = deviceList; + response["systemAudioMode"] = static_cast(m_system_audio_mode); + + returnResponse(retval); + } + + uint32_t LgiHdmiCec::setNameWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + + string name; + + returnIfStringParamNotFound(parameters, "name"); + getStringParameter("name", name); + + LOGINFO("%s", name.c_str()); + + try + { + setOsdName(name); + } + catch (const std::exception& e) + { + LOGWARN("failed: %s", e.what()); + returnResponse(false); + } + returnResponse(true); + } + + uint32_t LgiHdmiCec::setOneTouchViewPolicyWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + + returnIfBooleanParamNotFound(parameters, "turnOffAllDevices"); + bool turnOffAllDevices = true; + getBoolParameter("turnOffAllDevices", turnOffAllDevices); + + LOGINFO("turnOffDevices[%s]\n", turnOffAllDevices?"TRUE":"FALSE"); + + IARM_Bus_CECHost_SetOneTouchViewPolicy_Param_t param; + param.turnOffAllDevices = turnOffAllDevices; + const IARM_Result_t ret = IARM_Bus_Call(IARM_BUS_CECHOST_NAME, + IARM_BUS_CEC_HOST_SetOneTouchViewPolicy, + static_cast(¶m), + sizeof(param)); + + if (IARM_RESULT_SUCCESS != ret) + { + LOGERR("%s failed result %d\n", + IARM_BUS_CEC_HOST_SetOneTouchViewPolicy, + ret); + returnResponse(false); + } + + LOGINFO("SUCCESS - %s\n", IARM_BUS_CEC_HOST_SetOneTouchViewPolicy); + returnResponse(true); + } + + bool LgiHdmiCec::loadSettings() + { + Core::File file; + file = CEC_SETTING_ENABLED_FILE; + + if (!file.Open()) + { + return false; + } + + JsonObject parameters; + parameters.IElement::FromFile(file); + + file.Close(); + + getBoolParameter(CEC_SETTING_ENABLED, cecSettingEnabled); + + return cecSettingEnabled; + } + + void LgiHdmiCec::setEnabled(bool enabled) + { + LOGWARN("Entered setEnabled "); + + if (cecSettingEnabled != enabled) + { + Utils::persistJsonSettings (CEC_SETTING_ENABLED_FILE, CEC_SETTING_ENABLED, JsonValue(enabled)); + } + if(true == enabled) + { + CECEnable(); + } + else + { + CECDisable(); + } + return; + } + + void LgiHdmiCec::CECEnable(void) + { + LOGWARN("Entered CECEnable"); + if (cecEnableStatus) + { + LOGWARN("CEC Already Enabled"); + return; + } + + if(0 == libcecInitStatus) + { + try + { + LibCCEC::getInstance().init(); + } + catch (const std::exception e) + { + LOGWARN("CEC exception caught from CECEnable"); + } + } + libcecInitStatus++; + + smConnection = new Connection(LogicalAddress::UNREGISTERED,false,"ServiceManager::Connection::"); + smConnection->open(); + smConnection->addFrameListener(this); + + //Acquire CEC Addresses + getPhysicalAddress(); + getLogicalAddress(); + + cecEnableStatus = true; + + m_rescan_in_progress = true; + requestRescanning(getNextScanId()); + + return; + } + + void LgiHdmiCec::CECDisable(void) + { + LOGWARN("Entered CECDisable "); + + if(!cecEnableStatus) + { + LOGWARN("CEC Already Disabled "); + return; + } + + m_devices.clear(); + m_scan_devices.clear(); + m_rescan_in_progress = false; + m_scan_id = 0; + + if (smConnection != NULL) + { + smConnection->close(); + delete smConnection; + smConnection = NULL; + } + cecEnableStatus = false; + + if(1 == libcecInitStatus) + { + LibCCEC::getInstance().term(); + } + + if(libcecInitStatus > 0) + { + libcecInitStatus--; + } + + return; + } + + void LgiHdmiCec::readAddresses() + { + LOGINFO(); + + try + { + device::VideoOutputPort vPort = device::Host::getInstance().getVideoOutputPort("HDMI0"); + if (vPort.isDisplayConnected()) + { + if (getPhysicalAddress() || getLogicalAddress()) + { + cecAddressesChanged(PHYSICAL_ADDR_CHANGED); + } + } + } + catch (const std::exception& e) + { + LOGWARN("exception caught: %s", e.what()); + throw; + } + } + + bool LgiHdmiCec::getPhysicalAddress() + { + bool changed = false; + LOGINFO("Entered getPhysicalAddress "); + + uint32_t physAddress = 0x0F0F0F0F; + + try { + LibCCEC::getInstance().getPhysicalAddress(&physAddress); + + LOGINFO("getPhysicalAddress: physicalAddress: %x %x %x %x ", (physAddress >> 24) & 0xFF, (physAddress >> 16) & 0xFF, (physAddress >> 8) & 0xFF, (physAddress) & 0xFF); + if (physAddress != physicalAddress) + { + physicalAddress = physAddress; + changed = true; + } + } + catch (const std::exception& e) + { + LOGWARN("DS exception caught from getPhysicalAddress: %s", e.what()); + throw; + } + return changed; + } + + bool LgiHdmiCec::getLogicalAddress() + { + LOGINFO("Entered getLogicalAddress "); + bool changed = false; + + try{ + int addr = LibCCEC::getInstance().getLogicalAddress(DEV_TYPE_TUNER); + + std::string logicalAddrDeviceType = DeviceType(LogicalAddress(addr).getType()).toString().c_str(); + + LOGWARN("logical address obtained is %d , saved logical address is %d ", addr, logicalAddress); + + if ((int)logicalAddress != addr || logicalAddressDeviceType != logicalAddrDeviceType) + + { + logicalAddress = addr; + logicalAddressDeviceType = logicalAddrDeviceType; + changed = true; + } + } + catch (const std::exception& e) + { + LOGWARN("CEC exception caught from getLogicalAddress: %s", e.what()); + throw; + } + + return changed; + } + + bool LgiHdmiCec::getEnabled() + { + LOGWARN("Entered getEnabled "); + if(true == cecEnableStatus) + return true; + else + return false; + } + + std::string LgiHdmiCec::getName() + { + //SVCLOG_WARN("%s \r\n",__FUNCTION__); + IARM_Result_t ret = IARM_RESULT_INVALID_STATE; + if (ret != IARM_RESULT_SUCCESS) + { + LOGWARN("getName :: IARM_BUS_CEC_HOST_GetOSDName failed "); + return "STB"; + } + + return "STB"; + } + + JsonObject LgiHdmiCec::getCECAddresses() + { + JsonObject CECAddress; + LOGINFO("Entered getCECAddresses "); + + char pa[32] = {0}; + snprintf(pa, sizeof(pa), "\\u00%02X\\u00%02X\\u00%02X\\u00%02X", (physicalAddress >> 24) & 0xff, (physicalAddress >> 16) & 0xff, (physicalAddress >> 8) & 0xff, physicalAddress & 0xff); + + CECAddress["physicalAddress"] = (const char *)pa; + + JsonObject logical; + logical["deviceType"] = logicalAddressDeviceType; + logical["logicalAddress"] = logicalAddress; + + CECAddress["logicalAddresses"] = logical; + LOGWARN("getCECAddresses: physicalAddress from QByteArray : %x %x %x %x ", (physicalAddress >> 24) & 0xFF, (physicalAddress >> 16) & 0xFF, (physicalAddress >> 8) & 0xFF, (physicalAddress) & 0xFF); + LOGWARN("getCECAddresses: logical address: %x ", logicalAddress); + + return CECAddress; + } + + // Copy of Core::FromString, which doesn't add extra zero at the end + uint16_t LgiHdmiCec::FromBase64String(const string& newValue, uint8_t object[], uint16_t& length, const TCHAR* ignoreList) + { + uint8_t state = 0; + uint16_t index = 0; + uint16_t filler = 0; + uint8_t lastStuff = 0; + + while ((index < newValue.size()) && (filler < length)) { + uint8_t converted; + TCHAR current = newValue[index++]; + + if ((current >= 'A') && (current <= 'Z')) { + converted = (current - 'A'); + } else if ((current >= 'a') && (current <= 'z')) { + converted = (current - 'a' + 26); + } else if ((current >= '0') && (current <= '9')) { + converted = (current - '0' + 52); + } else if (current == '+') { + converted = 62; + } else if (current == '/') { + converted = 63; + } else if ((ignoreList != nullptr) && (::strchr(ignoreList, current) != nullptr)) { + continue; + } else { + break; + } + + if (state == 0) { + lastStuff = converted << 2; + state = 1; + } else if (state == 1) { + object[filler++] = (((converted & 0x30) >> 4) | lastStuff); + lastStuff = ((converted & 0x0F) << 4); + state = 2; + } else if (state == 2) { + object[filler++] = (((converted & 0x3C) >> 2) | lastStuff); + lastStuff = ((converted & 0x03) << 6); + state = 3; + } else if (state == 3) { + object[filler++] = ((converted & 0x3F) | lastStuff); + state = 0; + } + } + + // No need to do this + /*if ((state != 0) && (filler < length)) { + object[filler++] = lastStuff; + LOGINFO("state %d, lastStuff = %d", state, lastStuff); + }*/ + + length = filler; + + return (index); + } + + void LgiHdmiCec::sendMessage(std::string message) + { + LOGINFO("sendMessage "); + + if(true == cecEnableStatus) + { + std::vector buf; + buf.resize(message.size()); + + uint16_t decodedLen = message.size(); + FromBase64String(message, (uint8_t*)buf.data(), decodedLen, NULL); + + CECFrame frame = CECFrame((const uint8_t *)buf.data(), decodedLen); + // SVCLOG_WARN("Frame to be sent from servicemanager in %s \n",__FUNCTION__); + // frame.hexDump(); + smConnection->sendAsync(frame); + } + else + LOGWARN("cecEnableStatus=false"); + return; + } + + void LgiHdmiCec::cecAddressesChanged(int changeStatus) + { + JsonObject params; + JsonObject CECAddresses; + + LOGWARN(" cecAddressesChanged Change Status : %d ", changeStatus); + if(PHYSICAL_ADDR_CHANGED == changeStatus) + { + char pa[32] = {0}; + snprintf(pa, sizeof(pa), "\\u00%02X\\u00%02X\\u00%02X\\u00%02X", (physicalAddress >> 24) & 0xff, (physicalAddress >> 16) & 0xff, (physicalAddress >> 8) & 0xff, physicalAddress & 0xff); + + CECAddresses["physicalAddress"] = (const char *)pa; + } + else if(LOGICAL_ADDR_CHANGED == changeStatus) + { + CECAddresses["logicalAddresses"] = logicalAddress; + } + else + { + //Do Nothing + } + + params["CECAddresses"] = CECAddresses; + LOGWARN(" cecAddressesChanged send : %s ", HDMICEC_EVENT_ON_CEC_ADDRESS_CHANGE); + + sendNotify(HDMICEC_EVENT_ON_CEC_ADDRESS_CHANGE, params); + + return; + } + + void LgiHdmiCec::notify(const CECFrame &in) const + { + LOGINFO("Inside notify "); + size_t length; + const uint8_t *input_frameBuf = NULL; + CECFrame Frame = in; + // SVCLOG_WARN("Frame received by servicemanager is \n"); + // Frame.hexDump(); + Frame.getBuffer(&input_frameBuf,&length); + + std::vector buf; + // base64 encoded string uses 4 characters for every 3 bytes (using padding if necessary) - so assume padding is used + // Base64Encode doesn't seem to use base64 padding right now, but better to be future-proof + size_t required_buffer_size = 4 * (length / 3); + if (length % 3 != 0) required_buffer_size += 4; + buf.resize(required_buffer_size + 1); // +1 for null terminator + + uint16_t encodedLen = Core::URL::Base64Encode(input_frameBuf, length, buf.data(), buf.size()); + buf[encodedLen] = 0; + + (const_cast(this))->onMessage(buf.data()); + return; + } + + void LgiHdmiCec::onMessage( const char *message ) + { + JsonObject params; + params["message"] = message; + sendNotify(HDMICEC_EVENT_ON_MESSAGE, params); + } + + void LgiHdmiCec::onDeviceStatusChanged(IARM_EventId_t eventId, const void* data_ptr, size_t len) + { + assert(data_ptr != NULL); + if (!cecEnableStatus || len < sizeof(IARM_Bus_CECHost_DeviceStatusChanged_EventData_t)) + { + return; + } + const IARM_Bus_CECHost_DeviceStatusChanged_EventData_t* eData = static_cast(data_ptr); + + LOGINFO("id=%d, len=%u", (int)eventId, (unsigned)len); + + if ((eData->logicalAddress == LogicalAddress::TV) || (eData->logicalAddress == LogicalAddress::AUDIO_SYSTEM)) + { // the scanner may send events for other devices, so skip them ... + bool update = false; + try + { + readAddresses(); + + switch(eData->changedStatus) + { + case IARM_BUS_CECHost_OSD_NAME: + { + LOGINFO("IARM_BUS_CECHost_OSD_NAME for device %d name %s", eData->logicalAddress, eData->data.osdName); + update = setChangedDeviceOsdName(eData->data.osdName, eData->logicalAddress); + break; + } + case IARM_BUS_CECHost_VENDOR_ID: + { + LOGINFO("IARM_BUS_CECHost_VENDOR_ID for device %d vendor id 0x%x", eData->logicalAddress, eData->data.vendorId); + update = setChangedDeviceVendorId(eData->data.vendorId, eData->logicalAddress); + break; + } + case IARM_BUS_CECHost_POWER_STATUS: + { + LOGINFO("IARM_BUS_CECHost_POWER_STATUS for device %d powerState %d", eData->logicalAddress, eData->data.powerState); + update = setChangedDevicePowerState(eData->data.powerState, eData->logicalAddress); + break; + } + case IARM_BUS_CECHost_CONNECT_STATUS: + { + LOGINFO("IARM_BUS_CECHost_CONNECT_STATUS for device %d isConnected %d", eData->logicalAddress, eData->data.isConnected); + update = setChangedDeviceConnectedState(eData->data.isConnected, eData->logicalAddress); + break; + } + case IARM_BUS_CECHost_AUDIO_MODE: + { + LOGINFO("IARM_BUS_CECHost_AUDIO_MODE systemAudioMode %d\n", eData->data.systemAudioMode); + update = setChangedDeviceSystemAudioMode(eData->data.systemAudioMode); + break; + } + default: + { + LOGWARN("Unsupported event IARM_BUS_CECHost %d !!!!!", eData->changedStatus); + break; + } + } + } + catch (const std::exception& e) + { + LOGERR("exception caught"); + } + if (update) + { + m_updated = true; + } + } + else + { + LOGWARN("skipped IARM event %d for unknown device %d", eData->changedStatus, eData->logicalAddress); + } + } + + void LgiHdmiCec::cecHostDeviceStatusChangedEventHandler(const char* owner_str, IARM_EventId_t eventId, void* data_ptr, size_t len) + { + LOGINFO("owner=%s", owner_str? owner_str: "<>"); + + if (!LgiHdmiCec::_instance) + return; + + if (data_ptr && owner_str + && (eventId == IARM_BUS_CECHost_EVENT_DEVICESTATUSCHANGE) + && (strncmp(owner_str, IARM_BUS_CECHOST_NAME, strlen(IARM_BUS_CECHOST_NAME) + 1) == 0)) + { + _instance->onDeviceStatusChanged(eventId, data_ptr, len); + } + } + + void LgiHdmiCec::onDeviceStatusUpdateEnd(IARM_EventId_t eventId, const void* data_ptr, size_t len) + { + assert(data_ptr != NULL); + if (len < sizeof(IARM_Bus_CECHost_DeviceStatusUpdateEnd_EventData_t)) + { + return; + } + const IARM_Bus_CECHost_DeviceStatusUpdateEnd_EventData_t* eData = static_cast(data_ptr); + + LOGINFO("scanId %d(%d) scan finished %d", eData->scanId, m_scan_id.load(), eData->isScanFinished); + + if (eData->isScanFinished != 1) + { + // note that negative scan ids normally mean the scans automatically started by CecScanner: + // -4 for 'SCAN_ID_AUTO', -2 for 'SCAN_ID_AUTO_HOTPLUG_OUT', -1 for 'SCAN_ID_AUTO_HOTPLUG_IN' + LOGWARN("scan corrupted scanId %d(%d) scan finished %d", eData->scanId, m_scan_id.load(), eData->isScanFinished); + return; + } + + if (m_scan_id != eData->scanId) + { + // note that negative scan ids normally mean the scans automatically started by CecScanner: + // -4 for 'SCAN_ID_AUTO', -2 for 'SCAN_ID_AUTO_HOTPLUG_OUT', -1 for 'SCAN_ID_AUTO_HOTPLUG_IN' + LOGWARN("skipped on invalid scan_id %d(%d) scan finished %d", eData->scanId, m_scan_id.load(), eData->isScanFinished); + } + else + { + if (m_updated) + { + LOGWARN("Update OK, finished (%d) scanId %d(%d)", eData->isScanFinished, eData->scanId, m_scan_id.load()); + m_updated = false; + { + std::lock_guard guard(m_mutex); + m_devices = m_scan_devices; + m_rescan_in_progress = false; + } + onDevicesChanged(); + } + else + { + m_rescan_in_progress = false; + LOGWARN("skipped: no changes on devices (empty callback)"); + } + } + // m_scan_id = 0; ARRISAPOL-2697: previous corrupted/invalid scan id cases were invalidating the current scan id + // ARRISAPOL-2697: we can just keep on increasing the scan id, it should actually lower the chances of scan id collision + } + + void LgiHdmiCec::cecHostDeviceStatusUpdateEndEventHandler(const char* owner_str, IARM_EventId_t eventId, void* data_ptr, size_t len) + { + LOGINFO("owner=%s", owner_str? owner_str: "<>"); + + if (!LgiHdmiCec::_instance) + return; + + if (data_ptr && owner_str + && (strncmp(owner_str, IARM_BUS_CECHOST_NAME, strlen(IARM_BUS_CECHOST_NAME) + 1) == 0)) + { + _instance->onDeviceStatusUpdateEnd(eventId, data_ptr, len); + } + } + + void LgiHdmiCec::onCecStatusChange(IARM_EventId_t eventId, const void* data_ptr, size_t len) + { + assert(data_ptr != NULL); + if (len < sizeof(IARM_Bus_CECHost_DevMgrStatus_Param_t)) + { + return; + } + + const IARM_Bus_CECHost_DevMgrStatus_Param_t* eData = static_cast(data_ptr); + + LOGINFO("status=%s-%s\n", eData->status ? "true" : "false", cecEnableStatus ? "true" : "false"); + std::lock_guard guard(m_mutex); + if (cecEnableStatus != eData->status) + { + cecEnableStatus = eData->status; + if (cecEnableStatus == false) + { + m_devices.clear(); + m_scan_devices.clear(); + m_rescan_in_progress = false; + } + else + { + m_rescan_in_progress = true; + requestRescanning(getNextScanId()); + } + } + } + + void LgiHdmiCec::cecHostCecStatusChange(const char *owner_str, IARM_EventId_t eventId, void* data_ptr, size_t len) + { + LOGINFO("owner=%s", owner_str? owner_str: "<>"); + + if (!LgiHdmiCec::_instance) + return; + + if (data_ptr && owner_str + && (strncmp(owner_str, IARM_BUS_CECHOST_NAME, strlen(IARM_BUS_CECHOST_NAME) + 1) == 0)) + { + LgiHdmiCec::_instance->onCecStatusChange(eventId, data_ptr, len); + } + } + + bool LgiHdmiCec::setChangedDeviceOsdName(const char* name, int logical_address) + { + assert(name != NULL); + + bool result = false; + + LOGINFO("OSD name: '%s' set for device %d", name, logical_address); + + if ((logical_address >= LogicalAddress::TV) && (logical_address < LogicalAddress::UNREGISTERED)) + { + string dev_type = LogicalAddress(logical_address).toString(); + std::lock_guard guard(m_mutex); + device_t& device = m_scan_devices[dev_type]; + + result = (device.osdName!= name); + device.osdName = name; + LOGINFO("CEC: OSD NAME:'%s' set for device %s", name, dev_type.c_str()); + } + else + { + LOGWARN("CEC: device osdName failed - invalid logical address %d", logical_address); + } + return result; + } + + bool LgiHdmiCec::setChangedDeviceVendorId(uint32_t vendor_id, int logical_address) + { + bool result = false; + + LOGWARN("VendorID: '%u' set for device %d", vendor_id, logical_address); + + string dev_type = LogicalAddress(logical_address).toString(); + std::lock_guard guard(m_mutex); + device_t& device = m_scan_devices[dev_type]; + std::ostringstream vendor_id_conv; + vendor_id_conv << std::hex << vendor_id; + string vendor = vendor_id_conv.str(); + + if (device.vendor_id != vendor) + { + device.vendor_id = vendor; + LOGWARN("set vendor id %s for device %d", device.vendor_id.c_str(), logical_address); + } + else + { + LOGINFO("skipped device update (the same vendor id 0x%x) for device %d", vendor_id, logical_address); + } + + return result; + } + + bool LgiHdmiCec::setChangedDeviceConnectedState(int connected, int logical_address) + { + bool result = false; + + LOGINFO("connected: %d set for device %d", connected, logical_address); + + string dev_type = LogicalAddress(logical_address).toString(); + std::lock_guard guard(m_mutex); + device_t& device = m_scan_devices[dev_type]; + + result = device.connected != ((connected != 0) ? true : false); + if (result) + { + device.connected = (connected != 0); + LOGWARN("set connected %d set for %s", connected, dev_type.c_str()); + } + + return result; + } + + bool LgiHdmiCec::setChangedDevicePowerState(int power_state, int logical_address) + { + bool result = false; + + LOGINFO("power_state: %d set for device %d", power_state, logical_address); + + string dev_type = LogicalAddress(logical_address).toString(); + std::lock_guard guard(m_mutex); + device_t& device = m_scan_devices[dev_type]; + + result = (device.power_state != ((power_state != 0) ? true : false)); + + if (result) + { + device.power_state = (power_state != 0); + LOGWARN("set power_state %d for %s", power_state, dev_type.c_str()); + } + + return result; + } + + bool LgiHdmiCec::setChangedDeviceSystemAudioMode(int system_audio_mode) + { + LOGINFO("system_audio_mode: %d\n", system_audio_mode); + + bool old_value = m_system_audio_mode; + m_system_audio_mode = (system_audio_mode != 0); + + return old_value != m_system_audio_mode; + } + + int LgiHdmiCec::getNextScanId() { + do { + int expected = m_scan_id.load(); + int desired = (expected + 1) % std::numeric_limits::max(); + if (m_scan_id.compare_exchange_strong(expected, desired)) return desired; + } while (true); + } + + } // namespace Plugin +} // namespace WPEFramework + + + diff --git a/LgiHdmiCec/LgiHdmiCec.h b/LgiHdmiCec/LgiHdmiCec.h new file mode 100644 index 0000000000..3a0f5821c2 --- /dev/null +++ b/LgiHdmiCec/LgiHdmiCec.h @@ -0,0 +1,161 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* Copyright 2021 Liberty Global Service B.V. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once + +#include +#include +#include +#include +#include "ccec/FrameListener.hpp" +#include "ccec/Connection.hpp" + +#include "libIBus.h" + +#undef Assert // this define from Connection.hpp conflicts with WPEFramework + +#include "Module.h" +#include "UtilsJsonRpc.h" + +namespace WPEFramework { + + namespace Plugin { + + // This is a server for a JSONRPC communication channel. + // For a plugin to be capable to handle JSONRPC, inherit from PluginHost::JSONRPC. + // By inheriting from this class, the plugin realizes the interface PluginHost::IDispatcher. + // This realization of this interface implements, by default, the following methods on this plugin + // - exists + // - register + // - unregister + // Any other methood to be handled by this plugin can be added can be added by using the + // templated methods Register on the PluginHost::JSONRPC class. + // As the registration/unregistration of notifications is realized by the class PluginHost::JSONRPC, + // this class exposes a public method called, Notify(), using this methods, all subscribed clients + // will receive a JSONRPC message as a notification, in case this method is called. + class LgiHdmiCec : public PluginHost::IPlugin, public PluginHost::JSONRPC , public FrameListener { + private: + + // We do not allow this plugin to be copied !! + LgiHdmiCec(const LgiHdmiCec&) = delete; + LgiHdmiCec& operator=(const LgiHdmiCec&) = delete; + + //Begin methods + uint32_t setEnabledWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t getEnabledWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t getCECAddressesWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t sendMessageWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t enableOneTouchViewWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t triggerActionWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t setPingIntervalWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t getConnectedDevicesWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t setNameWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t setOneTouchViewPolicyWrapper(const JsonObject& parameters, JsonObject& response); + //End methods + public: + LgiHdmiCec(); + virtual ~LgiHdmiCec(); + virtual void Deinitialize(PluginHost::IShell* service) override; + virtual const string Initialize(PluginHost::IShell* service) override {return {};} + virtual string Information() const override { return {}; } + int getNextScanId(); + + //Build QueryInterface implementation, specifying all possible interfaces to be returned. + BEGIN_INTERFACE_MAP(Network) + INTERFACE_ENTRY(PluginHost::IPlugin) + INTERFACE_ENTRY(PluginHost::IDispatcher) + END_INTERFACE_MAP + + public: + static LgiHdmiCec* _instance; + private: + std::string logicalAddressDeviceType; + unsigned int logicalAddress; + unsigned int physicalAddress; + std::atomic_bool cecSettingEnabled; + std::atomic_bool cecEnableStatus; + Connection *smConnection; + + const void InitializeIARM(); + void DeinitializeIARM(); + static void cecMgrEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len); + static void dsHdmiEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len); + static void cecHostDeviceStatusChangedEventHandler(const char* owner_str, IARM_EventId_t eventId, void* data_ptr, size_t len); + static void cecHostDeviceStatusUpdateEndEventHandler(const char* owner_str, IARM_EventId_t eventId, void* data_ptr, size_t len); + static void cecHostCecStatusChange(const char* owner_str, IARM_EventId_t eventId, void* data_ptr, size_t len); + void onCECDaemonInit(); + void cecStatusUpdated(void *evtStatus); + void onHdmiHotPlug(int connectStatus); + void onDeviceStatusChanged(IARM_EventId_t eventId, const void* data_ptr, size_t len); + void onDeviceStatusUpdateEnd(IARM_EventId_t eventId, const void* data_ptr, size_t len); + void onDevicesChanged(); + void onCecStatusChange(IARM_EventId_t eventId, const void* data_ptr, size_t len); + + bool getConnectedDevices(JsonArray &deviceList); + + bool setChangedDeviceOsdName(const char* name, int logical_address); + bool setChangedDeviceVendorId(uint32_t vendor_id, int logical_address); + bool setChangedDeviceConnectedState(int connected, int logical_address); + bool setChangedDevicePowerState(int power_state, int logical_address); + bool setChangedDeviceSystemAudioMode(int system_audio_mode); + + bool loadSettings(); + + void persistSettings(bool enableStatus); + void setEnabled(bool enabled); + void CECEnable(void); + void CECDisable(void); + bool getPhysicalAddress(); + bool getLogicalAddress(); + void readAddresses(); + bool getEnabled(); + std::string getName(); + JsonObject getCECAddresses(); + + uint16_t FromBase64String(const string& newValue, uint8_t object[], uint16_t& length, const TCHAR* ignoreList); + void sendMessage(std::string message); + void cecAddressesChanged(int changeStatus); + + void notify(const CECFrame &in) const; + void onMessage(const char *message); + + std::atomic m_scan_id; + std::atomic_bool m_updated; + std::atomic_bool m_rescan_in_progress; + std::atomic_bool m_system_audio_mode; + + typedef struct device_s + { + std::string physical_address; + std::string osdName; + std::string vendor_id; + bool connected = false; + bool power_state = true; + } device_t; + + typedef std::map m_devices_map_t; + m_devices_map_t m_devices; + m_devices_map_t m_scan_devices; + std::mutex m_mutex; + }; + } // namespace Plugin +} // namespace WPEFramework + + diff --git a/LgiHdmiCec/LgiHdmiCec.json b/LgiHdmiCec/LgiHdmiCec.json new file mode 100644 index 0000000000..6121ba5980 --- /dev/null +++ b/LgiHdmiCec/LgiHdmiCec.json @@ -0,0 +1,369 @@ +{ + "$schema": "https://raw.githubusercontent.com/rdkcentral/Thunder/master/Tools/JsonGenerator/schemas/interface.schema.json", + "jsonrpc": "2.0", + "info": { + "title": "LgiHdmiCec API", + "class": "com.lgi.rdk.HdmiCec", + "description": "The `LgiHdmiCec` plugin allows you to configure HDMI Consumer Electronics Control (CEC) on a set-top box" + }, + "definitions": { + "enabled": { + "summary": "Indicates whether HDMI-CEC is enabled (`true`) or disabled (`false`)", + "type":"boolean", + "example": false + }, + "physicalAddress":{ + "summary": "The physical IP address of the device", + "type": "string", + "example": "255, 255, 255, 255" + }, + "actionName":{ + "summary": "Action name: POWER_ON or POWER_OFF.", + "type": "string", + "example": "POWER_ON" + }, + "intervalSeconds":{ + "summary": "CEC ping interval in seconds.", + "type": "number", + "example": 5 + }, + "devices":{ + "summary": "Object [] of information about each device", + "type":"array", + "items": { + "type": "object", + "properties": { + "vendorId": { + "summary": "Vendor ID of the device", + "type":"string", + "example": "0ce7" + }, + "osdName": { + "summary": "OSD name of the device if available", + "type":"string", + "example": "Fire TV Stick" + }, + "power": { + "summary": "Power state of device", + "type": "boolean", + "example": false + }, + "connected": { + "summary": "Indicates whether HDMI-CEC device is connected", + "type":"boolean", + "example": false + }, + "device": { + "summary": "Device type", + "type":"string", + "example": "Playback Device" + } + }, + "required": [ + "vendorId", + "osdName", + "power", + "connected", + "device" + ] + } + }, + "systemAudioMode": { + "summary": "System audio mode enabled or disabled", + "type": "boolean", + "example": false + }, + "name":{ + "summary": "OSD friendly name", + "type": "string", + "example": "My TV" + }, + "turnOffAllDevices":{ + "summary": "Turn off policy", + "type": "boolean", + "example": false + }, + "message":{ + "summary": "The message is a base64 encoded byte array of the raw CEC bytes. The CEC message includes the device ID for the intended destination.", + "type": "string", + "example": "1234567890" + }, + "result": { + "type":"object", + "properties": { + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "success" + ] + }, + "success": { + "summary": "Whether the request succeeded", + "type": "boolean", + "example": "true" + } + }, + "methods": { + "getCECAddresses":{ + "summary": "Returns the HDMI-CEC addresses that are assigned to the local device", + "result": { + "type": "object", + "properties": { + "CECAddresses": { + "summary": "An object that includes both the physical and logical HDMI-CEC addresses.", + "type":"object", + "properties": { + "physicalAddress":{ + "$ref": "#/definitions/physicalAddress" + }, + "logicalAddresses":{ + "summary": "The logical address including the device type", + "type":"object", + "properties": { + "deviceType": { + "summary": "The type of device", + "type": "string", + "example": "Tuner" + }, + "logicalAddress": { + "summary": "The logical address of the device", + "type": "integer", + "example": 3 + } + }, + "required": [ + "deviceType", + "logicalAddress" + ] + } + }, + "required": [ + "physicalAddress", + "logicalAddresses" + ] + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "CECAddresses", + "success" + ] + } + }, + "getEnabled": { + "summary": "Returns whether HDMI-CEC is enabled", + "result": { + "type": "object", + "properties": { + "enabled": { + "$ref": "#/definitions/enabled" + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "enabled", + "success" + ] + } + }, + "sendMessage":{ + "summary": "Writes HDMI-CEC frame to the driver", + "params": { + "type":"object", + "properties": { + "message":{ + "$ref": "#/definitions/message" + } + }, + "required": [ + "message" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "setEnabled":{ + "summary": "Enables or disables HDMI-CEC", + "params": { + "type":"object", + "properties": { + "enabled":{ + "$ref": "#/definitions/enabled" + } + }, + "required": [ + "enabled" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "enableOneTouchView":{ + "summary": "Enables or disables one touch view", + "params": { + "type":"object", + "properties": { + "enabled":{ + "$ref": "#/definitions/enabled" + } + }, + "required": [ + "enabled" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "triggerAction":{ + "summary": "Triggers actions like POWER_ON or POWER_OFF", + "params": { + "type":"object", + "properties": { + "actionName":{ + "$ref": "#/definitions/actionName" + } + }, + "required": [ + "actionName" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "setPingInterval":{ + "summary": "Enables or disables one touch view", + "params": { + "type":"object", + "properties": { + "intervalSeconds":{ + "$ref": "#/definitions/intervalSeconds" + } + }, + "required": [ + "intervalSeconds" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "getConnectedDevices":{ + "summary": "Gets CEC connected devices", + "result": { + "type": "object", + "properties": { + "devices": { + "$ref": "#/definitions/devices" + }, + "systemAudioMode": { + "$ref": "#/definitions/systemAudioMode" + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "devices", + "systemAudioMode", + "success" + ] + } + + }, + "setOneTouchViewPolicy":{ + "summary": "Sets policy for one touch view", + "params": { + "type":"object", + "properties": { + "turnOffAllDevices":{ + "$ref": "#/definitions/turnOffAllDevices" + } + }, + "required": [ + "turnOffAllDevices" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + }, + "setName":{ + "summary": "Sets OSD friendly name", + "params": { + "type":"object", + "properties": { + "name":{ + "$ref": "#/definitions/name" + } + }, + "required": [ + "name" + ] + }, + "result": { + "$ref": "#/definitions/result" + } + } + + }, + "events": { + "cecAddressesChanged":{ + "summary": "Triggered when the address of the host CEC device has changed", + "params": { + "type":"object", + "properties": { + "CECAddresses": { + "summary": "Includes either the `physicalAddress` or `logicalAddresses`", + "type":"object", + "properties": { + "physicalAddress":{ + "$ref": "#/definitions/physicalAddress" + } + } + } + }, + "required": [ + "CECAddresses" + ] + } + }, + "onMessage":{ + "summary": "Triggered when a message is sent from an HDMI device", + "params": { + "type":"object", + "properties": { + "message": { + "$ref": "#/definitions/message" + } + }, + "required": [ + "message" + ] + } + }, + "onDevicesChanged":{ + "summary": "Triggered when HDMI device has changed", + "params": { + "type":"object", + "properties": { + "devices": { + "$ref": "#/definitions/devices" + } + }, + "required": [ + "devices" + ] + } + } + } +} diff --git a/LgiHdmiCec/LgiHdmiCecPlugin.json b/LgiHdmiCec/LgiHdmiCecPlugin.json new file mode 100644 index 0000000000..df326892fe --- /dev/null +++ b/LgiHdmiCec/LgiHdmiCecPlugin.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://raw.githubusercontent.com/rdkcentral/Thunder/master/Tools/JsonGenerator/schemas/plugin.schema.json", + "info": { + "title": "LgiHdmiCecPlugin", + "callsign": "com.lgi.rdk.HdmiCec", + "locator": "libWPEFrameworkLgiHdmiCec.so", + "status": "production", + "description": "The `LgiHdmiCec` plugin allows you to configure HDMI Consumer Electronics Control (CEC) on a set-top box.", + "version": "1.0" + }, + "interface": { + "$ref": "LgiHdmiCec.json#" + } +} diff --git a/LgiHdmiCec/Module.cpp b/LgiHdmiCec/Module.cpp new file mode 100644 index 0000000000..43183b6d7a --- /dev/null +++ b/LgiHdmiCec/Module.cpp @@ -0,0 +1,23 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* Copyright 2021 Liberty Global Service B.V. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/LgiHdmiCec/Module.h b/LgiHdmiCec/Module.h new file mode 100644 index 0000000000..eecdd1046d --- /dev/null +++ b/LgiHdmiCec/Module.h @@ -0,0 +1,30 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* Copyright 2021 Liberty Global Service B.V. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once +#ifndef MODULE_NAME +#define MODULE_NAME Plugin_LgiHdmiCec +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/LgiHdmiCec/README.md b/LgiHdmiCec/README.md new file mode 100644 index 0000000000..228d4f6a37 --- /dev/null +++ b/LgiHdmiCec/README.md @@ -0,0 +1,9 @@ +----------------- +Build: + +bitbake wpeframework-service-plugins + +----------------- +Test: + +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"3","method": "HdmiCec.1."}' http://127.0.0.1:9998/jsonrpc diff --git a/LgiHdmiCec/doc/LgiHdmiCecPlugin.md b/LgiHdmiCec/doc/LgiHdmiCecPlugin.md new file mode 100644 index 0000000000..a0bd6eeae3 --- /dev/null +++ b/LgiHdmiCec/doc/LgiHdmiCecPlugin.md @@ -0,0 +1,354 @@ + + +# HdmiCecPlugin + +**Version: 1.0** + +**Status: :black_circle::black_circle::black_circle:** + +org.rdk.HdmiCec plugin for Thunder framework. + +### Table of Contents + +- [Introduction](#head.Introduction) +- [Description](#head.Description) +- [Configuration](#head.Configuration) +- [Methods](#head.Methods) +- [Notifications](#head.Notifications) + + +# Introduction + + +## Scope + +This document describes purpose and functionality of the org.rdk.HdmiCec plugin. It includes detailed specification about its configuration, methods provided and notifications sent. + + +## Case Sensitivity + +All identifiers of the interfaces described in this document are case-sensitive. Thus, unless stated otherwise, all keywords, entities, properties, relations and actions should be treated as such. + + +## Acronyms, Abbreviations and Terms + +The table below provides and overview of acronyms used in this document and their definitions. + +| Acronym | Description | +| :-------- | :-------- | +| API | Application Programming Interface | +| HTTP | Hypertext Transfer Protocol | +| JSON | JavaScript Object Notation; a data interchange format | +| JSON-RPC | A remote procedure call protocol encoded in JSON | + +The table below provides and overview of terms and abbreviations used in this document and their definitions. + +| Term | Description | +| :-------- | :-------- | +| callsign | The name given to an instance of a plugin. One plugin can be instantiated multiple times, but each instance the instance name, callsign, must be unique. | + + +## References + +| Ref ID | Description | +| :-------- | :-------- | +| [HTTP](http://www.w3.org/Protocols) | HTTP specification | +| [JSON-RPC](https://www.jsonrpc.org/specification) | JSON-RPC 2.0 specification | +| [JSON](http://www.json.org/) | JSON specification | +| [Thunder](https://github.com/WebPlatformForEmbedded/Thunder/blob/master/doc/WPE%20-%20API%20-%20WPEFramework.docx) | Thunder API Reference | + + +# Description + +The `HdmiCec` plugin allows you to configure HDMI Consumer Electronics Control (CEC) on a set-top box. + +The plugin is designed to be loaded and executed within the Thunder framework. For more information about the framework refer to [[Thunder](#ref.Thunder)]. + + +# Configuration + +The table below lists configuration options of the plugin. + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| callsign | string | Plugin instance name (default: *org.rdk.HdmiCec*) | +| classname | string | Class name: *org.rdk.HdmiCec* | +| locator | string | Library name: *libWPEFrameworkHdmiCec.so* | +| autostart | boolean | Determines if the plugin shall be started automatically along with the framework | + + +# Methods + +The following methods are provided by the org.rdk.HdmiCec plugin: + +HdmiCec interface methods: + +| Method | Description | +| :-------- | :-------- | +| [getCECAddresses](#method.getCECAddresses) | Returns the HDMI-CEC addresses that are assigned to the local device | +| [getEnabled](#method.getEnabled) | Returns whether HDMI-CEC is enabled | +| [sendMessage](#method.sendMessage) | Writes HDMI-CEC frame to the driver | +| [setEnabled](#method.setEnabled) | Enables or disables HDMI-CEC | + + + +## *getCECAddresses method* + +Returns the HDMI-CEC addresses that are assigned to the local device. + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.CECAddresses | object | An object that includes both the physical and logical HDMI-CEC addresses | +| result.CECAddresses.physicalAddress | array | The physical IP address of the device | +| result.CECAddresses.physicalAddress[#] | string | | +| result.CECAddresses.logicalAddresses | array | The logical address including the device type | +| result.CECAddresses.logicalAddresses[#] | object | | +| result.CECAddresses.logicalAddresses[#].deviceType | string | The type of device | +| result.CECAddresses.logicalAddresses[#].logicalAddress | integer | The logical address of the device | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "org.rdk.HdmiCec.1.getCECAddresses" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "CECAddresses": { + "physicalAddress": [ + "255, 255, 255, 255" + ], + "logicalAddresses": [ + { + "deviceType": "Tuner", + "logicalAddress": 3 + } + ] + }, + "success": true + } +} +``` + + +## *getEnabled method* + +Returns whether HDMI-CEC is enabled. + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.enabled | boolean | Indicates whether HDMI-CEC is enabled (`true`) or disabled (`false`) | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "org.rdk.HdmiCec.1.getEnabled" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "enabled": false, + "success": true + } +} +``` + + +## *sendMessage method* + +Writes HDMI-CEC frame to the driver. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.message | string | The message is a base64 encoded byte array of the raw CEC bytes. The CEC message includes the device ID for the intended destination | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "org.rdk.HdmiCec.1.sendMessage", + "params": { + "message": "1234567890" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *setEnabled method* + +Enables or disables HDMI-CEC. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.enabled | boolean | Indicates whether HDMI-CEC is enabled (`true`) or disabled (`false`) | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "org.rdk.HdmiCec.1.setEnabled", + "params": { + "enabled": false + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +# Notifications + +Notifications are autonomous events, triggered by the internals of the implementation, and broadcasted via JSON-RPC to all registered observers. Refer to [[Thunder](#ref.Thunder)] for information on how to register for a notification. + +The following events are provided by the org.rdk.HdmiCec plugin: + +HdmiCec interface events: + +| Event | Description | +| :-------- | :-------- | +| [cecAddressesChanged](#event.cecAddressesChanged) | Triggered when the address of the host CEC device has changed | +| [onMessage](#event.onMessage) | Triggered when a message is sent from an HDMI device | + + + +## *cecAddressesChanged event* + +Triggered when the address of the host CEC device has changed. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.CECAddresses | object | Includes either the `physicalAddress` or `logicalAddresses` | +| params.CECAddresses.physicalAddress | array | The physical IP address of the device | +| params.CECAddresses.physicalAddress[#] | string | | + +### Example + +```json +{ + "jsonrpc": "2.0", + "method": "client.events.1.cecAddressesChanged", + "params": { + "CECAddresses": { + "physicalAddress": [ + "255, 255, 255, 255" + ] + } + } +} +``` + + +## *onMessage event* + +Triggered when a message is sent from an HDMI device. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.message | string | The message is a base64 encoded byte array of the raw CEC bytes. The CEC message includes the device ID for the intended destination | + +### Example + +```json +{ + "jsonrpc": "2.0", + "method": "client.events.1.onMessage", + "params": { + "message": "1234567890" + } +} +``` + diff --git a/LgiNetwork/CHANGELOG.md b/LgiNetwork/CHANGELOG.md new file mode 100644 index 0000000000..b6dbca91ae --- /dev/null +++ b/LgiNetwork/CHANGELOG.md @@ -0,0 +1,33 @@ +# Changelog + +All notable changes to this RDK Service will be documented in this file. + +* Each RDK Service has a CHANGELOG file that contains all changes done so far. When version is updated, add a entry in the CHANGELOG.md at the top with user friendly information on what was changed with the new version. Please don't mention JIRA tickets in CHANGELOG. + +* Please Add entry in the CHANGELOG for each version change and indicate the type of change with these labels: + * **Added** for new features. + * **Changed** for changes in existing functionality. + * **Deprecated** for soon-to-be removed features. + * **Removed** for now removed features. + * **Fixed** for any bug fixes. + * **Security** in case of vulnerabilities. + +* 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. + +* For more details, refer to [versioning](https://github.com/rdkcentral/rdkservices#versioning) section under Main README. + +## [1.0.2] - 2022-10-04 +### Fixed +- Fixed warnings that are treated as errors with "-Wall -Werror" + +## [1.0.1] +### Fixed +- Optimize include + +## [1.0.0] - 2022-05-11 +### Added +- Add CHANGELOG + +### Change +- Reset API version to 1.0.0 +- Change README to inform how to update changelog and API version \ No newline at end of file diff --git a/LgiNetwork/CMakeLists.txt b/LgiNetwork/CMakeLists.txt new file mode 100644 index 0000000000..2659e66d21 --- /dev/null +++ b/LgiNetwork/CMakeLists.txt @@ -0,0 +1,72 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(PLUGIN_NAME Network) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) + +set(PLUGIN_NETWORK_STARTUPORDER "" CACHE STRING "To configure startup order of Network plugin") + +find_package(${NAMESPACE}Plugins REQUIRED) +find_package(${NAMESPACE}Definitions REQUIRED) +find_package(LibGlib REQUIRED) +find_package(LibGio REQUIRED) + +find_path(LIBGIOUNIX_INCLUDE_DIR + NAMES gio/gunixfdlist.h + PATH_SUFFIXES gio-unix-2.0 ) + +message("LIBGIOUNIX_INCLUDE_DIR = ${LIBGIOUNIX_INCLUDE_DIR}") +set(LIBGIO_INCLUDE_DIRS ${LIBGIO_INCLUDE_DIR} ${LIBGIOUNIX_INCLUDE_DIR}) + +add_library(${MODULE_NAME} SHARED + Network.cpp + NetUtils.cpp + NetUtilsNetlink.cpp + NetworkTraceroute.cpp + PingNotifier.cpp + Module.cpp + dbus/lginetwork_client.cpp + ) + +if(GDBUS_USE_CODEGEN_IMPL) + target_sources(${MODULE_NAME} PUBLIC dbus/gdbus-codegen-impl/lginetwork_dbus_api.c) + add_compile_definitions(GDBUS_USE_CODEGEN_IMPL) +else() + target_sources(${MODULE_NAME} PUBLIC dbus/lginetwork_dbus_api.c) +endif() + +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +add_definitions (-DNET_DEFINED_INTERFACES_ONLY) + +include_directories(${LIBGLIB_INCLUDE_DIRS}) +include_directories(${LIBGIO_INCLUDE_DIRS}) +target_include_directories(${MODULE_NAME} PRIVATE ../helpers) + +target_link_libraries(${MODULE_NAME} + PRIVATE + ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${NAMESPACE}Definitions::${NAMESPACE}Definitions + ${LIBGIO_LIBRARIES} + ${LIBGLIB_LIBRARIES}) + +install(TARGETS ${MODULE_NAME} + DESTINATION lib/${STORAGE_DIRECTORY}/plugins) + +write_config(${PLUGIN_NAME}) diff --git a/LgiNetwork/Module.cpp b/LgiNetwork/Module.cpp new file mode 100644 index 0000000000..69ecca0532 --- /dev/null +++ b/LgiNetwork/Module.cpp @@ -0,0 +1,22 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/LgiNetwork/Module.h b/LgiNetwork/Module.h new file mode 100644 index 0000000000..522458ee8e --- /dev/null +++ b/LgiNetwork/Module.h @@ -0,0 +1,29 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once +#ifndef MODULE_NAME +#define MODULE_NAME Plugin_Network +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/LgiNetwork/NetUtils.cpp b/LgiNetwork/NetUtils.cpp new file mode 100644 index 0000000000..0a29e459aa --- /dev/null +++ b/LgiNetwork/NetUtils.cpp @@ -0,0 +1,281 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "NetUtils.h" +#include +#include "Network.h" + +#include "UtilsLogging.h" + +#include + +//Defines + +#define NETUTIL_DEVICE_PROPERTIES_FILE "/etc/device.properties" +#define COMMAND_RESULT_FILE "cmdresult" + + +namespace WPEFramework { + namespace Plugin { + + unsigned int NetUtils::m_counter = 0; + std::mutex NetUtils::m_counterProtect; + + /* + * + */ + NetUtils::NetUtils() + { + } + + NetUtils::~NetUtils() + { + } + + /* + * Initialise netutils: + * - load interface descriptions + */ + void NetUtils::InitialiseNetUtils() + { + _loadInterfaceDescriptions(); + } + + void NetUtils::_loadInterfaceDescriptions() + { + string value; + + if (envGetValue("WIFI_SUPPORT", value) && (strncasecmp(value.c_str(), "true", 4)==0) && envGetValue("WIFI_INTERFACE", value)) + interface_descriptions.insert({value, "WIFI"}); + if (envGetValue("MOCA_SUPPORT", value) && (strncasecmp(value.c_str(), "true", 4)==0) && envGetValue("MOCA_INTERFACE", value)) + interface_descriptions.insert({value, "MOCA"}); + if (envGetValue("ETHERNET_INTERFACE", value)) + interface_descriptions.insert({value, "ETHERNET"}); + + for (const auto& e : interface_descriptions) + LOGINFO ("%s %s", e.first.c_str(), e.second.c_str()); + } + + const std::string& NetUtils::getInterfaceDescription(const std::string interface) + { + static std::string empty(""); + auto it = interface_descriptions.find(interface.substr(0, interface.find(':'))); // look up "eth0" (real interface) for "eth0:0" (virtual interface) input also + return (it != interface_descriptions.end()) ? it->second : empty; + } + + /* + * Returns >= 0 on success + * Blocking process so run in background thread + * + * If 'result' is not NULL then it will be set to the return status of the command + * + * If 'outputfile' is not NULL then it should contain a name for the outpur file. The output of the command will + * be dumped to '/tmp/[outputfile]x' where x is a unique number. + * The filepath will be returned in 'output' and the file should be deleted by the calling process after use. + */ + int NetUtils::execCmd(const char *command, std::string &output, bool *result, const char *outputfile) + { + std::string commandString; + std::string resultFile; + char buffer[MAX_OUTPUT_LENGTH]; + FILE *pipe = NULL; + size_t length = 0; + + output.clear(); + + commandString.assign(command); + + // If we have an output file then pipe the output from the command to /tmp/outpufile + if (outputfile) + { + getTmpFilename(outputfile, output); //append a count to get a unique filename and return the filename in output + commandString += " > "; + commandString += output; + } + + // If we have requested a result, store the result of the command in /tmp/cmdresult + if (result) + { + getTmpFilename(COMMAND_RESULT_FILE, resultFile); //append a count to get a unique filename + commandString += "; echo $? > "; + commandString += resultFile; + } + + pipe = popen(commandString.c_str(), "r"); + if (pipe == NULL) + { + LOGERR("%s: failed to open file '%s' for read mode with result: %s", __FUNCTION__, + commandString.c_str(), strerror(errno)); + return -1; + } + + LOGWARN("%s: opened file '%s' for read mode", __FUNCTION__, + commandString.c_str()); + + while (!feof(pipe) && fgets(buffer, MAX_OUTPUT_LENGTH, pipe) != NULL) + { + // If we are not dumping it to file, store the output + if (!outputfile) + { + output += buffer; + } + } + + // Strip trailing line feed from the output + if ((length = output.length()) > 0) + { + if (output[length-1] == '\n') + { + output.erase(length-1); + } + } + + // If we have requested a result, query the contents of the result file + if (result) + { + std::string commandResult = "1"; + if (!getFile(resultFile.c_str(), commandResult, true)) //delete file after reading + { + LOGERR("%s: failed to get command result '%s'", __FUNCTION__,resultFile.c_str()); + } + else + { + *result = (commandResult == "0"); + LOGINFO("%s: command result '%s'", __FUNCTION__,((*result)?"true":"false")); + } + } + + return pclose(pipe); + } + + /* + * See if an address is IPV4 format + */ + bool NetUtils::isIPV4(const std::string &address) + { + struct in_addr ipv4address; + return (inet_pton(AF_INET, address.c_str(), &ipv4address) > 0); + } + + /* + * See if an address is IPV6 format + */ + bool NetUtils::isIPV6(const std::string &address) + { + struct in6_addr ipv6address; + return (inet_pton(AF_INET6, address.c_str(), &ipv6address) > 0); + } + + /* + * Get the contents of a file + * if deleteFile is true, remove the file after it is read + * (default == false) + */ + bool NetUtils::getFile(const char *filepath, std::string &contents, bool deleteFile) + { + bool result = false; + if (filepath) + { + std::ifstream ifs(filepath); + if (ifs.is_open()) + { + std::getline(ifs, contents); + result = contents.length() > 0; + ifs.close(); + + if (deleteFile) + { + std::remove(filepath); + } + } + } + return result; + } + + /* + * Get the value of the given key from the environment (device properties file) + */ + bool NetUtils::envGetValue(const char *key, std::string &value) + { + std::ifstream fs(NETUTIL_DEVICE_PROPERTIES_FILE, std::ifstream::in); + std::string::size_type delimpos; + std::string line; + + if (!fs.fail()) + { + while (std::getline(fs, line)) + { + if (!line.empty() && + ((delimpos = line.find('=')) > 0)) + { + std::string itemKey = line.substr(0, delimpos); + if (itemKey == key) + { + value = line.substr(delimpos + 1, std::string::npos); + return true; + } + } + } + } + + return false; + } + + void NetUtils::getTmpFilename(const char *in, std::string &out) + { + std::lock_guard lock(m_counterProtect); + out = "/tmp/"; + out += in; + out += std::to_string(m_counter++); + } + + bool NetUtils::isIPV6LinkLocal(const std::string& address) + { + struct sockaddr_in6 sa6; + + if (inet_pton(AF_INET6, address.c_str(), &(sa6.sin6_addr)) == 0) + return false; + else + return IN6_IS_ADDR_LINKLOCAL(&sa6.sin6_addr); + } + + bool NetUtils::isIPV4LinkLocal(const std::string& address) + { + struct sockaddr_in sa; + + if (inet_pton(AF_INET, address.c_str(), &(sa.sin_addr)) == 0) + return false; + else + return IN_IS_ADDR_LINKLOCAL(sa.sin_addr.s_addr); + } + + // Not every character can be used for endpoint + bool NetUtils::_isCharacterIllegal(const int& c) + { + //character must be "-./0-9a-zA-Z" + return (c < 45) || ((c > 57) && (c < 65)) || ((c > 90) && (c < 97)) || (c > 122); + } + + // Check if valid - consist of only allowed characters + bool NetUtils::isValidEndpointURL(const std::string& endpoint) + { + return std::find_if(endpoint.begin(), endpoint.end(), _isCharacterIllegal) == endpoint.end(); + } + } // namespace Plugin +} // namespace WPEFramework diff --git a/LgiNetwork/NetUtils.h b/LgiNetwork/NetUtils.h new file mode 100644 index 0000000000..2864e411fb --- /dev/null +++ b/LgiNetwork/NetUtils.h @@ -0,0 +1,68 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once + +#include "Module.h" + +#include "NetUtilsNetlink.h" + +#include + +#define IN_IS_ADDR_LINKLOCAL(a) (((a) & htonl(0xffff0000)) == htonl (0xa9fe0000)) + +namespace WPEFramework { + #define MAX_COMMAND_LENGTH 256 + #define MAX_OUTPUT_LENGTH 128 + + namespace Plugin { + class Network; + + class NetUtils { + private: + void _loadInterfaceDescriptions(); + + public: + NetUtils(); + virtual ~NetUtils(); + + void InitialiseNetUtils(); + const std::string& getInterfaceDescription(const std::string name); + + static bool isIPV4(const std::string &address); + static bool isIPV6(const std::string &address); + static bool isIPV6LinkLocal(const std::string &address); + static bool isIPV4LinkLocal(const std::string &address); + static int execCmd(const char *command, std::string &output, bool *result = NULL, const char *outputfile = NULL); + static bool getFile(const char *filepath, std::string &contents, bool deleteFile = false); + + static bool envGetValue(const char *key, std::string &value); + + static void getTmpFilename(const char *in, std::string &out); + static bool isValidEndpointURL(const std::string& endpoint); + + private: + static bool _isCharacterIllegal(const int& c); + + static unsigned int m_counter; + static std::mutex m_counterProtect; + std::map interface_descriptions; + }; + } // namespace Plugin +} // namespace WPEFramework diff --git a/LgiNetwork/NetUtilsNetlink.cpp b/LgiNetwork/NetUtilsNetlink.cpp new file mode 100644 index 0000000000..453edb3b9b --- /dev/null +++ b/LgiNetwork/NetUtilsNetlink.cpp @@ -0,0 +1,380 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "NetUtilsNetlink.h" +#include +#include +#include +#include +#include + +#include "UtilsLogging.h" + +namespace WPEFramework { + namespace Plugin { + + Netlink::Netlink() : + m_fdNetlink(-1), + m_nlSockaddr(), + m_netlinkProtect() + { + } + + Netlink::~Netlink() + { + if (m_fdNetlink != -1) + { + close(m_fdNetlink); + m_fdNetlink = -1; + } + } + + /* + * Create a netlink socket + * Note - the socket is set to non-blocking so use select/poll to manage waiting and timeout + * - only one netlink connection should be made per thread (nl_pid should be unique) + */ + bool Netlink::connect(int groups) + { + std::lock_guard lock(m_netlinkProtect); + + memset(&m_nlSockaddr, 0, sizeof(m_nlSockaddr)); + m_nlSockaddr.nl_family = AF_NETLINK; + m_nlSockaddr.nl_groups = groups; + m_nlSockaddr.nl_pid = pthread_self(); + m_nlSockaddr.nl_pid <<= 16; + m_nlSockaddr.nl_pid += getpid(); + + m_fdNetlink = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (m_fdNetlink == -1) + { + LOGERR("Failed to create Netlink socket"); + return false; + } + + if (bind(m_fdNetlink, (struct sockaddr *)&m_nlSockaddr, sizeof(m_nlSockaddr)) < 0) + { + LOGERR("Failed to bind to Netlink socket: %s", strerror(errno)); + close(m_fdNetlink); + m_fdNetlink = -1; + return false; + } + + if (fcntl(m_fdNetlink, F_SETFL, O_NONBLOCK) == -1) + { + LOGWARN("Failed to set Netlink socket to non-blocking"); + } + + return true; + } + + /* + * Read data from the socket + * Wait for msTimeout milliseconds for data (do not wait if msTimeout is zero) + * Note - this will not block if data is not currently available + */ + int Netlink::read(char *buffer, int size) + { + std::lock_guard lock(m_netlinkProtect); + return _getMessage(buffer, size, 0); + } + + /* + * Get the default interface from netlink + */ + bool Netlink::getDefaultInterfaces(indexList &interfaceIndex, stringList &gatewayAddress, bool ipv6) + { + std::lock_guard lock(m_netlinkProtect); + if (!_sendRouteRequest(ipv6)) + { + LOGERR("Failed to send route information request."); + } + else if (!_getRoutesInformation(interfaceIndex, gatewayAddress)) + { + LOGINFO("Failed to find default route information."); + } + else + { + return true; + } + + return false; + } + + /* + * DEBUG function to log netlink messages in buffer + */ + void Netlink::displayMessages(const char* msgBuffer, int msgLength) + { + struct nlmsghdr *nlhdr; + nlhdr = (struct nlmsghdr *) msgBuffer; + struct ifinfomsg *ifinfo; + + // Display any netlink messages received. + for (nlhdr = (struct nlmsghdr *)msgBuffer; + NLMSG_OK(nlhdr, msgLength); + nlhdr = NLMSG_NEXT(nlhdr, msgLength)) + { + if (nlhdr->nlmsg_type == NLMSG_DONE) + { + break; + } + switch (nlhdr->nlmsg_type) + { + case RTM_DELADDR: + LOGINFO("Netlink DELADDR EVENT..."); + break; + case RTM_DELLINK: + LOGINFO("Netlink DISCONNECTION EVENT..."); + break; + case RTM_NEWLINK: + LOGINFO("Netlink NEWLINK EVENT..."); + break; + case RTM_NEWADDR: + LOGINFO("Netlink NEWADDR EVENT..."); + break; + default: + LOGINFO("Netlink OTHER EVENT (%d)...",nlhdr->nlmsg_type); + break; + } + + if ( nlhdr->nlmsg_type == RTM_NEWLINK ) + { + ifinfo = (struct ifinfomsg *)NLMSG_DATA(nlhdr); + if ((ifinfo->ifi_flags & IFF_UP) && + (ifinfo->ifi_flags & IFF_RUNNING)) + { + LOGINFO("EVENT TYPE = CONNECTED..."); + } + } + } + } + + + /* + * Internal functions + */ + + /* + * Wait for data returned by the socket for specified time + */ + bool Netlink::_waitForReply(unsigned ms) + { + struct timeval timeout; + fd_set readFDSet; + + FD_ZERO(&readFDSet); + FD_SET(m_fdNetlink, &readFDSet); + + timeout.tv_sec = (ms / 1000); + timeout.tv_usec = ((ms % 1000) * 1000); + + if (select(m_fdNetlink + 1, &readFDSet, NULL, NULL, &timeout) > 0) + { + return FD_ISSET(m_fdNetlink, &readFDSet); + } + return false; + } + + int Netlink::_getMessage(char *buffer, int size, unsigned msTimeout) + { + struct iovec iov; + struct msghdr msg; + int replyLength = -1; + + if (msTimeout) + { + if (!_waitForReply(msTimeout)) + { + LOGERR("No message received"); + return false; + } + } + + iov.iov_base = buffer; + iov.iov_len = size; + + msg.msg_name = &m_nlSockaddr; + msg.msg_namelen = sizeof(m_nlSockaddr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + if ((replyLength = recvmsg(m_fdNetlink, &msg, 0)) < static_cast(sizeof(struct nlmsghdr))) + { + LOGERR("Unable to read message"); + replyLength = -1; + } + + return replyLength; + } + + /* + * Functions for requesting and processing network route information + */ + + /* + * Send a netlink request for all network route information + */ + bool Netlink::_sendRouteRequest(bool ipv6) + { + struct messageBuffer { + struct nlmsghdr netlinkRequesthdr; + struct rtmsg request; + } requestMessage; + + memset(&requestMessage, 0, sizeof(struct messageBuffer)); + + requestMessage.netlinkRequesthdr.nlmsg_type = RTM_GETROUTE; + requestMessage.netlinkRequesthdr.nlmsg_len = sizeof(requestMessage); + requestMessage.netlinkRequesthdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + requestMessage.netlinkRequesthdr.nlmsg_seq = 1; + requestMessage.request.rtm_family = ipv6 ? AF_INET6 : AF_INET; + + if (send(m_fdNetlink, &requestMessage, sizeof(requestMessage), 0) < 0) + { + LOGERR("Failed to send socket message: %s", strerror(errno)); + return false; + } + + return true; + } + + /* + * Retrieve the route information data and parse it to retrieve the default route + */ + bool Netlink::_getRoutesInformation(indexList &defaultInterfaceIndex, stringList &gatewayAddress) + { + alignas(alignof(struct nlmsghdr)) char msgBuffer[NETLINK_MESSAGE_BUFFER_SIZE]; + struct nlmsghdr *nlhdr; + int msgLength = -1; + unsigned index = 0; + std::string destination = ""; + std::string gateway = ""; + + defaultInterfaceIndex.clear(); + gatewayAddress.clear(); + + /* Retrieve Netlink messages */ + if ((msgLength = _getMessage(msgBuffer, NETLINK_MESSAGE_BUFFER_SIZE, NETLINK_MESSAGE_TIMEOUT_MS)) < 0 ) + { + LOGERR("Unable to read message"); + return false; + } + + nlhdr = (struct nlmsghdr *)msgBuffer; + + for (nlhdr = (struct nlmsghdr *)msgBuffer; + NLMSG_OK(nlhdr, msgLength); + nlhdr = NLMSG_NEXT(nlhdr, msgLength)) + { + if (nlhdr->nlmsg_type == NLMSG_DONE) + { + break; + } + else if (nlhdr->nlmsg_type == RTM_NEWROUTE) + { + if (_parseRoute(nlhdr, index, destination, gateway)) + { + if (destination.length() == 0) // default route has no destination address + { + LOGINFO("Default interface found: %d, %s", index, gateway.c_str()); + gatewayAddress.push_back(gateway); + defaultInterfaceIndex.push_back(index); + } + } + } + else + { + // Ignore other message types + } + } + + return !defaultInterfaceIndex.empty(); + } + + /* + * Parse the route message returning the interface index and the destination address + */ + bool Netlink::_parseRoute(void *msg, unsigned &index, std::string &destination, std::string &gateway) + { + struct nlmsghdr *nlhdr = (struct nlmsghdr *)msg; + struct rtmsg *routeMsg; + struct rtattr *attribute; + int attrLength = -1; + char ipAddress[INET6_ADDRSTRLEN]; + + routeMsg = (struct rtmsg *)NLMSG_DATA(nlhdr); + + if (routeMsg->rtm_table != RT_TABLE_MAIN) + { + // we only want the main routing table information + return false; + } + + if ((routeMsg->rtm_family != AF_INET) && (routeMsg->rtm_family != AF_INET6)) + { + return false; + } + + index = 0; + + attrLength = nlhdr->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg)); + for (attribute = RTM_RTA(routeMsg); + RTA_OK(attribute, attrLength); + attribute = RTA_NEXT(attribute, attrLength)) + { + if (attribute->rta_type == RTA_OIF) + { + index = *(unsigned *)RTA_DATA(attribute); + } + else if (attribute->rta_type == RTA_DST) + { + inet_ntop(routeMsg->rtm_family, RTA_DATA(attribute), ipAddress, INET6_ADDRSTRLEN); + destination = ipAddress; + } + else if (attribute->rta_type == RTA_GATEWAY) + { + inet_ntop(routeMsg->rtm_family, RTA_DATA(attribute), ipAddress, INET6_ADDRSTRLEN); + gateway = ipAddress; + } + else + { + // ignore the rest for now + } + } + + //TBD - how to correctly determine the IPV6 gateway / default route? + if (routeMsg->rtm_family == AF_INET6) + { + if ((routeMsg->rtm_scope == RT_SCOPE_UNIVERSE) && + (routeMsg->rtm_type == RTN_UNICAST) && + (routeMsg->rtm_protocol == RTPROT_RA)) + { + destination = ""; + } + } + + return index > 0; + } + + } // namespace Plugin +} // namespace WPEFramework diff --git a/LgiNetwork/NetUtilsNetlink.h b/LgiNetwork/NetUtilsNetlink.h new file mode 100644 index 0000000000..d49bbf8178 --- /dev/null +++ b/LgiNetwork/NetUtilsNetlink.h @@ -0,0 +1,63 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once + +#include "Module.h" + +#include + +namespace WPEFramework { + namespace Plugin { + #define NETLINK_MESSAGE_BUFFER_SIZE 8192 + #define NETLINK_MESSAGE_TIMEOUT_MS 500 + + typedef std::vector stringList; + typedef std::vector indexList; + + /* + * This is a helper class to manage a netlink socket used to get info about various network + * parameters + */ + class Netlink + { + public: + Netlink(); + virtual ~Netlink(); + + bool connect(int groups = 0); + int read(char *buffer, int size); + bool getDefaultInterfaces(indexList &interfaceIndex, stringList &gatewayAddress, bool ipv6 = false); + int sockfd() { return m_fdNetlink;} + + void displayMessages(const char* msgBuffer, int msgLength); + + private: + int m_fdNetlink; + struct sockaddr_nl m_nlSockaddr; + std::mutex m_netlinkProtect; + + bool _waitForReply(unsigned ms); + bool _sendRouteRequest(bool ipv6 = false); + int _getMessage(char *buffer, int size, unsigned msTimeout); + bool _getRoutesInformation(indexList &defaultInterfaceIndex, stringList &gatewayAddress); + bool _parseRoute(void *msg, unsigned &index, std::string &destination, std::string &gateway); + }; + } // namespace Plugin +} // namespace WPEFramework diff --git a/LgiNetwork/Network.config b/LgiNetwork/Network.config new file mode 100644 index 0000000000..1961dee2b6 --- /dev/null +++ b/LgiNetwork/Network.config @@ -0,0 +1,7 @@ +set (autostart true) +set (preconditions Platform) +set (callsign "org.rdk.Network") + +if(PLUGIN_NETWORK_STARTUPORDER) +set (startuporder ${PLUGIN_NETWORK_STARTUPORDER}) +endif() diff --git a/LgiNetwork/Network.cpp b/LgiNetwork/Network.cpp new file mode 100644 index 0000000000..88bc5c49a5 --- /dev/null +++ b/LgiNetwork/Network.cpp @@ -0,0 +1,1216 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "Network.h" +#include +#include +#include "dbus/lginetwork_client.hpp" + +#include "UtilsJsonRpc.h" +#include "UtilsLogging.h" + +using namespace std; + +#define DEFAULT_PING_PACKETS 15 +#define CIDR_NETMASK_IP_LEN 32 + +#define API_VERSION_NUMBER_MAJOR 1 +#define API_VERSION_NUMBER_MINOR 0 +#define API_VERSION_NUMBER_PATCH 2 + +/* Netsrvmgr Based Macros & Structures */ +#define INTERFACE_SIZE 10 +#define INTERFACE_LIST 50 +#define MAX_IP_ADDRESS_LEN 46 +#define MAX_IP_FAMILY_SIZE 10 +#define MAX_HOST_NAME_LEN 128 +#define MAX_ENDPOINTS 5 +#define MAX_ENDPOINT_SIZE 260 // 253 + 1 + 5 + 1 (domain name max length + ':' + port number max chars + '\0') + +// TODO: remove this +#define registerMethod(...) for (uint8_t i = 1; GetHandler(i); i++) GetHandler(i)->Register(__VA_ARGS__) + +namespace WPEFramework +{ + + namespace { + + static Plugin::Metadata metadata( + // Version (Major, Minor, Patch) + API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH, + // Preconditions + {}, + // Terminations + {}, + // Controls + {} + ); + } + + namespace Plugin + { + SERVICE_REGISTRATION(Network, API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH); + Network* Network::_instance = nullptr; + + Network::Network() + : PluginHost::JSONRPC() + , m_apiVersionNumber(API_VERSION_NUMBER_MAJOR) + { + Network::_instance = this; + m_isPluginInited = false; + + CreateHandler({2}); + + // Quirk + registerMethod("getQuirks", &Network::getQuirks, this); + + // Network_API_Version_1 + registerMethod("getInterfaces", &Network::getInterfaces, this); + registerMethod("isInterfaceEnabled", &Network::isInterfaceEnabled, this); + registerMethod("setInterfaceEnabled", &Network::setInterfaceEnabled, this); + registerMethod("getDefaultInterface", &Network::getDefaultInterface, this); + registerMethod("setDefaultInterface", &Network::setDefaultInterface, this); + + registerMethod("getStbIp", &Network::getStbIp, this); + + registerMethod("trace", &Network::trace, this); + registerMethod("traceNamedEndpoint", &Network::traceNamedEndpoint, this); + + registerMethod("getNamedEndpoints", &Network::getNamedEndpoints, this); + + registerMethod("ping", &Network::ping, this); + registerMethod("pingNamedEndpoint", &Network::pingNamedEndpoint, this); + + Register("setIPSettings", &Network::setIPSettings, this); + GetHandler(2)->Register("setIPSettings", &Network::setIPSettings2, this); + Register("getIPSettings", &Network::getIPSettings, this); + GetHandler(2)->Register("getIPSettings", &Network::getIPSettings2, this); + + registerMethod("getSTBIPFamily", &Network::getSTBIPFamily, this); + registerMethod("isConnectedToInternet", &Network::isConnectedToInternet, this); + registerMethod("setConnectivityTestEndpoints", &Network::setConnectivityTestEndpoints, this); + + registerMethod("getPublicIP", &Network::getPublicIP, this); + registerMethod("setStunEndPoint", &Network::setStunEndPoint, this); + + m_defaultInterface = ""; + m_gatewayInterface = ""; + + m_netUtils.InitialiseNetUtils(); + m_stunEndPoint = "stun.l.google.com"; + m_stunPort = 19302; + m_stunBindTimeout = 30; + m_stunCacheTimeout = 0; + m_stunSync = true; + } + + Network::~Network() + { + Network::_instance = nullptr; + } + + const string Network::Initialize(PluginHost::IShell* /* service */) + { + string msg; + int rcode = m_NetworkClient.Run(); + if (rcode) + { + LOGERR("Failed to initialize DBus Network Client (%d)", rcode); + msg = "Failed to initialize DBus Network Client"; + m_isPluginInited = false; + } + else + { + m_isPluginInited = true; + m_NetworkClient.onStatusChangeEvent = StatusChangeEvent; + m_NetworkClient.onNetworkingEvent = NetworkingEvent; + m_NetworkClient.onHandleIpv4ConfigurationChangedEvent = IP4ConfigurationChangedEvent; + m_NetworkClient.onHandleIpv6ConfigurationChangedEvent = IP6ConfigurationChangedEvent; + LOGINFO("Succeeded initializing LGI DBus Network Client"); + CheckIfDefaultInterfaceChanged(); + } + + return msg; + } + + void Network::Deinitialize(PluginHost::IShell* /* service */) + { + m_isPluginInited = false; + m_NetworkClient.Stop(); + Unregister("getQuirks"); + Unregister("getInterfaces"); + Unregister("isInterfaceEnabled"); + Unregister("setInterfaceEnabled"); + Unregister("getDefaultInterface"); + Unregister("setDefaultInterface"); + Unregister("getStbIp"); + Unregister("trace"); + Unregister("traceNamedEndpoint"); + Unregister("getNamedEndpoints"); + Unregister("ping"); + Unregister("pingNamedEndpoint"); + Unregister("setIPSettings"); + Unregister("getIPSettings"); + Unregister("isConnectedToInternet"); + Unregister("setConnectivityTestEndpoints"); + Unregister("getPublicIP"); + Unregister("setStunEndPoint"); + + Network::_instance = nullptr; + } + + string Network::Information() const + { + return(string()); + } + + bool Network::isValidCIDRv4(string buf) + { + string CIDR_PREFIXES[CIDR_NETMASK_IP_LEN] = { + "128.0.0.0", + "192.0.0.0", + "224.0.0.0", + "240.0.0.0", + "248.0.0.0", + "252.0.0.0", + "254.0.0.0", + "255.0.0.0", + "255.128.0.0", + "255.192.0.0", + "255.224.0.0", + "255.240.0.0", + "255.248.0.0", + "255.252.0.0", + "255.254.0.0", + "255.255.0.0", + "255.255.128.0", + "255.255.192.0", + "255.255.224.0", + "255.255.240.0", + "255.255.248.0", + "255.255.252.0", + "255.255.254.0", + "255.255.255.0", + "255.255.255.128", + "255.255.255.192", + "255.255.255.224", + "255.255.255.240", + "255.255.255.248", + "255.255.255.252", + "255.255.255.254", + "255.255.255.255", + }; + int i = 0; + bool retval = false; + while(i < CIDR_NETMASK_IP_LEN) + { + if((buf.compare(CIDR_PREFIXES[i])) == 0) + { + retval = true; + break; + } + i++; + } + return retval; + } + + // Wrapper methods + uint32_t Network::getQuirks(const JsonObject& parameters, JsonObject& response) + { + JsonArray array; + array.Add("RDK-20093"); + response["quirks"] = array; + returnResponse(true) + } + + uint32_t Network::getInterfaces (const JsonObject& parameters, JsonObject& response) + { + bool result = false; + + if (m_isPluginInited) + { + JsonArray networkInterfaces; + std::vector* interfaces = m_NetworkClient.getInterfaces(); + if (interfaces) + { + for (auto& iface : *interfaces) + { + JsonObject interface; + std::map parameters; + parameters[lginet::ParamMac] = ""; + parameters[lginet::ParamConnectivity] = ""; + + if (iface == "") + continue; + if (m_NetworkClient.getSpecificParamsForInterface(iface, parameters)) + { +#ifdef LGINET_STRICT_IFACE_NAMES + interface["interface"] = getTypeOfInterface(iface); +#else + interface["interface"] = iface; +#endif + interface["macAddress"] = parameters[lginet::ParamMac]; + interface["connected"] = (parameters[lginet::ParamConnectivity] == "yes"); + interface["enabled"] = m_NetworkClient.isInterfaceEnabled(iface); + networkInterfaces.Add(interface); + } + } + delete interfaces; + response["interfaces"] = networkInterfaces; + result = true; + } + else + { + LOGWARN ("Call for %s failed", __FUNCTION__); + } + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + + returnResponse(result) + } + + uint32_t Network::getDefaultInterface (const JsonObject& parameters, JsonObject& response) + { + string interface; + string gateway; + string type; + + bool result = false; + if (m_isPluginInited) + { + if (_getDefaultInterface(interface, gateway, type)) + { +#ifdef LGINET_STRICT_IFACE_NAMES + response["interface"] = type; +#else + response["interface"] = interface; +#endif + result = true; + } + else + { + LOGERR("Failed to get default interface"); + } + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + + returnResponse(result); + } + + uint32_t Network::setDefaultInterface (const JsonObject& parameters, JsonObject& response) + { + bool result = false; + + if(m_isPluginInited) + { + if ((parameters.HasLabel("interface")) && (parameters.HasLabel("persist"))) + { + // TODO: + LOGERR("Call %s not implemented", __FUNCTION__); + } + else + { + LOGWARN ("Required attributes are not provided."); + } + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + + returnResponse(result) + } + + uint32_t Network::getStbIp(const JsonObject ¶meters, JsonObject &response) + { + bool result = false; + + if (m_isPluginInited) + { + // TODO: + LOGERR("Call %s not implemented\n", __FUNCTION__); + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + + returnResponse(result) + } + + uint32_t Network::getSTBIPFamily(const JsonObject ¶meters, JsonObject &response) + { + bool result = false; + + if (m_isPluginInited) + { + if (parameters.HasLabel("family")) + { + // TODO: + LOGERR("Call %s not implemented\n", __FUNCTION__); + } + else + { + LOGWARN ("Required Family Attribute is not provided."); + } + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + + returnResponse(result) + } + + uint32_t Network::isInterfaceEnabled (const JsonObject& parameters, JsonObject& response) + { + bool result = false; + + if (m_isPluginInited) + { + if (parameters.HasLabel("interface")) + { + string interface = ""; + getStringParameter("interface", interface); + interface = convertIfaceName(interface); + + response["enabled"] = m_NetworkClient.isInterfaceEnabled(interface); + result = true; + } + else + { + LOGWARN("Required Interface attribute is not provided."); + } + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + + returnResponse(result) + } + + // API expects either ETHERNET or WIFI as iface name, not actual, physical iface name + // (like eth2 or wifi0). Convert API name to expected interface name. + std::string Network::convertIfaceName(const std::string iface) + { + std::string result = ""; + bool WantWifi = false; + if (iface == "WIFI") + WantWifi = true; + else if (iface != "ETHERNET") + { + // neither ethernet nor wifi, play dumb and assume someone passed physical name + LOGINFO("unexpected interface name (%s), passing as-is in good faith", iface.c_str()); + return iface; + } + + std::vector* names = m_NetworkClient.getInterfaces(); + if (names) + { + for (auto& name : *names) + { + std::map parameters; + parameters[lginet::ParamType] = ""; + if (m_NetworkClient.getSpecificParamsForInterface(name, parameters)) + { + if ((parameters[lginet::ParamType] == "eth" && !WantWifi) || + (parameters[lginet::ParamType] == "wifi" && WantWifi)) + { + result = name; + break; + } + } + } + delete(names); + } + + return result; + } + +#ifdef LGINET_STRICT_IFACE_NAMES + // convertIfaceName is outside of this ifdef as we want to be + // liberal in what we get and handle gracefully bad api usage + string Network::getTypeOfInterface(const string interface) + { + if (interface.size() >= 4) + { + if (strncmp(interface.c_str(), "eth", 3) == 0) + return "ETHERNET"; + if (strncmp(interface.c_str(), "wlan", 4) == 0) + return "WIFI"; + } + + LOGERR("Cannot determine interface type for %s", interface.c_str()); + return "N/A"; + } +#endif + + uint32_t Network::setInterfaceEnabled (const JsonObject& parameters, JsonObject& response) + { + bool result = false; + + if (m_isPluginInited) + { + if ((parameters.HasLabel("interface")) && (parameters.HasLabel("enabled")) /*&& (parameters.HasLabel("persist"))*/) + { + std::string interface; + bool enabled = false; + // TODO: + // bool persist = false; + + getStringParameter("interface", interface); + getBoolParameter("enabled", enabled); + interface = convertIfaceName(interface); + + if (!m_NetworkClient.setInterfaceEnabled(interface, enabled)) + { + LOGERR("call to setInterfaceEnabled failed"); + } + else + { + result = true; + } + } + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + + returnResponse(result) + } + + uint32_t Network::getNamedEndpoints(const JsonObject& parameters, JsonObject& response) + { + JsonArray namedEndpoints; + namedEndpoints.Add("CMTS"); + + response["endpoints"] = namedEndpoints; + returnResponse(true) + } + + uint32_t Network::trace(const JsonObject& parameters, JsonObject& response) + { + bool result = false; + + if(m_isPluginInited) + { + if (!parameters.HasLabel("endpoint")) + LOGERR("No endpoint specified"); + else + { + string endpoint = ""; + int packets = 0; + + getStringParameter("endpoint", endpoint); + if (parameters.HasLabel("packets")) // packets is optional? + getNumberParameter("packets", packets); + + if (_doTrace(endpoint, packets, response)) + result = true; + else + LOGERR("Failed to perform network trace"); + } + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + + returnResponse(result) + } + + uint32_t Network::traceNamedEndpoint(const JsonObject& parameters, JsonObject& response) + { + bool result = false; + + if (m_isPluginInited) + { + if (!parameters.HasLabel("endpointName")) + LOGERR("No endpointName specified"); + else + { + string endpointName = ""; + int packets = 0; + + getStringParameter("endpointName", endpointName); + if (parameters.HasLabel("packets")) // packets is optional? + getNumberParameter("packets", packets); + + if (_doTraceNamedEndpoint(endpointName, packets, response)) + result = true; + else + LOGERR("Failed to perform network trace names endpoint"); + } + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + + returnResponse(result) + } + + uint32_t Network::ping (const JsonObject& parameters, JsonObject& response) + { + string guid; + getStringParameter("guid", guid) + + uint32_t packets; + getDefaultNumberParameter("packets", packets, DEFAULT_PING_PACKETS); + + bool result = false; + + if (m_isPluginInited) + { + if (parameters.HasLabel("endpoint")) + { + string endpoint; + getStringParameter("endpoint", endpoint); + response = _doPing(guid, endpoint, packets); + result = response["success"].Boolean(); + } + else + { + LOGERR("No endpoint argument"); + } + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + returnResponse(result) + } + + uint32_t Network::pingNamedEndpoint (const JsonObject& parameters, JsonObject& response) + { + string guid; + getStringParameter("guid", guid) + + uint32_t packets; + getDefaultNumberParameter("packets", packets, DEFAULT_PING_PACKETS); + + bool result = false; + + if (m_isPluginInited) + { + if (parameters.HasLabel("endpointName")) + { + string endpointName; + getDefaultStringParameter("endpointName", endpointName, ""); + + response = _doPingNamedEndpoint(guid, endpointName, packets); + result = response["success"].Boolean(); + } + else + { + LOGERR("No endpointName argument"); + } + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + + returnResponse(result) + } + + uint32_t Network::setIPSettings(const JsonObject& parameters, JsonObject& response) + { + bool result = false; + + if (m_isPluginInited) + return setIPSettingsInternal(parameters, response); + else + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + + returnResponse(result) + } + + uint32_t Network::setIPSettings2(const JsonObject& parameters, JsonObject& response) + { + JsonObject internal; + string interface = ""; + string ipversion = ""; + string netmask = ""; + string gateway = ""; + string ipaddr = ""; + string primarydns = ""; + string secondarydns = ""; + bool autoconfig = true; + bool result = false; + + if(m_isPluginInited) + { + getDefaultStringParameter("interface", interface, ""); + internal ["interface"] = interface; + getDefaultStringParameter("ipversion", ipversion, ""); + internal ["ipversion"] = ipversion; + getDefaultBoolParameter("autoconfig", autoconfig, true); + internal ["autoconfig"] = autoconfig; + getDefaultStringParameter("ipaddr", ipaddr, ""); + internal ["ipaddr"] = ipaddr; + getDefaultStringParameter("netmask", netmask, ""); + internal ["netmask"] = netmask; + getDefaultStringParameter("gateway", gateway, ""); + internal ["gateway"] = gateway; + getDefaultStringParameter("primarydns", primarydns, "0.0.0.0"); + internal ["primarydns"] = primarydns; + getDefaultStringParameter("secondarydns", secondarydns, ""); + internal ["secondarydns"] = secondarydns; + + return setIPSettingsInternal(internal, response); + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + returnResponse(result) + } + + uint32_t Network::setIPSettingsInternal(const JsonObject& parameters, JsonObject& response) + { + bool result = false; + // struct in_addr ip_address, gateway_address, mask; + // struct in_addr broadcast_addr1, broadcast_addr2; + + if ((parameters.HasLabel("interface")) && (parameters.HasLabel("ipversion")) && (parameters.HasLabel("autoconfig")) && + (parameters.HasLabel("ipaddr")) && (parameters.HasLabel("netmask")) && (parameters.HasLabel("gateway")) && + (parameters.HasLabel("primarydns")) && (parameters.HasLabel("secondarydns"))) + { + response["supported"] = false; + result = false; + LOGERR("Call to %s not implemented.", __FUNCTION__); + } + + returnResponse(result) + } + + uint32_t Network::getIPSettings(const JsonObject& parameters, JsonObject& response) + { + bool result = false; + if (m_isPluginInited) + { + string interface = ""; + string ipversion = ""; + + if ((parameters.HasLabel("interface")) || (parameters.HasLabel("ipversion"))) + { + getStringParameter("interface", interface); + getStringParameter("ipversion", ipversion); + + bool isIpv6 = ipversion == "IPv6"; + + string iface = convertIfaceName(interface); + + std::map parameters; + parameters[lginet::ParamDns] = ""; + + if (isIpv6) + { + LOGWARN("Reading ipv6 stats"); + parameters[lginet::ParamIpv6Ip] = ""; + parameters[lginet::ParamIpv6PrefixLength] = ""; + parameters[lginet::ParamIpv6Gateway] = ""; + parameters[lginet::ParamIpv6DhcpServer] = ""; + parameters[lginet::ParamIpv6DhcpOptionPrefix] = ""; + } + else + { + LOGWARN("Reading ipv4 stats"); + parameters[lginet::ParamIpv4Ip] = ""; + parameters[lginet::ParamIpv4Mask] = ""; + parameters[lginet::ParamIpv4PrefixLength] = ""; + parameters[lginet::ParamIpv4Gateway] = ""; + parameters[lginet::ParamIpv4DhcpServer] = ""; + parameters[lginet::ParamIpv4DhcpOptionPrefix] = ""; + } + + if (m_NetworkClient.getSpecificParamsForInterface(iface, parameters)) + { + // retutn original interface name in case it was converted + response["interface"] = interface; + response["ipversion"] = ipversion; + response["primarydns"] = parameters[lginet::ParamDns]; + response["autoconfig"] = (parameters[lginet::ParamIpv4DhcpServer] != "") || + (parameters[lginet::ParamIpv6DhcpServer] != ""); + if (isIpv6) + { + response["ipaddr"] = parameters[lginet::ParamIpv6Ip]; + response["gateway"] = parameters[lginet::ParamIpv6Gateway]; + response["prefix_length"] = parameters[lginet::ParamIpv6PrefixLength]; + } + else + { + response["ipaddr"] = parameters[lginet::ParamIpv4Ip]; + response["netmask"] = parameters[lginet::ParamIpv4Mask]; + response["gateway"] = parameters[lginet::ParamIpv4Gateway]; + response["prefix_length"] = parameters[lginet::ParamIpv4PrefixLength]; + } + result = true; + } + } + else + { + LOGERR("Required attributes (interface and/or ipversion) are missing"); + } + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + + returnResponse(result) + } + + uint32_t Network::getIPSettings2(const JsonObject& parameters, JsonObject& response) + { + // stub method. + // turns out the getIpSettings in original method doesn't return _anything_ when IP isn't acquired yet + // due to dhcp server absence/failure and their solution was to add new one that returns just + // interface and autoconfig fields in that case. We're already doing it so just pass it to other method. + return getIPSettings(parameters, response); + } + + uint32_t Network::isConnectedToInternet (const JsonObject ¶meters, JsonObject &response) + { + bool result = false; + bool connected = false; + + if (m_isPluginInited) + { + std::vector* interfaces = m_NetworkClient.getInterfaces(); + if (interfaces) + { + for (auto& iface : *interfaces) + { + JsonObject interface; + std::map parameters; + parameters[lginet::ParamConnectivity] = ""; + + if (iface == "") + continue; + + if (m_NetworkClient.getSpecificParamsForInterface(iface, parameters)) + { + connected = connected | (parameters[lginet::ParamConnectivity] == "yes"); + } + } + delete interfaces; + result = true; + } + else + { + LOGWARN ("Call for %s failed", __FUNCTION__); + } + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + response["connectedToInternet"] = connected; + returnResponse(result); + } + + uint32_t Network::setConnectivityTestEndpoints (const JsonObject ¶meters, JsonObject &response) + { + bool result = true; + LOGWARN("Call ignored, not applicable here"); + returnResponse(result); + } + + uint32_t Network::getPublicIP(const JsonObject& parameters, JsonObject& response) + { + JsonObject internal; + internal ["server"] = m_stunEndPoint; + internal ["port"] = m_stunPort; + internal ["timeout"] = m_stunBindTimeout; + internal ["cache_timeout"] = m_stunCacheTimeout; + internal ["sync"] = m_stunSync; + + if (parameters.HasLabel("iface")) + internal ["iface"] = parameters["iface"]; + + if (parameters.HasLabel("ipv6")) + internal ["ipv6"] = parameters["ipv6"]; + + return getPublicIPInternal(internal, response); + } + + uint32_t Network::setStunEndPoint(const JsonObject& parameters, JsonObject& response) + { + getDefaultStringParameter("server", m_stunEndPoint, "stun.l.google.com"); + getDefaultNumberParameter("port", m_stunPort, 19302); + getDefaultBoolParameter("sync", m_stunSync, true); + getDefaultNumberParameter("timeout", m_stunBindTimeout, 30); + getDefaultNumberParameter("cache_timeout", m_stunCacheTimeout, 0); + + returnResponse(true); + } + + uint32_t Network::getPublicIPInternal(const JsonObject& parameters, JsonObject& response) + { + bool result = false; + + string server, iface; + int port; + + if (m_isPluginInited) + { + getStringParameter("server", server); + if (server.length() > MAX_HOST_NAME_LEN - 1) + { + LOGWARN("invalid args: server exceeds max length of %u", MAX_HOST_NAME_LEN); + returnResponse(result) + } + + getNumberParameter("port", port); + + /*only makes sense to get both server and port or neither*/ + if (!server.empty() && !port) + { + LOGWARN("invalid args: port missing"); + returnResponse(result) + } + if (port && server.empty()) + { + LOGWARN("invalid args: server missing"); + returnResponse(result) + } + + getDefaultStringParameter("iface", iface, ""); + if (iface.length() > 16 - 1) + { + LOGWARN("invalid args: interface exceeds max length of 16"); + returnResponse(result) + } + + // TODO: + LOGERR("Call %s not implememted\n", __FUNCTION__); + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + + response["success"] = result; + return (Core::ERROR_NONE); + } + + /* + * Notifications + * Note that some LGI NCS signals might be translated to different + * RDKNetwork notification depending solely on signal parameters. + * It's intentional. + */ + + void Network::onInterfaceEnabledStatusChanged(string interface, bool enabled) + { + JsonObject params; +#ifdef LGINET_STRICT_IFACE_NAMES + params["interface"] = getTypeOfInterface(interface); +#else + params["interface"] = interface; +#endif + params["enabled"] = enabled; + sendNotify("onInterfaceStatusChanged", params); + } + + void Network::CheckIfDefaultInterfaceChanged() + { + // handle onDefaultInterfaceChanged + string defaultInterface = m_NetworkClient.getDefaultInterface(); + if (defaultInterface != "" && defaultInterface != m_CurrentDefaultInterface) + { + onDefaultInterfaceChanged(m_CurrentDefaultInterface, defaultInterface); + m_CurrentDefaultInterface = defaultInterface; + } + } + + void Network::CheckInterfaceConnectionStatus(const string& interface) + { + bool connected = false; + std::map parameters { {lginet::ParamConnectivity, ""} }; + if (m_NetworkClient.getSpecificParamsForInterface(interface, parameters)) + { + connected = connected | (parameters[lginet::ParamConnectivity] == "yes"); + } + auto previous = m_InterfaceConnected.find(interface); + if (previous != m_InterfaceConnected.end() && previous->second == connected) + { + return; + } + else + { + m_InterfaceConnected[interface] = connected; + onInterfaceConnectionStatusChanged(interface, connected); + } + } + + void Network::CheckInterfaceEnabledStatus(const string& interface) + { + // handle onInterfaceStatusChanged ("Triggered when interface’s status changes to enabled/disabled.") + const bool enabled = m_NetworkClient.isInterfaceEnabled(interface); + auto itPreviouslyEnabled = m_InterfaceEnabled.find(interface); + if (itPreviouslyEnabled == m_InterfaceEnabled.end() || enabled != itPreviouslyEnabled->second) + { + onInterfaceEnabledStatusChanged(interface, enabled); + m_InterfaceEnabled[interface] = enabled; + } + } + + void Network::StatusChangeEvent(const std::string id, const std::string status) + { + if (Network::_instance) + { + LOGINFO("StatusChangeEvent entry id=%s status=%s", id.c_str(), status.c_str()); + + if (status == "Dormant") + { + Network::_instance->onInterfaceIPAddressChanged(id, "", "", false); + } + + Network::_instance->CheckInterfaceEnabledStatus(id); + Network::_instance->CheckIfDefaultInterfaceChanged(); + Network::_instance->CheckInterfaceConnectionStatus(id); + + LOGINFO("StatusChangeEvent exit"); + } + } + + void Network::NetworkingEvent(const string id, const string event, const std::map params) + { + if (Network::_instance) + Network::_instance->onNetworkingEvent(id, event, params); + } + + void Network::onNetworkingEvent(const string id, const string event, const std::map params) + { + LOGINFO("onNetworkingEvent entry id=%s event=%s", id.c_str(), event.c_str()); + if (event == "dhcp4.options") + { + auto iter = params.find("ip"); + if (iter != params.end()) + { + // this might trigger two IPAddressChanged events but that's fine + // - first one might not be actually triggered as ip isn't acquired + // yet at that time + onInterfaceIPAddressChanged(id, "", iter->second, true); + } + } + else if (event == "dhcp6.options") + { + auto iter = params.find("ip"); + if (iter != params.end()) + { + onInterfaceIPAddressChanged(id, iter->second, "", true); + } + } + else if (event == "configuration.changed") + { + auto iter = params.find(lginet::ParamNetworkChanged); + if (iter != params.end()) + { + // onInterfaceIPAddressChanged will only trigger notification in case the provided IP is different from whatever we have stored + std::map parameters; + parameters[lginet::ParamIpv4Ip] = ""; + parameters[lginet::ParamIpv6Ip] = ""; + + if (m_NetworkClient.getSpecificParamsForInterface(id, parameters)) + { + if (parameters[lginet::ParamIpv4Ip] != "" || parameters[lginet::ParamIpv6Ip] != "") + onInterfaceIPAddressChanged(id, parameters[lginet::ParamIpv6Ip], parameters[lginet::ParamIpv4Ip], true); + } + } + CheckIfDefaultInterfaceChanged(); + } + + CheckInterfaceConnectionStatus(id); + LOGINFO("onNetworkingEvent exit"); + } + + void Network::onInterfaceConnectionStatusChanged(string interface, bool connected) + { + JsonObject params; +#ifdef LGINET_STRICT_IFACE_NAMES + params["interface"] = getTypeOfInterface(interface); +#else + params["interface"] = interface; +#endif + + params["status"] = string (connected ? "CONNECTED" : "DISCONNECTED"); + if (!connected) + { + // ips are lost, so send onIPAddressStatusChanged/LOST & clear the old values + onInterfaceIPAddressChanged(interface, "", "", false); + } + sendNotify("onConnectionStatusChanged", params); + } + + void Network::onInterfaceIPAddressChanged(string interface, string ipv6Addr, string ipv4Addr, bool acquired) + { + JsonObject params; +#ifdef LGINET_STRICT_IFACE_NAMES + params["interface"] = getTypeOfInterface(interface); +#else + params["interface"] = interface; +#endif + + // take care of 'empty' addresses + ipv6Addr = ipv6Addr == "::" ? "" : ipv6Addr; + ipv4Addr = ipv4Addr == "0.0.0.0" ? "" : ipv4Addr; + + const string oldIpv4 = m_oldAddresses[{interface, 4}]; + const string oldIpv6 = m_oldAddresses[{interface, 6}]; + + // prevent re-triggering event for the same IP address + bool sendNotification = false; + if (acquired) + { + // ip4Address & ip6Address are required, so always set both keys + // in 'acquired' case, treat "" as "not available" (do not update m_oldAddresses) + if (!ipv4Addr.empty() && oldIpv4 != ipv4Addr) + { + m_oldAddresses[{interface, 4}] = ipv4Addr; + sendNotification = true; + } + params["ip4Address"] = m_oldAddresses[{interface, 4}]; + + if (!ipv6Addr.empty() && oldIpv6 != ipv6Addr) + { + m_oldAddresses[{interface, 6}] = ipv6Addr; + sendNotification = true; + } + params["ip6Address"] = m_oldAddresses[{interface, 6}]; + + } + else if (oldIpv6 != "" || oldIpv4 != "") + { + params["ip4Address"] = oldIpv4; + params["ip6Address"] = oldIpv6; + // ips are lost, so clear the old values + m_oldAddresses[{interface, 6}] = ""; + m_oldAddresses[{interface, 4}] = ""; + sendNotification = true; + } + + if (sendNotification) + { + params["status"] = string (acquired ? "ACQUIRED" : "LOST"); + sendNotify("onIPAddressStatusChanged", params); + } + } + + void Network::onDefaultInterfaceChanged(string oldInterface, string newInterface) + { + JsonObject params; +#ifdef LGINET_STRICT_IFACE_NAMES + params["oldInterfaceName"] = getTypeOfInterface(oldInterface); + params["newInterfaceName"] = getTypeOfInterface(newInterface); +#else + params["oldInterfaceName"] = oldInterface; + params["newInterfaceName"] = newInterface; +#endif + sendNotify("onDefaultInterfaceChanged", params); + } + + /* + * Internal functions + */ + + bool Network::_getDefaultInterface(string& interface, string& gateway, string& type) + { + bool result = false; + + if (m_isPluginInited) + { + interface = m_NetworkClient.getDefaultInterface(); + if (interface != "") + { + std::map parameters; + parameters[lginet::ParamIpv4Gateway] = ""; + parameters[lginet::ParamIpv6Gateway] = ""; + parameters[lginet::ParamType] = ""; + if (m_NetworkClient.getSpecificParamsForInterface(interface, parameters)) + { + type = parameters[lginet::ParamType]; + if (type == "eth") + type = "ETHERNET"; + else if (type == "wifi") + type = "WIFI"; + + if (parameters[lginet::ParamIpv4Gateway] != "") + { + gateway = parameters[lginet::ParamIpv4Gateway]; + result = true; + } + else if (parameters[lginet::ParamIpv6Gateway] != "") + { + gateway = parameters[lginet::ParamIpv6Gateway]; + result = true; + } + } + } + + if (result) + LOGINFO("Evaluated default network interface: '%s' and gateway: '%s'", interface.c_str(), gateway.c_str()); + else + LOGWARN("Unable to detect default network interface"); + } + else + { + LOGWARN ("Network plugin not initialised yet returning from %s", __FUNCTION__); + } + + return result; + } + + void Network::IP4ConfigurationChangedEvent(const string id) + { + if (Network::_instance) + Network::_instance->onIpConfigurationChangedEvent(id, 4); + } + + void Network::IP6ConfigurationChangedEvent(const string id) + { + if (Network::_instance) + Network::_instance->onIpConfigurationChangedEvent(id, 6); + } + + void Network::onIpConfigurationChangedEvent(const string interface, const int ipVersion) + { + std::map parameters; + const auto param = ipVersion == 4 ? lginet::ParamIpv4Ip : lginet::ParamIpv6Ip; + parameters[param] = ""; + + if (m_NetworkClient.getSpecificParamsForInterface(interface, parameters)) + { + onInterfaceIPAddressChanged(interface, parameters[lginet::ParamIpv6Ip], parameters[lginet::ParamIpv4Ip], true); + } + } + + } // namespace Plugin +} // namespace WPEFramework diff --git a/LgiNetwork/Network.h b/LgiNetwork/Network.h new file mode 100644 index 0000000000..4b4ee6150a --- /dev/null +++ b/LgiNetwork/Network.h @@ -0,0 +1,168 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once + +#include +#include +#include +#include +#include + +#include "Module.h" +#include "NetUtils.h" +#include "dbus/lginetwork_client.hpp" + +// Define this to use netlink calls (where there may be an alternative method but netlink could provide +// the information or perform the action required) +//#define USE_NETLINK + +// keep this defined to have API use and return interface names strictly with accordance +// to RDK Network docs. Undefine to use and return physical (actual) interface names. +#define LGINET_STRICT_IFACE_NAMES + +namespace WPEFramework { + namespace Plugin { + + // This is a server for a JSONRPC communication channel. + // For a plugin to be capable to handle JSONRPC, inherit from PluginHost::JSONRPC. + // By inheriting from this class, the plugin realizes the interface PluginHost::IDispatcher. + // This realization of this interface implements, by default, the following methods on this plugin + // - exists + // - register + // - unregister + // Any other methood to be handled by this plugin can be added can be added by using the + // templated methods Register on the PluginHost::JSONRPC class. + // As the registration/unregistration of notifications is realized by the class PluginHost::JSONRPC, + // this class exposes a public method called, Notify(), using this methods, all subscribed clients + // will receive a JSONRPC message as a notification, in case this method is called. + class Network : public PluginHost::IPlugin, public PluginHost::JSONRPC { + private: + + // We do not allow this plugin to be copied !! + Network(const Network&) = delete; + Network& operator=(const Network&) = delete; + + //Private variables + std::atomic_bool m_isPluginInited{false}; + std::thread m_registrationThread; + + //Begin methods + uint32_t getQuirks(const JsonObject& parameters, JsonObject& response); + + // Network_API_Version_1 + uint32_t getInterfaces(const JsonObject& parameters, JsonObject& response); + uint32_t isInterfaceEnabled(const JsonObject& parameters, JsonObject& response); + uint32_t setInterfaceEnabled(const JsonObject& parameters, JsonObject& response); + uint32_t getDefaultInterface(const JsonObject& parameters, JsonObject& response); + uint32_t setDefaultInterface(const JsonObject& parameters, JsonObject& response); + uint32_t getStbIp(const JsonObject& parameters, JsonObject& response); + uint32_t trace(const JsonObject& parameters, JsonObject& response); + uint32_t traceNamedEndpoint(const JsonObject& parameters, JsonObject& response); + uint32_t getNamedEndpoints(const JsonObject& parameters, JsonObject& response); + uint32_t ping(const JsonObject& parameters, JsonObject& response); + uint32_t pingNamedEndpoint(const JsonObject& parameters, JsonObject& response); + uint32_t setIPSettings(const JsonObject& parameters, JsonObject& response); + uint32_t setIPSettings2(const JsonObject& parameters, JsonObject& response); + uint32_t getIPSettings(const JsonObject& parameters, JsonObject& response); + uint32_t getIPSettings2(const JsonObject& parameters, JsonObject& response); + uint32_t getSTBIPFamily(const JsonObject& parameters, JsonObject& response); + uint32_t isConnectedToInternet(const JsonObject& parameters, JsonObject& response); + uint32_t setConnectivityTestEndpoints(const JsonObject& parameters, JsonObject& response); + uint32_t getPublicIP(const JsonObject& parameters, JsonObject& response); + uint32_t setStunEndPoint(const JsonObject& parameters, JsonObject& response); + + void onInterfaceEnabledStatusChanged(std::string interface, bool enabled); + void onInterfaceConnectionStatusChanged(std::string interface, bool connected); + void onInterfaceIPAddressChanged(std::string interface, std::string ipv6Addr, std::string ipv4Addr, bool acquired); + void onDefaultInterfaceChanged(std::string oldInterface, std::string newInterface); + + // Netmask Validation + bool isValidCIDRv4(std::string interface); + // Internal methods + bool _getDefaultInterface(std::string& interface, std::string& gateway, std::string& type); + + void retryIarmEventRegistration(); + void threadEventRegistration(); + + bool _doTrace(std::string &endpoint, int packets, JsonObject& response); + bool _doTraceNamedEndpoint(std::string &endpointName, int packets, JsonObject& response); + + JsonObject _doPing(const std::string& guid, const std::string& endPoint, int packets); + JsonObject _doPingNamedEndpoint(const std::string& guid, const std::string& endpointName, int packets); + uint32_t setIPSettingsInternal(const JsonObject& parameters, JsonObject& response); + + std::string convertIfaceName(const std::string iface); +#ifdef LGINET_STRICT_IFACE_NAMES + string getTypeOfInterface(const string interface); +#endif + + static void StatusChangeEvent(const std::string id, const std::string status); + static void NetworkingEvent(const string id, const string event, const std::map params); + static void IP4ConfigurationChangedEvent(const string id); + static void IP6ConfigurationChangedEvent(const string id); + void onIpConfigurationChangedEvent(const string interface, const int ipVersion); + // this one is just to reroute event according to type *and* params + void onNetworkingEvent(const string id, const string event, const std::map params); + + void CheckIfDefaultInterfaceChanged(); + void CheckInterfaceConnectionStatus(const string& id); + void CheckInterfaceEnabledStatus(const string& interface); + + public: + Network(); + virtual ~Network(); + + //Build QueryInterface implementation, specifying all possible interfaces to be returned. + BEGIN_INTERFACE_MAP(Network) + INTERFACE_ENTRY(PluginHost::IPlugin) + INTERFACE_ENTRY(PluginHost::IDispatcher) + END_INTERFACE_MAP + + //IPlugin methods + virtual const std::string Initialize(PluginHost::IShell* service) override; + virtual void Deinitialize(PluginHost::IShell* service) override; + virtual std::string Information() const override; + uint32_t getPublicIPInternal(const JsonObject& parameters, JsonObject& response); + + public: + static Network *_instance; + static Network *getInstance() {return _instance;} + + private: + lginet::LgiNetworkClient m_NetworkClient; + NetUtils m_netUtils; + string m_stunEndPoint; + string m_defaultInterface; + string m_gatewayInterface; + // (interface id, ip version number) -> ip (string) + std::map, string> m_oldAddresses; + //interface id -> status + std::map m_InterfaceEnabled; + //interface id -> status + std::map m_InterfaceConnected; + string m_CurrentDefaultInterface; + uint16_t m_stunPort; + uint16_t m_stunBindTimeout; + uint16_t m_stunCacheTimeout; + bool m_stunSync; + uint32_t m_apiVersionNumber; + }; + } // namespace Plugin +} // namespace WPEFramework diff --git a/LgiNetwork/Network.json b/LgiNetwork/Network.json new file mode 100644 index 0000000000..b3e60f6ffb --- /dev/null +++ b/LgiNetwork/Network.json @@ -0,0 +1,1001 @@ +{ + "$schema": "https://raw.githubusercontent.com/rdkcentral/rdkservices/main/Tools/json_generator/schemas/interface.schema.json", + "jsonrpc": "2.0", + "info": { + "title": "Network API", + "class": "Network", + "description": "The `Network` plugin allows you to manage network interfaces on a set-top box." + }, + "common": { + "$ref": "../common/common.json" + }, + "definitions": { + "interface":{ + "summary": "An interface, such as `ETHERNET` or `WIFI`, depending upon availability of the given interface in `getInterfaces`", + "type": "string", + "example": "WIFI" + }, + "ipversion": { + "summary": "either IPv4 or IPv6", + "type": "string", + "example": "IPv4" + }, + "ipv6": { + "summary": "either IPv4 or IPv6", + "type": "boolean", + "example": true + }, + "sipversion": { + "summary": "IPv4 (IPv6 is not currently supported)", + "type": "string", + "example": "IPv4" + }, + "autoconfig": { + "summary": "`true` if DHCP is used, `false` if IP is configured manually", + "type": "boolean", + "example": true + }, + "dhcpserver": { + "summary": "The DHCP Server address", + "type": "string", + "example": "192.168.1.1" + }, + "ipaddr": { + "summary": "The IP address", + "type": "string", + "example": "192.168.1.101" + }, + "netmask": { + "summary": "The network mask address", + "type": "string", + "example": "255.255.255.0" + }, + "gateway": { + "summary": "The gateway address", + "type": "string", + "example": "192.168.1.1" + }, + "primarydns": { + "summary": "The primary DNS address", + "type": "string", + "example": "192.168.1.1" + }, + "secondarydns": { + "summary": "The secondary DNS address", + "type": "string", + "example": "192.168.1.2" + }, + "endpoint":{ + "summary": "The host name or IP address", + "type": "string", + "example": "45.57.221.20" + }, + "packets": { + "summary": "The number of packets to send. Default is 15.", + "type": "integer", + "example": 10 + }, + "target": { + "summary": "The target IP address", + "type": "string", + "example": "45.57.221.20" + }, + "packetsTransmitted": { + "summary": "The number of packets sent", + "type": "integer", + "example": 10 + }, + "packetsReceived": { + "summary": "The number of packets received", + "type": "integer", + "example": 10 + }, + "packetLoss": { + "summary": "The number of packets lost", + "type": "string", + "example": "0.0" + }, + "tripMin": { + "summary": "The minimum amount of time to receive the packets", + "type": "string", + "example": "61.264" + }, + "tripAvg": { + "summary": "The average time to receive the packets", + "type": "string", + "example": "130.397" + }, + "tripMax": { + "summary": "The maximum amount of time to receive the packets", + "type": "string", + "example": "230.832" + }, + "tripStdDev": { + "summary": "The standard deviation for the trip", + "type": "string", + "example": "80.919" + }, + "error": { + "summary": "An error message", + "type": "string", + "example": "" + }, + "endpointName":{ + "summary": "An endpoint name returned by `getNamedEndpoints`", + "type": "string", + "example": "CMTS" + }, + "results": { + "summary": "The results from `traceroute`", + "type": "string", + "example": "<<>>" + }, + + "guid": { + "summary": "The globally unique identifier", + "type": "string", + "example": "" + }, + "quirks":{ + "summary": "Update `RDK-20093` string", + "type": "string", + "example": "RDK-20093" + }, + "server":{ + "summary": "STUN server", + "type": "string", + "example": "global.stun.twilio.com" + }, + "port":{ + "summary": "STUN server port", + "type": "integer", + "example": "3478" + }, + "sync":{ + "summary": "STUN server sync", + "type": "boolean", + "example": "true" + }, + "timeout":{ + "summary": "STUN server bind timeout", + "type": "integer", + "example": "30" + }, + "cache_timeout":{ + "summary": "STUN server cache timeout", + "type": "integer", + "example": "0" + }, + "prefix_length":{ + "summary": "ip v4/6 prefix length", + "type": "integer", + "example": "64" + } + }, + "methods": { + "getDefaultInterface":{ + "summary": "Gets the default network interface. The active network interface is defined as the one that can make requests to the external network. Returns one of the supported interfaces as per `getInterfaces`, or an empty value which indicates that there is no default network interface.", + "result": { + "type": "object", + "properties": { + "interface": { + "$ref": "#/definitions/interface" + }, + "success": { + "$ref": "#/common/success" + } + }, + "required": [ + "interface", + "success" + ] + } + }, + "getInterfaces":{ + "summary": "Returns a list of interfaces supported by this device including their state.", + "result": { + "type": "object", + "properties": { + "Interfaces": { + "summary": "An interface", + "type":"array", + "items": { + "type":"object", + "properties": { + "interface":{ + "$ref": "#/definitions/interface" + }, + "macAddress":{ + "summary": "Interface MAC address", + "type": "string", + "example": "AA:AA:AA:AA:AA:AA" + }, + "enabled":{ + "summary": "Whether the interface is currently enabled", + "type": "boolean", + "example": true + }, + "connected":{ + "summary": "Whether the interface is currently connected", + "type": "boolean", + "example": true + } + }, + "required": [ + "interface", + "macAddress", + "enabled", + "connected" + ] + } + }, + "success": { + "$ref": "#/common/success" + } + }, + "required": [ + "interfaces", + "success" + ] + } + }, + "getIPSettings":{ + "summary": "Gets the IP setting for the given interface.", + "params": { + "type":"object", + "properties": { + "interface": { + "$ref": "#/definitions/interface" + }, + "ipversion": { + "$ref": "#/definitions/ipversion" + } + }, + "required": [ + "interface" + ] + }, + "result": { + "type": "object", + "properties": { + "interface": { + "$ref": "#/definitions/interface" + }, + "ipversion": { + "$ref": "#/definitions/ipversion" + }, + "autoconfig": { + "$ref": "#/definitions/autoconfig" + }, + "dhcpserver": { + "$ref": "#/definitions/dhcpserver" + }, + "ipaddr": { + "$ref": "#/definitions/ipaddr" + }, + "netmask": { + "$ref": "#/definitions/netmask" + }, + "gateway": { + "$ref": "#/definitions/gateway" + }, + "primarydns": { + "$ref": "#/definitions/primarydns" + }, + "secondarydns": { + "$ref": "#/definitions/secondarydns" + }, + "prefix_length": { + "$ref": "#/definitions/prefix_length" + }, + "success": { + "$ref": "#/common/success" + } + }, + "required": [ + "interface", + "ipversion", + "autoconfig", + "ipaddr", + "netmask", + "gateway", + "primarydns", + "secondarydns", + "success" + ] + } + }, + "getNamedEndpoints":{ + "summary": "Returns a list of endpoint names. Currently supported endpoint names are: `CMTS`.", + "result": { + "type": "object", + "properties": { + "endpoints": { + "summary": "A list of supported endpoint names", + "type": "array", + "items": { + "type": "string", + "example": "CMTS" + } + }, + "success": { + "$ref": "#/common/success" + } + }, + "required": [ + "endpoints", + "success" + ] + } + }, + "getQuirks":{ + "summary": "Get standard string `RDK-20093`.", + "result": { + "type": "object", + "properties": { + "quirks": { + "$ref": "#/definitions/quirks" + }, + "success": { + "$ref": "#/common/success" + } + }, + "required": [ + "quirks", + "success" + ] + } + }, + "getStbIp":{ + "summary": "Gets the IP address of the default interface.", + "result": { + "type": "object", + "properties": { + "ip": { + "summary": "The IP address", + "type": "string", + "example": "192.168.1.101" + }, + "success": { + "$ref": "#/common/success" + } + }, + "required": [ + "ip", + "success" + ] + } + }, + "getSTBIPFamily":{ + "summary": "Gets the IP address of the default interface by address family.", + "params": { + "type":"object", + "properties": { + "family": { + "summary": "The address family to query", + "type": "string", + "example": "AF_INET" + } + }, + "required": [ + "family" + ] + }, + "result": { + "type": "object", + "properties": { + "ip": { + "summary": "The IP address", + "type": "string", + "example": "192.168.1.101" + }, + "success": { + "$ref": "#/common/success" + } + }, + "required": [ + "ip", + "success" + ] + } + }, + "isConnectedToInternet":{ + "summary": "Whether the device has internet connectivity. This API might take up to 2s to validate internet connectivity.", + "result": { + "type": "object", + "properties": { + "connectedToInternet": { + "summary": "`true` if internet connectivity is detected, otherwise `false`", + "type": "boolean", + "example": true + }, + "success": { + "$ref": "#/common/success" + } + }, + "required": [ + "connectedToInternet", + "success" + ] + } + }, + "isInterfaceEnabled":{ + "summary": "Whether the specified interface is enabled.", + "params": { + "type":"object", + "properties": { + "interface": { + "$ref": "#/definitions/interface" + } + }, + "required": [ + "interface" + ] + }, + "result": { + "type": "object", + "properties": { + "enabled":{ + "summary": "`true` if the interface is enabled, otherwise `false`", + "type": "boolean", + "example": true + }, + "success": { + "$ref": "#/common/success" + } + }, + "required": [ + "enabled", + "success" + ] + } + }, + "ping":{ + "summary": "Pings the specified endpoint with the specified number of packets.", + "params": { + "type":"object", + "properties": { + "endpoint":{ + "$ref": "#/definitions/endpoint" + }, + "packets": { + "$ref": "#/definitions/packets" + }, + "guid": { + "$ref": "#/definitions/guid" + } + }, + "required": [ + "endpoint", + "packets" + ] + }, + "result": { + "type": "object", + "properties": { + "target": { + "$ref": "#/definitions/target" + }, + "success": { + "$ref": "#/common/success" + }, + "packetsTransmitted": { + "$ref": "#/definitions/packetsTransmitted" + }, + "packetsReceived": { + "$ref": "#/definitions/packetsReceived" + }, + "packetLoss": { + "$ref": "#/definitions/packetLoss" + }, + "tripMin": { + "$ref": "#/definitions/tripMin" + }, + "tripAvg": { + "$ref": "#/definitions/tripAvg" + }, + "tripMax": { + "$ref": "#/definitions/tripMax" + }, + "tripStdDev": { + "$ref": "#/definitions/tripStdDev" + }, + "error": { + "$ref": "#/definitions/error" + }, + "guid": { + "$ref": "#/definitions/guid" + } + }, + "required": [ + "target", + "success", + "packetsTransmitted", + "packetsReceived", + "packetLoss", + "tripMin", + "tripAvg", + "tripMax", + "tripStdDev", + "error", + "guid" + ] + } + }, + "pingNamedEndpoint":{ + "summary": "Pings the specified named endpoint with the specified number of packets. Only names returned by `getNamedEndpoints` can be used. The named endpoint is resolved to a specific host or IP address on the device side based on the `endpointName`.", + "params": { + "type":"object", + "properties": { + "endpointName":{ + "$ref": "#/definitions/endpointName" + }, + "packets": { + "$ref": "#/definitions/packets" + }, + "guid": { + "$ref": "#/definitions/guid" + } + }, + "required": [ + "endpointName", + "packets" + ] + }, + "result": { + "type": "object", + "properties": { + "target": { + "$ref": "#/definitions/target" + }, + "success": { + "$ref": "#/common/success" + }, + "packetsTransmitted": { + "$ref": "#/definitions/packetsTransmitted" + }, + "packetsReceived": { + "$ref": "#/definitions/packetsReceived" + }, + "packetLoss": { + "$ref": "#/definitions/packetLoss" + }, + "tripMin": { + "$ref": "#/definitions/tripMin" + }, + "tripAvg": { + "$ref": "#/definitions/tripAvg" + }, + "tripMax": { + "$ref": "#/definitions/tripMax" + }, + "tripStdDev": { + "$ref": "#/definitions/tripStdDev" + }, + "error": { + "$ref": "#/definitions/error" + }, + "guid": { + "$ref": "#/definitions/guid" + } + }, + "required": [ + "target", + "success", + "packetsTransmitted", + "packetsReceived", + "packetLoss", + "tripMin", + "tripAvg", + "tripMax", + "tripStdDev", + "error", + "guid" + ] + } + }, + "setConnectivityTestEndpoints":{ + "summary": "Sets the default list of endpoints used for a connectivity test. Maximum number of endpoints is 5.", + "params": { + "type":"object", + "properties": { + "endpoints": { + "summary": "A list of endpoints to test", + "type": "array", + "items": { + "type": "string", + "example": "xfinity.com:8080" + } + } + }, + "required": [ + "endpoints" + ] + }, + "result": { + "$ref": "#/common/result" + } + }, + "setDefaultInterface":{ + "summary": "Sets the default interface. The call fails if the interface is not enabled.", + "events":{ + "onInterfaceStatusChanged" : "Triggered when interface’s status changes to enabled/disabled.", + "onConnectionStatusChanged" : "Triggered when a connection is made or lost.", + "onIPAddressStatusChanged" : "Triggered when each IP address is lost or acquired.", + "onDefaultInterfaceChanged" : "Triggered when the default interface changes, regardless if it's from a system operation or through the `setDefaultInterface` method." + }, + "params": { + "type":"object", + "properties": { + "interface": { + "$ref": "#/definitions/interface" + }, + "persist":{ + "summary": "Whether the default interface setting persists after reboot. When `true`, this interface is enabled as the default interface currently AND on the next reboot. When `false`, this interface is only the default during this session.", + "type": "boolean", + "example": true + } + }, + "required": [ + "interface", + "persist" + ] + }, + "result": { + "$ref": "#/common/result" + } + }, + "setInterfaceEnabled":{ + "summary": "Enables the specified interface.", + "events": { + "onInterfaceStatusChanged" : "Triggered when interface’s status changes to enabled/disabled." + }, + "params": { + "type": "object", + "properties": { + "interface": { + "$ref": "#/definitions/interface" + }, + "enabled": { + "summary": "Enables the interface when `true`. Disables the interface when `false`", + "type": "boolean", + "example": true + }, + "persist":{ + "summary": "Whether the interface setting persists after reboot. When `true`, this interface is enabled currently AND on the next reboot. When `false`, this interface is only enabled during this session.", + "type": "boolean", + "example": true + } + }, + "required": [ + "interface", + "enabled", + "persist" + ] + }, + "result": { + "$ref": "#/common/result" + } + }, + "setIPSettings":{ + "summary": "Sets the IP settings.All the inputs are mandatory for v1. But for v2, the interface and autconfig params are mandatory input to autoconfig IP settings & other parameters not required. For manual IP, all the input parameters are mandatory except secondaryDNS.", + "events":{ + "onIPAddressStatusChanged" : "Triggered when each IP address is lost or acquired." + }, + "params": { + "type":"object", + "properties": { + "interface": { + "$ref": "#/definitions/interface" + }, + "ipversion": { + "$ref": "#/definitions/sipversion" + }, + "autoconfig": { + "$ref": "#/definitions/autoconfig" + }, + "ipaddr": { + "$ref": "#/definitions/ipaddr" + }, + "netmask": { + "$ref": "#/definitions/netmask" + }, + "gateway": { + "$ref": "#/definitions/gateway" + }, + "primarydns": { + "$ref": "#/definitions/primarydns" + }, + "secondarydns": { + "$ref": "#/definitions/secondarydns" + } + }, + "required": [ + "interface", + "ipversion", + "autoconfig", + "ipaddr", + "netmask", + "gateway", + "primarydns", + "secondarydns" + ] + }, + "result": { + "type": "object", + "properties": { + "supported":{ + "summary": "`true` if setting the IP settings are supported, otherwise `false` if not supported.", + "type":"boolean", + "example": true + }, + "success": { + "$ref": "#/common/success" + } + }, + "required": [ + "supported", + "success" + ] + } + }, + "getPublicIP":{ + "summary": "It allows either zero parameter or with only interface and ipv6 parameter to determine WAN ip address.", + "params": { + "type":"object", + "summary":"it allows empty parameter too", + "properties": { + "iface": { + "$ref": "#/definitions/interface" + }, + "ipv6": { + "$ref": "#/definitions/ipv6" + } + }, + "required": [ + "iface", + "ipv6" + ] + }, + "result": { + "type": "object", + "properties": { + "public_ip":{ + "summary": "Returns an public ip of the device ,if ipv6 is `true`,returns IPv6 public ip , otherwise returns IPv4 public ip", + "type":"string", + "example": "69.136.49.95" + }, + "success": { + "$ref": "#/common/success" + } + }, + "required": [ + "public_ip", + "success" + ] + } + }, + "setStunEndPoint":{ + "summary": "Set the Stun Endpoint used for getPublicIP.", + "params": { + "type":"object", + "properties": { + "server": { + "$ref": "#/definitions/server" + }, + "port": { + "$ref": "#/definitions/port" + }, + "sync": { + "$ref": "#/definitions/sync" + }, + "timeout": { + "$ref": "#/definitions/timeout" + }, + "cache_timeout": { + "$ref": "#/definitions/cache_timeout" + } + }, + "required": [ + "server", + "port", + "sync", + "timeout", + "cache_timeout" + ] + }, + "result": { + "type": "object", + "properties": { + "success": { + "$ref": "#/common/success" + } + }, + "required": [ + "public_ip", + "success" + ] + } + }, + "trace":{ + "summary": "Traces the specified endpoint with the specified number of packets using `traceroute`.", + "params": { + "type":"object", + "properties": { + "endpoint":{ + "$ref": "#/definitions/endpoint" + }, + "packets": { + "$ref": "#/definitions/packets" + } + }, + "required": [ + "endpoint", + "packets" + ] + }, + "result": { + "type": "object", + "properties": { + "target": { + "$ref": "#/definitions/target" + }, + "success": { + "$ref": "#/common/success" + }, + "error": { + "$ref": "#/definitions/error" + }, + "results": { + "$ref": "#/definitions/results" + } + }, + "required": [ + "target", + "success", + "error", + "results" + ] + } + }, + "traceNamedEndpoint":{ + "summary": "Traces the specified named endpoint with the specified number of packets using `traceroute`.", + "params": { + "type":"object", + "properties": { + "endpointName":{ + "$ref": "#/definitions/endpointName" + }, + "packets": { + "$ref": "#/definitions/packets" + } + }, + "required": [ + "endpointName", + "packets" + ] + }, + "result": { + "type": "object", + "properties": { + "target": { + "$ref": "#/definitions/target" + }, + "success": { + "$ref": "#/common/success" + }, + "error": { + "$ref": "#/definitions/error" + }, + "results": { + "$ref": "#/definitions/results" + } + }, + "required": [ + "target", + "success", + "error", + "results" + ] + } + } + }, + "events": { + "onInterfaceStatusChanged":{ + "summary": "Triggered when an interface becomes enabled or disabled.", + "params": { + "type": "object", + "properties": { + "interface":{ + "$ref": "#/definitions/interface" + }, + "enabled":{ + "summary": "Whether the interface is enabled (`true`) or disabled (`false`)", + "type": "boolean", + "example": true + } + }, + "required": [ + "interface", + "enabled" + ] + } + }, + "onConnectionStatusChanged":{ + "summary": "Triggered when a connection is made or lost.", + "params": { + "type": "object", + "properties": { + "interface":{ + "$ref": "#/definitions/interface" + }, + "status":{ + "summary": "Whether the interface is currently connected or disconnected", + "type": "string", + "enum": ["`CONNECTED`", "`DISCONNECTED`" ], + "example": "CONNECTED" + } + }, + "required": [ + "interface", + "status" + ] + } + }, + "onIPAddressStatusChanged":{ + "summary": "Triggered when an IP Address is assigned or lost.", + "params": { + "type": "object", + "properties": { + "interface":{ + "$ref": "#/definitions/interface" + }, + "ip6Address":{ + "summary": "The IPv6 address for the interface", + "type": "string", + "example": "2001:0xx8:85a3:0000:0000:8x2x:0370:7334" + }, + "ip4address":{ + "summary": "The IPv4 address for the interface", + "type": "string", + "example": "192.168.1.2" + }, + "status":{ + "summary": "Whether IP address was acquired or lost", + "type": "string", + "enum": ["`ACQUIRED`", "`LOST`" ], + "example": "ACQUIRED" + } + }, + "required": [ + "interface", + "ip6Address", + "ip4address", + "status" + ] + } + }, + "onDefaultInterfaceChanged":{ + "summary": "Triggered when the default interface changes, regardless if it's from a system operation or through the `setDefaultInterface` method.", + "params": { + "type": "object", + "properties": { + "oldInterfaceName":{ + "summary": "The previous interface that was changed", + "type": "string", + "example": "ETHERNET" + }, + "newInterfaceName":{ + "summary": "The current interface", + "type": "string", + "example": "WIFI" + } + }, + "required": [ + "oldInterfaceName", + "newInterfaceName" + ] + } + } + } +} diff --git a/LgiNetwork/NetworkPlugin.json b/LgiNetwork/NetworkPlugin.json new file mode 100644 index 0000000000..fac398e12d --- /dev/null +++ b/LgiNetwork/NetworkPlugin.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/rdkcentral/rdkservices/main/Tools/json_generator/schemas/plugin.schema.json", + "info": { + "title": "NetworkPlugin", + "callsign": "org.rdk.Network", + "locator": "libWPEFrameworkNetwork.so", + "status": "production", + "description": "The `Network` plugin allows you to manage network interfaces on a set-top box." + }, + "interface": { + "$ref": "Network.json#" + } +} diff --git a/LgiNetwork/NetworkTraceroute.cpp b/LgiNetwork/NetworkTraceroute.cpp new file mode 100644 index 0000000000..d33ee1b643 --- /dev/null +++ b/LgiNetwork/NetworkTraceroute.cpp @@ -0,0 +1,156 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "Network.h" +#include +#include + +//e.g. traceroute -w 3 -m 6 -q 3 192.168.1.100 52 +#define CMD_TRACEROUTE "traceroute -w %d -m %d -q %d %s %d 2>&1" +//e.g. traceroute6 -i eth0 -w 3 -m 6 -q 3 fe80::5a19:f8ff:fe37:7a3d 52 +#define CMD_TRACEROUTE6 "traceroute6 -i %s -w %d -m %d -q %d %s %d 2>&1" + +#define DEFAULT_PACKET_LENGTH 52 +#define DEFAULT_WAIT 3 +#define DEFAULT_MAX_HOPS 6 +#define DEFAULT_QUERIES 3 + + +namespace WPEFramework { + namespace Plugin { + + bool Network::_doTraceNamedEndpoint(std::string &endpointName, int packets, JsonObject &response) + { + std::string interface; + std::string endpoint = ""; + std::string type = ""; + + if (endpointName != "CMTS") // currently we only support CMTS + { + response["error"] = "Unsupported named endpoint"; + } + else if (_getDefaultInterface(interface, endpoint, type) && !endpoint.empty()) + { + return _doTrace(endpoint, packets, response); + } + else + { + response["error"] = "Could not find CMTS gateway"; + } + + return false; + } + + bool Network::_doTrace(std::string &endpoint, int packets, JsonObject &response) + { + std::string output = ""; + std::string error = ""; + std::string interface = ""; + std::string gateway; + std::string type; + int wait = DEFAULT_WAIT; + int maxHops = DEFAULT_MAX_HOPS; + int packetLen = DEFAULT_PACKET_LENGTH; + char command[MAX_COMMAND_LENGTH]; + bool result = false; + + if (packets <= 0) + { + packets = DEFAULT_QUERIES; + } + + if (endpoint.empty()) + { + error = "Invalid endpoint"; + } + else if (!NetUtils::isIPV4(endpoint) && + !NetUtils::isIPV6(endpoint) && + !NetUtils::isValidEndpointURL(endpoint)) + { + error = "Invalid endpoint"; + } + else if (!_getDefaultInterface(interface, gateway, type) || interface.empty()) + { + error = "Could not get default interface"; + } + else + { + if (NetUtils::isIPV6(endpoint)) + { + snprintf(command, MAX_COMMAND_LENGTH, CMD_TRACEROUTE6, + interface.c_str(), + wait, + maxHops, + packets, + endpoint.c_str(), + packetLen); + } + else + { + snprintf(command, MAX_COMMAND_LENGTH, CMD_TRACEROUTE, + wait, + maxHops, + packets, + endpoint.c_str(), + packetLen); + } + + if (NetUtils::execCmd(command, output, &result) < 0) + { + error = "Failed to execute traceroute command"; + } + else if (!result) // check the command return status + { + error = "Failed to execute traceroute"; + } + } + + if (error.empty()) + { + // We return the entire output of the trace command but since this contains newlines it is not valid as + // a json value so we will parse the output into an array of strings, one element for each line. + JsonArray list; + if (!output.empty()) + { + std::string::size_type last = 0; + std::string::size_type next = output.find('\n'); + while (next != std::string::npos) + { + list.Add(output.substr(last, next - last)); + last = next + 1; + next = output.find('\n', last); + } + list.Add(output.substr(last)); + } + + response["target"] = endpoint; + response["results"] = list; + response["error"] = ""; + return true; + } + else + { + response["target"] = endpoint; + response["results"] = ""; + response["error"] = error; + return false; + } + } + } // namespace Plugin +} // namespace WPEFramework diff --git a/LgiNetwork/PingNotifier.cpp b/LgiNetwork/PingNotifier.cpp new file mode 100644 index 0000000000..682cc4613e --- /dev/null +++ b/LgiNetwork/PingNotifier.cpp @@ -0,0 +1,238 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "Network.h" + +#include "UtilsLogging.h" + +using namespace std; + +namespace WPEFramework +{ + namespace Plugin + { + /** + * @ingroup SERVMGR_PING_API + */ + JsonObject Network::_doPing(const string& guid, const string& endPoint, int packets) + { + LOGINFO("PingService calling ping"); + JsonObject pingResult; + string interface = ""; + string gateway; + string type; + bool result = false; + string outputFile; + FILE *fp = NULL; + + pingResult["target"] = endPoint; + + if(NetUtils::isIPV6(endPoint)) + { + LOGINFO("%s: Endpoint '%s' is ipv6", __FUNCTION__,endPoint.c_str()); + } + else if(NetUtils::isIPV4(endPoint)) + { + LOGINFO("%s: Endpoint '%s' is ipv4", __FUNCTION__,endPoint.c_str()); + } + else if(NetUtils::isValidEndpointURL(endPoint)) + { + LOGINFO("%s: Endpoint '%s' is url", __FUNCTION__,endPoint.c_str()); + } + else + { + LOGERR("%s: Endpoint '%s' is not valid", __FUNCTION__,endPoint.c_str()); + pingResult["success"] = false; + pingResult["error"] = "invalid input for endpoint: " + endPoint; + return pingResult; + } + + if (!_getDefaultInterface(interface, gateway, type) || interface.empty()) + { + LOGERR("%s: Could not get default interface", __FUNCTION__); + pingResult["success"] = false; + pingResult["error"] = "Could not get default interface"; + return pingResult; + } + + char cmd [1000] = {0x0}; + if (NetUtils::isIPV6(endPoint)) + { + snprintf(cmd, sizeof(cmd), "ping6 -I %s -c %d -W 5 '%s' 2>&1", + interface.c_str(), packets, endPoint.c_str()); + } + else + { + snprintf(cmd, sizeof(cmd), "ping -c %d -W 5 '%s' 2>&1", + packets, endPoint.c_str()); + } + + LOGWARN("ping command: %s", cmd); + + // Run the command and dump the output to /tmp/pingoutput + if (NetUtils::execCmd(cmd, outputFile, &result, "pingoutput") < 0) + { + LOGERR("%s: SERVICEMANAGER_FILE_ERROR: Can't open pipe for command '%s' for read mode: %s" + , __FUNCTION__, cmd, strerror(errno)); + + pingResult["success"] = false; + pingResult["error"] = "Could not run command"; + } + else if (!result) // check the command return status + { + pingResult["success"] = false; + pingResult["error"] = "Could not ping endpoint"; + } + else if ((fp = fopen(outputFile.c_str(), "r")) == NULL) + { + pingResult["success"] = false; + pingResult["error"] = "Could not read ping result"; + } + else + { + pingResult["success"] = true; + pingResult["error"] = ""; + + char linearray[1000]={0x0}; + while(fgets(linearray, sizeof(linearray), fp) != NULL) + { + // remove newline from linearray + linearray[strcspn(linearray, "\n")] = '\0'; + string line(linearray); + LOGINFO("ping result: %s", line.c_str()); + + if( line.find( "packet" ) != string::npos ) + { + //Example: 10 packets transmitted, 10 packets received, 0% packet loss + + stringstream ss( line ); + int transCount; + ss >> transCount; + pingResult["packetsTransmitted"] = transCount; + + string token; + getline( ss, token, ',' ); + getline( ss, token, ',' ); + stringstream ss2( token ); + int rxCount; + ss2 >> rxCount; + pingResult["packetsReceived"] = rxCount; + + getline( ss, token, ',' ); + string prefix = token.substr(0, token.find("%")); + //double lossFloat = ::atof(prefix.c_str()); + //pingResult["packetLoss"] = lossFloat; + pingResult["packetLoss"] = prefix.c_str(); + + } + else if( line.find( "min/avg/max" ) != string::npos ) + { + //Example: round-trip min/avg/max = 17.038/18.310/20.197 ms + + stringstream ss( line ); + string fullpath; + getline( ss, fullpath, '=' ); + getline( ss, fullpath, '=' ); + + string prefix; + int index = fullpath.find("/"); + if (index >= 0) + { + prefix = fullpath.substr(0, fullpath.find("/")); + pingResult["tripMin"] = prefix.c_str(); + } + + index = fullpath.find("/"); + if (index >= 0) + { + fullpath = fullpath.substr(index + 1, fullpath.length()); + prefix = fullpath.substr(0, fullpath.find("/")); + pingResult["tripAvg"] = prefix.c_str(); + } + + index = fullpath.find("/"); + if (index >= 0) + { + fullpath = fullpath.substr(index + 1, fullpath.length()); + prefix = fullpath.substr(0, fullpath.find("/")); + pingResult["tripMax"] = prefix.c_str(); + } + + index = fullpath.find("/"); + if (index >= 0) + { + fullpath = fullpath.substr(index + 1, fullpath.length()); + pingResult["tripStdDev"] = fullpath.c_str(); + } + } + else if( line.find( "bad" ) != string::npos ) + { + pingResult["success"] = false; + pingResult["error"] = "Bad Address"; + } + } + fclose(fp); + + // clear up + remove(outputFile.c_str()); + } + + pingResult["guid"] = guid; + + return pingResult; + } + + /** + * @ingroup SERVMGR_PING_API + */ + JsonObject Network::_doPingNamedEndpoint(const string& guid, const string& endpointName, int packets) + { + LOGINFO("PingService calling pingNamedEndpoint for %s", endpointName.c_str()); + string error = ""; + JsonObject returnResult; + + if (endpointName == "CMTS") + { + std::string interface; + std::string type; + std::string gateway = ""; + if (_getDefaultInterface(interface, gateway, type) && !gateway.empty()) + { + returnResult = _doPing(guid, gateway, packets); + } + else + { + LOGERR("%s: Can't get gateway address for interface CMTS", __FUNCTION__); + error = "Could not find interface"; + } + } + else + error = "Invalid endpoint name"; + + if (error != "") + { + returnResult["target"] = endpointName; + returnResult["success"] = false; + returnResult["error"] = error; + } + + return returnResult; + } + } +} diff --git a/LgiNetwork/README.md b/LgiNetwork/README.md new file mode 100644 index 0000000000..df26c88e0f --- /dev/null +++ b/LgiNetwork/README.md @@ -0,0 +1,35 @@ +----------------- +Build: + +bitbake thunder-plugins +----------------- + +Test: + +Commands to use +---------------- +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.getQuirks"}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.setApiVersionNumber", "params":{"version":5}}' http://127.0.0.1:9998/jsonrpc +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.getApiVersionNumber"}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.getInterfaces"}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.getDefaultInterface"}' http://127.0.0.1:9998/jsonrpc +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.setDefaultInterface", "params":{"interface":"WIFI", "persist":false}}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.isInterfaceEnabled", "params":{"interface":"WIFI"}}' http://127.0.0.1:9998/jsonrpc +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.setInterfaceEnabled", "params":{"interface":"WIFI", "enabled":true, "persist":true}}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.getStbIp"}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.getNamedEndpoints"}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.trace", "params":{"endpoint":"45.57.221.20", "packets": 3}}' http://127.0.0.1:9998/jsonrpc +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.traceNamedEndpoint", "params":{"endpointName":"CMTS", "packets": 3}}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.ping", "params":{"endpoint":"45.57.221.20", "packets": 3}}' http://127.0.0.1:9998/jsonrpc +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.pingNamedEndpoint", "params":{"endpointName":"CMTS", "packets": 3}}' http://127.0.0.1:9998/jsonrpc + + + diff --git a/LgiNetwork/dbus/gdbus-codegen-impl/gencode.sh b/LgiNetwork/dbus/gdbus-codegen-impl/gencode.sh new file mode 100644 index 0000000000..8254c768d4 --- /dev/null +++ b/LgiNetwork/dbus/gdbus-codegen-impl/gencode.sh @@ -0,0 +1,3 @@ +#!/bin/bash +gdbus-codegen lginetwork_dbus_api --interface-prefix com.lgi.rdk.utils.networkconfig1. ../../.././onemw-src/networking/om-netconfig/api/dbus/com.lgi.rdk.utils.networkconfig1.xml + diff --git a/LgiNetwork/dbus/gdbus-codegen-impl/lginetwork_dbus_api.c b/LgiNetwork/dbus/gdbus-codegen-impl/lginetwork_dbus_api.c new file mode 100644 index 0000000000..8ddbae0dcc --- /dev/null +++ b/LgiNetwork/dbus/gdbus-codegen-impl/lginetwork_dbus_api.c @@ -0,0 +1,3854 @@ +/* + * Generated by gdbus-codegen 2.56.4. DO NOT EDIT. + * + * The license of this code is the same as for the D-Bus interface description + * it was derived from. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "lginetwork_dbus_api.h" + +#include +#ifdef G_OS_UNIX +# include +#endif + +typedef struct +{ + GDBusArgInfo parent_struct; + gboolean use_gvariant; +} _ExtendedGDBusArgInfo; + +typedef struct +{ + GDBusMethodInfo parent_struct; + const gchar *signal_name; + gboolean pass_fdlist; +} _ExtendedGDBusMethodInfo; + +typedef struct +{ + GDBusSignalInfo parent_struct; + const gchar *signal_name; +} _ExtendedGDBusSignalInfo; + +typedef struct +{ + GDBusPropertyInfo parent_struct; + const gchar *hyphen_name; + gboolean use_gvariant; +} _ExtendedGDBusPropertyInfo; + +typedef struct +{ + GDBusInterfaceInfo parent_struct; + const gchar *hyphen_name; +} _ExtendedGDBusInterfaceInfo; + +typedef struct +{ + const _ExtendedGDBusPropertyInfo *info; + guint prop_id; + GValue orig_value; /* the value before the change */ +} ChangedProperty; + +static void +_changed_property_free (ChangedProperty *data) +{ + g_value_unset (&data->orig_value); + g_free (data); +} + +static gboolean +_g_strv_equal0 (gchar **a, gchar **b) +{ + gboolean ret = FALSE; + guint n; + if (a == NULL && b == NULL) + { + ret = TRUE; + goto out; + } + if (a == NULL || b == NULL) + goto out; + if (g_strv_length (a) != g_strv_length (b)) + goto out; + for (n = 0; a[n] != NULL; n++) + if (g_strcmp0 (a[n], b[n]) != 0) + goto out; + ret = TRUE; +out: + return ret; +} + +static gboolean +_g_variant_equal0 (GVariant *a, GVariant *b) +{ + gboolean ret = FALSE; + if (a == NULL && b == NULL) + { + ret = TRUE; + goto out; + } + if (a == NULL || b == NULL) + goto out; + ret = g_variant_equal (a, b); +out: + return ret; +} + +G_GNUC_UNUSED static gboolean +_g_value_equal (const GValue *a, const GValue *b) +{ + gboolean ret = FALSE; + g_assert (G_VALUE_TYPE (a) == G_VALUE_TYPE (b)); + switch (G_VALUE_TYPE (a)) + { + case G_TYPE_BOOLEAN: + ret = (g_value_get_boolean (a) == g_value_get_boolean (b)); + break; + case G_TYPE_UCHAR: + ret = (g_value_get_uchar (a) == g_value_get_uchar (b)); + break; + case G_TYPE_INT: + ret = (g_value_get_int (a) == g_value_get_int (b)); + break; + case G_TYPE_UINT: + ret = (g_value_get_uint (a) == g_value_get_uint (b)); + break; + case G_TYPE_INT64: + ret = (g_value_get_int64 (a) == g_value_get_int64 (b)); + break; + case G_TYPE_UINT64: + ret = (g_value_get_uint64 (a) == g_value_get_uint64 (b)); + break; + case G_TYPE_DOUBLE: + { + /* Avoid -Wfloat-equal warnings by doing a direct bit compare */ + gdouble da = g_value_get_double (a); + gdouble db = g_value_get_double (b); + ret = memcmp (&da, &db, sizeof (gdouble)) == 0; + } + break; + case G_TYPE_STRING: + ret = (g_strcmp0 (g_value_get_string (a), g_value_get_string (b)) == 0); + break; + case G_TYPE_VARIANT: + ret = _g_variant_equal0 (g_value_get_variant (a), g_value_get_variant (b)); + break; + default: + if (G_VALUE_TYPE (a) == G_TYPE_STRV) + ret = _g_strv_equal0 (g_value_get_boxed (a), g_value_get_boxed (b)); + else + g_critical ("_g_value_equal() does not handle type %s", g_type_name (G_VALUE_TYPE (a))); + break; + } + return ret; +} + +/* ------------------------------------------------------------------------ + * Code for interface com.lgi.rdk.utils.networkconfig1 + * ------------------------------------------------------------------------ + */ + +/** + * SECTION:Networkconfig1 + * @title: Networkconfig1 + * @short_description: Generated C code for the com.lgi.rdk.utils.networkconfig1 D-Bus interface + * + * This section contains code for working with the com.lgi.rdk.utils.networkconfig1 D-Bus interface in C. + */ + +/* ---- Introspection data for com.lgi.rdk.utils.networkconfig1 ---- */ + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_interfaces_OUT_ARG_count = +{ + { + -1, + (gchar *) "count", + (gchar *) "u", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_interfaces_OUT_ARG_ids = +{ + { + -1, + (gchar *) "ids", + (gchar *) "as", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_get_interfaces_OUT_ARG_pointers[] = +{ + &_networkconfig1_method_info_get_interfaces_OUT_ARG_count, + &_networkconfig1_method_info_get_interfaces_OUT_ARG_ids, + NULL +}; + +static const _ExtendedGDBusMethodInfo _networkconfig1_method_info_get_interfaces = +{ + { + -1, + (gchar *) "GetInterfaces", + NULL, + (GDBusArgInfo **) &_networkconfig1_method_info_get_interfaces_OUT_ARG_pointers, + NULL + }, + "handle-get-interfaces", + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_enable_IN_ARG_id = +{ + { + -1, + (gchar *) "id", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_enable_IN_ARG_enable = +{ + { + -1, + (gchar *) "enable", + (gchar *) "b", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_enable_IN_ARG_pointers[] = +{ + &_networkconfig1_method_info_enable_IN_ARG_id, + &_networkconfig1_method_info_enable_IN_ARG_enable, + NULL +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_enable_OUT_ARG_status = +{ + { + -1, + (gchar *) "status", + (gchar *) "i", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_enable_OUT_ARG_pointers[] = +{ + &_networkconfig1_method_info_enable_OUT_ARG_status, + NULL +}; + +static const _ExtendedGDBusMethodInfo _networkconfig1_method_info_enable = +{ + { + -1, + (gchar *) "Enable", + (GDBusArgInfo **) &_networkconfig1_method_info_enable_IN_ARG_pointers, + (GDBusArgInfo **) &_networkconfig1_method_info_enable_OUT_ARG_pointers, + NULL + }, + "handle-enable", + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_is_enabled_IN_ARG_id = +{ + { + -1, + (gchar *) "id", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_is_enabled_IN_ARG_pointers[] = +{ + &_networkconfig1_method_info_is_enabled_IN_ARG_id, + NULL +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_is_enabled_OUT_ARG_status = +{ + { + -1, + (gchar *) "status", + (gchar *) "i", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_is_enabled_OUT_ARG_enabled = +{ + { + -1, + (gchar *) "enabled", + (gchar *) "b", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_is_enabled_OUT_ARG_pointers[] = +{ + &_networkconfig1_method_info_is_enabled_OUT_ARG_status, + &_networkconfig1_method_info_is_enabled_OUT_ARG_enabled, + NULL +}; + +static const _ExtendedGDBusMethodInfo _networkconfig1_method_info_is_enabled = +{ + { + -1, + (gchar *) "IsEnabled", + (GDBusArgInfo **) &_networkconfig1_method_info_is_enabled_IN_ARG_pointers, + (GDBusArgInfo **) &_networkconfig1_method_info_is_enabled_OUT_ARG_pointers, + NULL + }, + "handle-is-enabled", + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_active_interface_OUT_ARG_id = +{ + { + -1, + (gchar *) "id", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_active_interface_OUT_ARG_status = +{ + { + -1, + (gchar *) "status", + (gchar *) "i", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_get_active_interface_OUT_ARG_pointers[] = +{ + &_networkconfig1_method_info_get_active_interface_OUT_ARG_id, + &_networkconfig1_method_info_get_active_interface_OUT_ARG_status, + NULL +}; + +static const _ExtendedGDBusMethodInfo _networkconfig1_method_info_get_active_interface = +{ + { + -1, + (gchar *) "GetActiveInterface", + NULL, + (GDBusArgInfo **) &_networkconfig1_method_info_get_active_interface_OUT_ARG_pointers, + NULL + }, + "handle-get-active-interface", + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_set_active_interface_IN_ARG_id = +{ + { + -1, + (gchar *) "id", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_set_active_interface_IN_ARG_pointers[] = +{ + &_networkconfig1_method_info_set_active_interface_IN_ARG_id, + NULL +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_set_active_interface_OUT_ARG_status = +{ + { + -1, + (gchar *) "status", + (gchar *) "i", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_set_active_interface_OUT_ARG_pointers[] = +{ + &_networkconfig1_method_info_set_active_interface_OUT_ARG_status, + NULL +}; + +static const _ExtendedGDBusMethodInfo _networkconfig1_method_info_set_active_interface = +{ + { + -1, + (gchar *) "SetActiveInterface", + (GDBusArgInfo **) &_networkconfig1_method_info_set_active_interface_IN_ARG_pointers, + (GDBusArgInfo **) &_networkconfig1_method_info_set_active_interface_OUT_ARG_pointers, + NULL + }, + "handle-set-active-interface", + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_params_IN_ARG_id = +{ + { + -1, + (gchar *) "id", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_get_params_IN_ARG_pointers[] = +{ + &_networkconfig1_method_info_get_params_IN_ARG_id, + NULL +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_params_OUT_ARG_status = +{ + { + -1, + (gchar *) "status", + (gchar *) "i", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_params_OUT_ARG_count = +{ + { + -1, + (gchar *) "count", + (gchar *) "u", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_params_OUT_ARG_params = +{ + { + -1, + (gchar *) "params", + (gchar *) "a{ss}", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_get_params_OUT_ARG_pointers[] = +{ + &_networkconfig1_method_info_get_params_OUT_ARG_status, + &_networkconfig1_method_info_get_params_OUT_ARG_count, + &_networkconfig1_method_info_get_params_OUT_ARG_params, + NULL +}; + +static const _ExtendedGDBusMethodInfo _networkconfig1_method_info_get_params = +{ + { + -1, + (gchar *) "GetParams", + (GDBusArgInfo **) &_networkconfig1_method_info_get_params_IN_ARG_pointers, + (GDBusArgInfo **) &_networkconfig1_method_info_get_params_OUT_ARG_pointers, + NULL + }, + "handle-get-params", + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_param_IN_ARG_id = +{ + { + -1, + (gchar *) "id", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_param_IN_ARG_paramName = +{ + { + -1, + (gchar *) "paramName", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_get_param_IN_ARG_pointers[] = +{ + &_networkconfig1_method_info_get_param_IN_ARG_id, + &_networkconfig1_method_info_get_param_IN_ARG_paramName, + NULL +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_param_OUT_ARG_status = +{ + { + -1, + (gchar *) "status", + (gchar *) "i", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_param_OUT_ARG_paramValue = +{ + { + -1, + (gchar *) "paramValue", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_get_param_OUT_ARG_pointers[] = +{ + &_networkconfig1_method_info_get_param_OUT_ARG_status, + &_networkconfig1_method_info_get_param_OUT_ARG_paramValue, + NULL +}; + +static const _ExtendedGDBusMethodInfo _networkconfig1_method_info_get_param = +{ + { + -1, + (gchar *) "GetParam", + (GDBusArgInfo **) &_networkconfig1_method_info_get_param_IN_ARG_pointers, + (GDBusArgInfo **) &_networkconfig1_method_info_get_param_OUT_ARG_pointers, + NULL + }, + "handle-get-param", + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_set_param_IN_ARG_id = +{ + { + -1, + (gchar *) "id", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_set_param_IN_ARG_paramName = +{ + { + -1, + (gchar *) "paramName", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_set_param_IN_ARG_paramValue = +{ + { + -1, + (gchar *) "paramValue", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_set_param_IN_ARG_pointers[] = +{ + &_networkconfig1_method_info_set_param_IN_ARG_id, + &_networkconfig1_method_info_set_param_IN_ARG_paramName, + &_networkconfig1_method_info_set_param_IN_ARG_paramValue, + NULL +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_set_param_OUT_ARG_status = +{ + { + -1, + (gchar *) "status", + (gchar *) "i", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_set_param_OUT_ARG_pointers[] = +{ + &_networkconfig1_method_info_set_param_OUT_ARG_status, + NULL +}; + +static const _ExtendedGDBusMethodInfo _networkconfig1_method_info_set_param = +{ + { + -1, + (gchar *) "SetParam", + (GDBusArgInfo **) &_networkconfig1_method_info_set_param_IN_ARG_pointers, + (GDBusArgInfo **) &_networkconfig1_method_info_set_param_OUT_ARG_pointers, + NULL + }, + "handle-set-param", + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_dhcprenew_OUT_ARG_status = +{ + { + -1, + (gchar *) "status", + (gchar *) "i", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_dhcprenew_OUT_ARG_pointers[] = +{ + &_networkconfig1_method_info_dhcprenew_OUT_ARG_status, + NULL +}; + +static const _ExtendedGDBusMethodInfo _networkconfig1_method_info_dhcprenew = +{ + { + -1, + (gchar *) "DHCPRenew", + NULL, + (GDBusArgInfo **) &_networkconfig1_method_info_dhcprenew_OUT_ARG_pointers, + NULL + }, + "handle-dhcprenew", + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_status_IN_ARG_id = +{ + { + -1, + (gchar *) "id", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_get_status_IN_ARG_pointers[] = +{ + &_networkconfig1_method_info_get_status_IN_ARG_id, + NULL +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_status_OUT_ARG_status = +{ + { + -1, + (gchar *) "status", + (gchar *) "i", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_get_status_OUT_ARG_ifaceStatus = +{ + { + -1, + (gchar *) "ifaceStatus", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_get_status_OUT_ARG_pointers[] = +{ + &_networkconfig1_method_info_get_status_OUT_ARG_status, + &_networkconfig1_method_info_get_status_OUT_ARG_ifaceStatus, + NULL +}; + +static const _ExtendedGDBusMethodInfo _networkconfig1_method_info_get_status = +{ + { + -1, + (gchar *) "GetStatus", + (GDBusArgInfo **) &_networkconfig1_method_info_get_status_IN_ARG_pointers, + (GDBusArgInfo **) &_networkconfig1_method_info_get_status_OUT_ARG_pointers, + NULL + }, + "handle-get-status", + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_reset_configuration_OUT_ARG_status = +{ + { + -1, + (gchar *) "status", + (gchar *) "i", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_reset_configuration_OUT_ARG_pointers[] = +{ + &_networkconfig1_method_info_reset_configuration_OUT_ARG_status, + NULL +}; + +static const _ExtendedGDBusMethodInfo _networkconfig1_method_info_reset_configuration = +{ + { + -1, + (gchar *) "ResetConfiguration", + NULL, + (GDBusArgInfo **) &_networkconfig1_method_info_reset_configuration_OUT_ARG_pointers, + NULL + }, + "handle-reset-configuration", + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_send_wake_up_request_IN_ARG_mac = +{ + { + -1, + (gchar *) "mac", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_send_wake_up_request_IN_ARG_pointers[] = +{ + &_networkconfig1_method_info_send_wake_up_request_IN_ARG_mac, + NULL +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_method_info_send_wake_up_request_OUT_ARG_status = +{ + { + -1, + (gchar *) "status", + (gchar *) "i", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_method_info_send_wake_up_request_OUT_ARG_pointers[] = +{ + &_networkconfig1_method_info_send_wake_up_request_OUT_ARG_status, + NULL +}; + +static const _ExtendedGDBusMethodInfo _networkconfig1_method_info_send_wake_up_request = +{ + { + -1, + (gchar *) "SendWakeUpRequest", + (GDBusArgInfo **) &_networkconfig1_method_info_send_wake_up_request_IN_ARG_pointers, + (GDBusArgInfo **) &_networkconfig1_method_info_send_wake_up_request_OUT_ARG_pointers, + NULL + }, + "handle-send-wake-up-request", + FALSE +}; + +static const _ExtendedGDBusMethodInfo * const _networkconfig1_method_info_pointers[] = +{ + &_networkconfig1_method_info_get_interfaces, + &_networkconfig1_method_info_enable, + &_networkconfig1_method_info_is_enabled, + &_networkconfig1_method_info_get_active_interface, + &_networkconfig1_method_info_set_active_interface, + &_networkconfig1_method_info_get_params, + &_networkconfig1_method_info_get_param, + &_networkconfig1_method_info_set_param, + &_networkconfig1_method_info_dhcprenew, + &_networkconfig1_method_info_get_status, + &_networkconfig1_method_info_reset_configuration, + &_networkconfig1_method_info_send_wake_up_request, + NULL +}; + +static const _ExtendedGDBusSignalInfo _networkconfig1_signal_info_network_configuration_service_started = +{ + { + -1, + (gchar *) "NetworkConfigurationServiceStarted", + NULL, + NULL + }, + "network-configuration-service-started" +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_signal_info_ipv4_configuration_changed_ARG_id = +{ + { + -1, + (gchar *) "id", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_signal_info_ipv4_configuration_changed_ARG_pointers[] = +{ + &_networkconfig1_signal_info_ipv4_configuration_changed_ARG_id, + NULL +}; + +static const _ExtendedGDBusSignalInfo _networkconfig1_signal_info_ipv4_configuration_changed = +{ + { + -1, + (gchar *) "IPv4ConfigurationChanged", + (GDBusArgInfo **) &_networkconfig1_signal_info_ipv4_configuration_changed_ARG_pointers, + NULL + }, + "ipv4-configuration-changed" +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_signal_info_ipv6_configuration_changed_ARG_id = +{ + { + -1, + (gchar *) "id", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_signal_info_ipv6_configuration_changed_ARG_pointers[] = +{ + &_networkconfig1_signal_info_ipv6_configuration_changed_ARG_id, + NULL +}; + +static const _ExtendedGDBusSignalInfo _networkconfig1_signal_info_ipv6_configuration_changed = +{ + { + -1, + (gchar *) "IPv6ConfigurationChanged", + (GDBusArgInfo **) &_networkconfig1_signal_info_ipv6_configuration_changed_ARG_pointers, + NULL + }, + "ipv6-configuration-changed" +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_signal_info_status_changed_ARG_id = +{ + { + -1, + (gchar *) "id", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_signal_info_status_changed_ARG_ifaceStatus = +{ + { + -1, + (gchar *) "ifaceStatus", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_signal_info_status_changed_ARG_pointers[] = +{ + &_networkconfig1_signal_info_status_changed_ARG_id, + &_networkconfig1_signal_info_status_changed_ARG_ifaceStatus, + NULL +}; + +static const _ExtendedGDBusSignalInfo _networkconfig1_signal_info_status_changed = +{ + { + -1, + (gchar *) "StatusChanged", + (GDBusArgInfo **) &_networkconfig1_signal_info_status_changed_ARG_pointers, + NULL + }, + "status-changed" +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_signal_info_networking_event_ARG_id = +{ + { + -1, + (gchar *) "id", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_signal_info_networking_event_ARG_event = +{ + { + -1, + (gchar *) "event", + (gchar *) "s", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_signal_info_networking_event_ARG_count = +{ + { + -1, + (gchar *) "count", + (gchar *) "u", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo _networkconfig1_signal_info_networking_event_ARG_params = +{ + { + -1, + (gchar *) "params", + (gchar *) "a{ss}", + NULL + }, + FALSE +}; + +static const _ExtendedGDBusArgInfo * const _networkconfig1_signal_info_networking_event_ARG_pointers[] = +{ + &_networkconfig1_signal_info_networking_event_ARG_id, + &_networkconfig1_signal_info_networking_event_ARG_event, + &_networkconfig1_signal_info_networking_event_ARG_count, + &_networkconfig1_signal_info_networking_event_ARG_params, + NULL +}; + +static const _ExtendedGDBusSignalInfo _networkconfig1_signal_info_networking_event = +{ + { + -1, + (gchar *) "NetworkingEvent", + (GDBusArgInfo **) &_networkconfig1_signal_info_networking_event_ARG_pointers, + NULL + }, + "networking-event" +}; + +static const _ExtendedGDBusSignalInfo * const _networkconfig1_signal_info_pointers[] = +{ + &_networkconfig1_signal_info_network_configuration_service_started, + &_networkconfig1_signal_info_ipv4_configuration_changed, + &_networkconfig1_signal_info_ipv6_configuration_changed, + &_networkconfig1_signal_info_status_changed, + &_networkconfig1_signal_info_networking_event, + NULL +}; + +static const _ExtendedGDBusInterfaceInfo _networkconfig1_interface_info = +{ + { + -1, + (gchar *) "com.lgi.rdk.utils.networkconfig1", + (GDBusMethodInfo **) &_networkconfig1_method_info_pointers, + (GDBusSignalInfo **) &_networkconfig1_signal_info_pointers, + NULL, + NULL + }, + "networkconfig1", +}; + + +/** + * networkconfig1_interface_info: + * + * Gets a machine-readable description of the com.lgi.rdk.utils.networkconfig1 D-Bus interface. + * + * Returns: (transfer none): A #GDBusInterfaceInfo. Do not free. + */ +GDBusInterfaceInfo * +networkconfig1_interface_info (void) +{ + return (GDBusInterfaceInfo *) &_networkconfig1_interface_info.parent_struct; +} + +/** + * networkconfig1_override_properties: + * @klass: The class structure for a #GObject derived class. + * @property_id_begin: The property id to assign to the first overridden property. + * + * Overrides all #GObject properties in the #Networkconfig1 interface for a concrete class. + * The properties are overridden in the order they are defined. + * + * Returns: The last property id. + */ +guint +networkconfig1_override_properties (GObjectClass *klass, guint property_id_begin) +{ + return property_id_begin - 1; +} + + + +/** + * Networkconfig1: + * + * Abstract interface type for the D-Bus interface com.lgi.rdk.utils.networkconfig1. + */ + +/** + * Networkconfig1Iface: + * @parent_iface: The parent interface. + * @handle_dhcprenew: Handler for the #Networkconfig1::handle-dhcprenew signal. + * @handle_enable: Handler for the #Networkconfig1::handle-enable signal. + * @handle_get_active_interface: Handler for the #Networkconfig1::handle-get-active-interface signal. + * @handle_get_interfaces: Handler for the #Networkconfig1::handle-get-interfaces signal. + * @handle_get_param: Handler for the #Networkconfig1::handle-get-param signal. + * @handle_get_params: Handler for the #Networkconfig1::handle-get-params signal. + * @handle_get_status: Handler for the #Networkconfig1::handle-get-status signal. + * @handle_is_enabled: Handler for the #Networkconfig1::handle-is-enabled signal. + * @handle_reset_configuration: Handler for the #Networkconfig1::handle-reset-configuration signal. + * @handle_send_wake_up_request: Handler for the #Networkconfig1::handle-send-wake-up-request signal. + * @handle_set_active_interface: Handler for the #Networkconfig1::handle-set-active-interface signal. + * @handle_set_param: Handler for the #Networkconfig1::handle-set-param signal. + * @ipv4_configuration_changed: Handler for the #Networkconfig1::ipv4-configuration-changed signal. + * @ipv6_configuration_changed: Handler for the #Networkconfig1::ipv6-configuration-changed signal. + * @network_configuration_service_started: Handler for the #Networkconfig1::network-configuration-service-started signal. + * @networking_event: Handler for the #Networkconfig1::networking-event signal. + * @status_changed: Handler for the #Networkconfig1::status-changed signal. + * + * Virtual table for the D-Bus interface com.lgi.rdk.utils.networkconfig1. + */ + +typedef Networkconfig1Iface Networkconfig1Interface; +G_DEFINE_INTERFACE (Networkconfig1, networkconfig1, G_TYPE_OBJECT) + +static void +networkconfig1_default_init (Networkconfig1Iface *iface) +{ + /* GObject signals for incoming D-Bus method calls: */ + /** + * Networkconfig1::handle-get-interfaces: + * @object: A #Networkconfig1. + * @invocation: A #GDBusMethodInvocation. + * + * Signal emitted when a remote caller is invoking the GetInterfaces() D-Bus method. + * + * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call networkconfig1_complete_get_interfaces() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned. + * + * Returns: %TRUE if the invocation was handled, %FALSE to let other signal handlers run. + */ + g_signal_new ("handle-get-interfaces", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, handle_get_interfaces), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, + 1, + G_TYPE_DBUS_METHOD_INVOCATION); + + /** + * Networkconfig1::handle-enable: + * @object: A #Networkconfig1. + * @invocation: A #GDBusMethodInvocation. + * @arg_id: Argument passed by remote caller. + * @arg_enable: Argument passed by remote caller. + * + * Signal emitted when a remote caller is invoking the Enable() D-Bus method. + * + * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call networkconfig1_complete_enable() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned. + * + * Returns: %TRUE if the invocation was handled, %FALSE to let other signal handlers run. + */ + g_signal_new ("handle-enable", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, handle_enable), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, + 3, + G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_STRING, G_TYPE_BOOLEAN); + + /** + * Networkconfig1::handle-is-enabled: + * @object: A #Networkconfig1. + * @invocation: A #GDBusMethodInvocation. + * @arg_id: Argument passed by remote caller. + * + * Signal emitted when a remote caller is invoking the IsEnabled() D-Bus method. + * + * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call networkconfig1_complete_is_enabled() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned. + * + * Returns: %TRUE if the invocation was handled, %FALSE to let other signal handlers run. + */ + g_signal_new ("handle-is-enabled", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, handle_is_enabled), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, + 2, + G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_STRING); + + /** + * Networkconfig1::handle-get-active-interface: + * @object: A #Networkconfig1. + * @invocation: A #GDBusMethodInvocation. + * + * Signal emitted when a remote caller is invoking the GetActiveInterface() D-Bus method. + * + * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call networkconfig1_complete_get_active_interface() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned. + * + * Returns: %TRUE if the invocation was handled, %FALSE to let other signal handlers run. + */ + g_signal_new ("handle-get-active-interface", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, handle_get_active_interface), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, + 1, + G_TYPE_DBUS_METHOD_INVOCATION); + + /** + * Networkconfig1::handle-set-active-interface: + * @object: A #Networkconfig1. + * @invocation: A #GDBusMethodInvocation. + * @arg_id: Argument passed by remote caller. + * + * Signal emitted when a remote caller is invoking the SetActiveInterface() D-Bus method. + * + * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call networkconfig1_complete_set_active_interface() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned. + * + * Returns: %TRUE if the invocation was handled, %FALSE to let other signal handlers run. + */ + g_signal_new ("handle-set-active-interface", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, handle_set_active_interface), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, + 2, + G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_STRING); + + /** + * Networkconfig1::handle-get-params: + * @object: A #Networkconfig1. + * @invocation: A #GDBusMethodInvocation. + * @arg_id: Argument passed by remote caller. + * + * Signal emitted when a remote caller is invoking the GetParams() D-Bus method. + * + * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call networkconfig1_complete_get_params() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned. + * + * Returns: %TRUE if the invocation was handled, %FALSE to let other signal handlers run. + */ + g_signal_new ("handle-get-params", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, handle_get_params), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, + 2, + G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_STRING); + + /** + * Networkconfig1::handle-get-param: + * @object: A #Networkconfig1. + * @invocation: A #GDBusMethodInvocation. + * @arg_id: Argument passed by remote caller. + * @arg_paramName: Argument passed by remote caller. + * + * Signal emitted when a remote caller is invoking the GetParam() D-Bus method. + * + * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call networkconfig1_complete_get_param() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned. + * + * Returns: %TRUE if the invocation was handled, %FALSE to let other signal handlers run. + */ + g_signal_new ("handle-get-param", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, handle_get_param), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, + 3, + G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_STRING, G_TYPE_STRING); + + /** + * Networkconfig1::handle-set-param: + * @object: A #Networkconfig1. + * @invocation: A #GDBusMethodInvocation. + * @arg_id: Argument passed by remote caller. + * @arg_paramName: Argument passed by remote caller. + * @arg_paramValue: Argument passed by remote caller. + * + * Signal emitted when a remote caller is invoking the SetParam() D-Bus method. + * + * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call networkconfig1_complete_set_param() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned. + * + * Returns: %TRUE if the invocation was handled, %FALSE to let other signal handlers run. + */ + g_signal_new ("handle-set-param", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, handle_set_param), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, + 4, + G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + + /** + * Networkconfig1::handle-dhcprenew: + * @object: A #Networkconfig1. + * @invocation: A #GDBusMethodInvocation. + * + * Signal emitted when a remote caller is invoking the DHCPRenew() D-Bus method. + * + * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call networkconfig1_complete_dhcprenew() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned. + * + * Returns: %TRUE if the invocation was handled, %FALSE to let other signal handlers run. + */ + g_signal_new ("handle-dhcprenew", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, handle_dhcprenew), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, + 1, + G_TYPE_DBUS_METHOD_INVOCATION); + + /** + * Networkconfig1::handle-get-status: + * @object: A #Networkconfig1. + * @invocation: A #GDBusMethodInvocation. + * @arg_id: Argument passed by remote caller. + * + * Signal emitted when a remote caller is invoking the GetStatus() D-Bus method. + * + * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call networkconfig1_complete_get_status() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned. + * + * Returns: %TRUE if the invocation was handled, %FALSE to let other signal handlers run. + */ + g_signal_new ("handle-get-status", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, handle_get_status), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, + 2, + G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_STRING); + + /** + * Networkconfig1::handle-reset-configuration: + * @object: A #Networkconfig1. + * @invocation: A #GDBusMethodInvocation. + * + * Signal emitted when a remote caller is invoking the ResetConfiguration() D-Bus method. + * + * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call networkconfig1_complete_reset_configuration() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned. + * + * Returns: %TRUE if the invocation was handled, %FALSE to let other signal handlers run. + */ + g_signal_new ("handle-reset-configuration", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, handle_reset_configuration), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, + 1, + G_TYPE_DBUS_METHOD_INVOCATION); + + /** + * Networkconfig1::handle-send-wake-up-request: + * @object: A #Networkconfig1. + * @invocation: A #GDBusMethodInvocation. + * @arg_mac: Argument passed by remote caller. + * + * Signal emitted when a remote caller is invoking the SendWakeUpRequest() D-Bus method. + * + * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call networkconfig1_complete_send_wake_up_request() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned. + * + * Returns: %TRUE if the invocation was handled, %FALSE to let other signal handlers run. + */ + g_signal_new ("handle-send-wake-up-request", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, handle_send_wake_up_request), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, + 2, + G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_STRING); + + /* GObject signals for received D-Bus signals: */ + /** + * Networkconfig1::network-configuration-service-started: + * @object: A #Networkconfig1. + * + * On the client-side, this signal is emitted whenever the D-Bus signal "NetworkConfigurationServiceStarted" is received. + * + * On the service-side, this signal can be used with e.g. g_signal_emit_by_name() to make the object emit the D-Bus signal. + */ + g_signal_new ("network-configuration-service-started", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, network_configuration_service_started), + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 0); + + /** + * Networkconfig1::ipv4-configuration-changed: + * @object: A #Networkconfig1. + * @arg_id: Argument. + * + * On the client-side, this signal is emitted whenever the D-Bus signal "IPv4ConfigurationChanged" is received. + * + * On the service-side, this signal can be used with e.g. g_signal_emit_by_name() to make the object emit the D-Bus signal. + */ + g_signal_new ("ipv4-configuration-changed", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, ipv4_configuration_changed), + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, G_TYPE_STRING); + + /** + * Networkconfig1::ipv6-configuration-changed: + * @object: A #Networkconfig1. + * @arg_id: Argument. + * + * On the client-side, this signal is emitted whenever the D-Bus signal "IPv6ConfigurationChanged" is received. + * + * On the service-side, this signal can be used with e.g. g_signal_emit_by_name() to make the object emit the D-Bus signal. + */ + g_signal_new ("ipv6-configuration-changed", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, ipv6_configuration_changed), + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, G_TYPE_STRING); + + /** + * Networkconfig1::status-changed: + * @object: A #Networkconfig1. + * @arg_id: Argument. + * @arg_ifaceStatus: Argument. + * + * On the client-side, this signal is emitted whenever the D-Bus signal "StatusChanged" is received. + * + * On the service-side, this signal can be used with e.g. g_signal_emit_by_name() to make the object emit the D-Bus signal. + */ + g_signal_new ("status-changed", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, status_changed), + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 2, G_TYPE_STRING, G_TYPE_STRING); + + /** + * Networkconfig1::networking-event: + * @object: A #Networkconfig1. + * @arg_id: Argument. + * @arg_event: Argument. + * @arg_count: Argument. + * @arg_params: Argument. + * + * On the client-side, this signal is emitted whenever the D-Bus signal "NetworkingEvent" is received. + * + * On the service-side, this signal can be used with e.g. g_signal_emit_by_name() to make the object emit the D-Bus signal. + */ + g_signal_new ("networking-event", + G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (Networkconfig1Iface, networking_event), + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_VARIANT); + +} + +/** + * networkconfig1_emit_network_configuration_service_started: + * @object: A #Networkconfig1. + * + * Emits the "NetworkConfigurationServiceStarted" D-Bus signal. + */ +void +networkconfig1_emit_network_configuration_service_started ( + Networkconfig1 *object) +{ + g_signal_emit_by_name (object, "network-configuration-service-started"); +} + +/** + * networkconfig1_emit_ipv4_configuration_changed: + * @object: A #Networkconfig1. + * @arg_id: Argument to pass with the signal. + * + * Emits the "IPv4ConfigurationChanged" D-Bus signal. + */ +void +networkconfig1_emit_ipv4_configuration_changed ( + Networkconfig1 *object, + const gchar *arg_id) +{ + g_signal_emit_by_name (object, "ipv4-configuration-changed", arg_id); +} + +/** + * networkconfig1_emit_ipv6_configuration_changed: + * @object: A #Networkconfig1. + * @arg_id: Argument to pass with the signal. + * + * Emits the "IPv6ConfigurationChanged" D-Bus signal. + */ +void +networkconfig1_emit_ipv6_configuration_changed ( + Networkconfig1 *object, + const gchar *arg_id) +{ + g_signal_emit_by_name (object, "ipv6-configuration-changed", arg_id); +} + +/** + * networkconfig1_emit_status_changed: + * @object: A #Networkconfig1. + * @arg_id: Argument to pass with the signal. + * @arg_ifaceStatus: Argument to pass with the signal. + * + * Emits the "StatusChanged" D-Bus signal. + */ +void +networkconfig1_emit_status_changed ( + Networkconfig1 *object, + const gchar *arg_id, + const gchar *arg_ifaceStatus) +{ + g_signal_emit_by_name (object, "status-changed", arg_id, arg_ifaceStatus); +} + +/** + * networkconfig1_emit_networking_event: + * @object: A #Networkconfig1. + * @arg_id: Argument to pass with the signal. + * @arg_event: Argument to pass with the signal. + * @arg_count: Argument to pass with the signal. + * @arg_params: Argument to pass with the signal. + * + * Emits the "NetworkingEvent" D-Bus signal. + */ +void +networkconfig1_emit_networking_event ( + Networkconfig1 *object, + const gchar *arg_id, + const gchar *arg_event, + guint arg_count, + GVariant *arg_params) +{ + g_signal_emit_by_name (object, "networking-event", arg_id, arg_event, arg_count, arg_params); +} + +/** + * networkconfig1_call_get_interfaces: + * @proxy: A #Networkconfig1Proxy. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Asynchronously invokes the GetInterfaces() D-Bus method on @proxy. + * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from. + * You can then call networkconfig1_call_get_interfaces_finish() to get the result of the operation. + * + * See networkconfig1_call_get_interfaces_sync() for the synchronous, blocking version of this method. + */ +void +networkconfig1_call_get_interfaces ( + Networkconfig1 *proxy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_proxy_call (G_DBUS_PROXY (proxy), + "GetInterfaces", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +/** + * networkconfig1_call_get_interfaces_finish: + * @proxy: A #Networkconfig1Proxy. + * @out_count: (out): Return location for return parameter or %NULL to ignore. + * @out_ids: (out) (array zero-terminated=1): Return location for return parameter or %NULL to ignore. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to networkconfig1_call_get_interfaces(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with networkconfig1_call_get_interfaces(). + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_get_interfaces_finish ( + Networkconfig1 *proxy, + guint *out_count, + gchar ***out_ids, + GAsyncResult *res, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(u^as)", + out_count, + out_ids); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_get_interfaces_sync: + * @proxy: A #Networkconfig1Proxy. + * @out_count: (out): Return location for return parameter or %NULL to ignore. + * @out_ids: (out) (array zero-terminated=1): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the GetInterfaces() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See networkconfig1_call_get_interfaces() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_get_interfaces_sync ( + Networkconfig1 *proxy, + guint *out_count, + gchar ***out_ids, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), + "GetInterfaces", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(u^as)", + out_count, + out_ids); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_enable: + * @proxy: A #Networkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @arg_enable: Argument to pass with the method invocation. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Asynchronously invokes the Enable() D-Bus method on @proxy. + * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from. + * You can then call networkconfig1_call_enable_finish() to get the result of the operation. + * + * See networkconfig1_call_enable_sync() for the synchronous, blocking version of this method. + */ +void +networkconfig1_call_enable ( + Networkconfig1 *proxy, + const gchar *arg_id, + gboolean arg_enable, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_proxy_call (G_DBUS_PROXY (proxy), + "Enable", + g_variant_new ("(sb)", + arg_id, + arg_enable), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +/** + * networkconfig1_call_enable_finish: + * @proxy: A #Networkconfig1Proxy. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to networkconfig1_call_enable(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with networkconfig1_call_enable(). + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_enable_finish ( + Networkconfig1 *proxy, + gint *out_status, + GAsyncResult *res, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(i)", + out_status); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_enable_sync: + * @proxy: A #Networkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @arg_enable: Argument to pass with the method invocation. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the Enable() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See networkconfig1_call_enable() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_enable_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gboolean arg_enable, + gint *out_status, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), + "Enable", + g_variant_new ("(sb)", + arg_id, + arg_enable), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(i)", + out_status); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_is_enabled: + * @proxy: A #Networkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Asynchronously invokes the IsEnabled() D-Bus method on @proxy. + * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from. + * You can then call networkconfig1_call_is_enabled_finish() to get the result of the operation. + * + * See networkconfig1_call_is_enabled_sync() for the synchronous, blocking version of this method. + */ +void +networkconfig1_call_is_enabled ( + Networkconfig1 *proxy, + const gchar *arg_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_proxy_call (G_DBUS_PROXY (proxy), + "IsEnabled", + g_variant_new ("(s)", + arg_id), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +/** + * networkconfig1_call_is_enabled_finish: + * @proxy: A #Networkconfig1Proxy. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @out_enabled: (out): Return location for return parameter or %NULL to ignore. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to networkconfig1_call_is_enabled(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with networkconfig1_call_is_enabled(). + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_is_enabled_finish ( + Networkconfig1 *proxy, + gint *out_status, + gboolean *out_enabled, + GAsyncResult *res, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(ib)", + out_status, + out_enabled); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_is_enabled_sync: + * @proxy: A #Networkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @out_enabled: (out): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the IsEnabled() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See networkconfig1_call_is_enabled() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_is_enabled_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gint *out_status, + gboolean *out_enabled, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), + "IsEnabled", + g_variant_new ("(s)", + arg_id), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(ib)", + out_status, + out_enabled); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_get_active_interface: + * @proxy: A #Networkconfig1Proxy. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Asynchronously invokes the GetActiveInterface() D-Bus method on @proxy. + * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from. + * You can then call networkconfig1_call_get_active_interface_finish() to get the result of the operation. + * + * See networkconfig1_call_get_active_interface_sync() for the synchronous, blocking version of this method. + */ +void +networkconfig1_call_get_active_interface ( + Networkconfig1 *proxy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_proxy_call (G_DBUS_PROXY (proxy), + "GetActiveInterface", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +/** + * networkconfig1_call_get_active_interface_finish: + * @proxy: A #Networkconfig1Proxy. + * @out_id: (out): Return location for return parameter or %NULL to ignore. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to networkconfig1_call_get_active_interface(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with networkconfig1_call_get_active_interface(). + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_get_active_interface_finish ( + Networkconfig1 *proxy, + gchar **out_id, + gint *out_status, + GAsyncResult *res, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(si)", + out_id, + out_status); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_get_active_interface_sync: + * @proxy: A #Networkconfig1Proxy. + * @out_id: (out): Return location for return parameter or %NULL to ignore. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the GetActiveInterface() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See networkconfig1_call_get_active_interface() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_get_active_interface_sync ( + Networkconfig1 *proxy, + gchar **out_id, + gint *out_status, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), + "GetActiveInterface", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(si)", + out_id, + out_status); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_set_active_interface: + * @proxy: A #Networkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Asynchronously invokes the SetActiveInterface() D-Bus method on @proxy. + * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from. + * You can then call networkconfig1_call_set_active_interface_finish() to get the result of the operation. + * + * See networkconfig1_call_set_active_interface_sync() for the synchronous, blocking version of this method. + */ +void +networkconfig1_call_set_active_interface ( + Networkconfig1 *proxy, + const gchar *arg_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_proxy_call (G_DBUS_PROXY (proxy), + "SetActiveInterface", + g_variant_new ("(s)", + arg_id), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +/** + * networkconfig1_call_set_active_interface_finish: + * @proxy: A #Networkconfig1Proxy. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to networkconfig1_call_set_active_interface(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with networkconfig1_call_set_active_interface(). + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_set_active_interface_finish ( + Networkconfig1 *proxy, + gint *out_status, + GAsyncResult *res, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(i)", + out_status); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_set_active_interface_sync: + * @proxy: A #Networkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the SetActiveInterface() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See networkconfig1_call_set_active_interface() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_set_active_interface_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gint *out_status, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), + "SetActiveInterface", + g_variant_new ("(s)", + arg_id), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(i)", + out_status); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_get_params: + * @proxy: A #Networkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Asynchronously invokes the GetParams() D-Bus method on @proxy. + * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from. + * You can then call networkconfig1_call_get_params_finish() to get the result of the operation. + * + * See networkconfig1_call_get_params_sync() for the synchronous, blocking version of this method. + */ +void +networkconfig1_call_get_params ( + Networkconfig1 *proxy, + const gchar *arg_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_proxy_call (G_DBUS_PROXY (proxy), + "GetParams", + g_variant_new ("(s)", + arg_id), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +/** + * networkconfig1_call_get_params_finish: + * @proxy: A #Networkconfig1Proxy. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @out_count: (out): Return location for return parameter or %NULL to ignore. + * @out_params: (out): Return location for return parameter or %NULL to ignore. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to networkconfig1_call_get_params(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with networkconfig1_call_get_params(). + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_get_params_finish ( + Networkconfig1 *proxy, + gint *out_status, + guint *out_count, + GVariant **out_params, + GAsyncResult *res, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(iu@a{ss})", + out_status, + out_count, + out_params); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_get_params_sync: + * @proxy: A #Networkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @out_count: (out): Return location for return parameter or %NULL to ignore. + * @out_params: (out): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the GetParams() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See networkconfig1_call_get_params() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_get_params_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gint *out_status, + guint *out_count, + GVariant **out_params, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), + "GetParams", + g_variant_new ("(s)", + arg_id), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(iu@a{ss})", + out_status, + out_count, + out_params); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_get_param: + * @proxy: A #Networkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @arg_paramName: Argument to pass with the method invocation. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Asynchronously invokes the GetParam() D-Bus method on @proxy. + * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from. + * You can then call networkconfig1_call_get_param_finish() to get the result of the operation. + * + * See networkconfig1_call_get_param_sync() for the synchronous, blocking version of this method. + */ +void +networkconfig1_call_get_param ( + Networkconfig1 *proxy, + const gchar *arg_id, + const gchar *arg_paramName, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_proxy_call (G_DBUS_PROXY (proxy), + "GetParam", + g_variant_new ("(ss)", + arg_id, + arg_paramName), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +/** + * networkconfig1_call_get_param_finish: + * @proxy: A #Networkconfig1Proxy. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @out_paramValue: (out): Return location for return parameter or %NULL to ignore. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to networkconfig1_call_get_param(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with networkconfig1_call_get_param(). + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_get_param_finish ( + Networkconfig1 *proxy, + gint *out_status, + gchar **out_paramValue, + GAsyncResult *res, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(is)", + out_status, + out_paramValue); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_get_param_sync: + * @proxy: A #Networkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @arg_paramName: Argument to pass with the method invocation. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @out_paramValue: (out): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the GetParam() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See networkconfig1_call_get_param() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_get_param_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + const gchar *arg_paramName, + gint *out_status, + gchar **out_paramValue, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), + "GetParam", + g_variant_new ("(ss)", + arg_id, + arg_paramName), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(is)", + out_status, + out_paramValue); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_set_param: + * @proxy: A #Networkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @arg_paramName: Argument to pass with the method invocation. + * @arg_paramValue: Argument to pass with the method invocation. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Asynchronously invokes the SetParam() D-Bus method on @proxy. + * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from. + * You can then call networkconfig1_call_set_param_finish() to get the result of the operation. + * + * See networkconfig1_call_set_param_sync() for the synchronous, blocking version of this method. + */ +void +networkconfig1_call_set_param ( + Networkconfig1 *proxy, + const gchar *arg_id, + const gchar *arg_paramName, + const gchar *arg_paramValue, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_proxy_call (G_DBUS_PROXY (proxy), + "SetParam", + g_variant_new ("(sss)", + arg_id, + arg_paramName, + arg_paramValue), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +/** + * networkconfig1_call_set_param_finish: + * @proxy: A #Networkconfig1Proxy. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to networkconfig1_call_set_param(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with networkconfig1_call_set_param(). + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_set_param_finish ( + Networkconfig1 *proxy, + gint *out_status, + GAsyncResult *res, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(i)", + out_status); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_set_param_sync: + * @proxy: A #Networkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @arg_paramName: Argument to pass with the method invocation. + * @arg_paramValue: Argument to pass with the method invocation. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the SetParam() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See networkconfig1_call_set_param() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_set_param_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + const gchar *arg_paramName, + const gchar *arg_paramValue, + gint *out_status, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), + "SetParam", + g_variant_new ("(sss)", + arg_id, + arg_paramName, + arg_paramValue), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(i)", + out_status); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_dhcprenew: + * @proxy: A #Networkconfig1Proxy. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Asynchronously invokes the DHCPRenew() D-Bus method on @proxy. + * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from. + * You can then call networkconfig1_call_dhcprenew_finish() to get the result of the operation. + * + * See networkconfig1_call_dhcprenew_sync() for the synchronous, blocking version of this method. + */ +void +networkconfig1_call_dhcprenew ( + Networkconfig1 *proxy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_proxy_call (G_DBUS_PROXY (proxy), + "DHCPRenew", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +/** + * networkconfig1_call_dhcprenew_finish: + * @proxy: A #Networkconfig1Proxy. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to networkconfig1_call_dhcprenew(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with networkconfig1_call_dhcprenew(). + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_dhcprenew_finish ( + Networkconfig1 *proxy, + gint *out_status, + GAsyncResult *res, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(i)", + out_status); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_dhcprenew_sync: + * @proxy: A #Networkconfig1Proxy. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the DHCPRenew() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See networkconfig1_call_dhcprenew() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_dhcprenew_sync ( + Networkconfig1 *proxy, + gint *out_status, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), + "DHCPRenew", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(i)", + out_status); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_get_status: + * @proxy: A #Networkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Asynchronously invokes the GetStatus() D-Bus method on @proxy. + * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from. + * You can then call networkconfig1_call_get_status_finish() to get the result of the operation. + * + * See networkconfig1_call_get_status_sync() for the synchronous, blocking version of this method. + */ +void +networkconfig1_call_get_status ( + Networkconfig1 *proxy, + const gchar *arg_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_proxy_call (G_DBUS_PROXY (proxy), + "GetStatus", + g_variant_new ("(s)", + arg_id), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +/** + * networkconfig1_call_get_status_finish: + * @proxy: A #Networkconfig1Proxy. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @out_ifaceStatus: (out): Return location for return parameter or %NULL to ignore. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to networkconfig1_call_get_status(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with networkconfig1_call_get_status(). + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_get_status_finish ( + Networkconfig1 *proxy, + gint *out_status, + gchar **out_ifaceStatus, + GAsyncResult *res, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(is)", + out_status, + out_ifaceStatus); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_get_status_sync: + * @proxy: A #Networkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @out_ifaceStatus: (out): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the GetStatus() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See networkconfig1_call_get_status() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_get_status_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gint *out_status, + gchar **out_ifaceStatus, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), + "GetStatus", + g_variant_new ("(s)", + arg_id), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(is)", + out_status, + out_ifaceStatus); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_reset_configuration: + * @proxy: A #Networkconfig1Proxy. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Asynchronously invokes the ResetConfiguration() D-Bus method on @proxy. + * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from. + * You can then call networkconfig1_call_reset_configuration_finish() to get the result of the operation. + * + * See networkconfig1_call_reset_configuration_sync() for the synchronous, blocking version of this method. + */ +void +networkconfig1_call_reset_configuration ( + Networkconfig1 *proxy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_proxy_call (G_DBUS_PROXY (proxy), + "ResetConfiguration", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +/** + * networkconfig1_call_reset_configuration_finish: + * @proxy: A #Networkconfig1Proxy. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to networkconfig1_call_reset_configuration(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with networkconfig1_call_reset_configuration(). + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_reset_configuration_finish ( + Networkconfig1 *proxy, + gint *out_status, + GAsyncResult *res, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(i)", + out_status); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_reset_configuration_sync: + * @proxy: A #Networkconfig1Proxy. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the ResetConfiguration() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See networkconfig1_call_reset_configuration() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_reset_configuration_sync ( + Networkconfig1 *proxy, + gint *out_status, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), + "ResetConfiguration", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(i)", + out_status); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_send_wake_up_request: + * @proxy: A #Networkconfig1Proxy. + * @arg_mac: Argument to pass with the method invocation. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL. + * @user_data: User data to pass to @callback. + * + * Asynchronously invokes the SendWakeUpRequest() D-Bus method on @proxy. + * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from. + * You can then call networkconfig1_call_send_wake_up_request_finish() to get the result of the operation. + * + * See networkconfig1_call_send_wake_up_request_sync() for the synchronous, blocking version of this method. + */ +void +networkconfig1_call_send_wake_up_request ( + Networkconfig1 *proxy, + const gchar *arg_mac, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_proxy_call (G_DBUS_PROXY (proxy), + "SendWakeUpRequest", + g_variant_new ("(s)", + arg_mac), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +/** + * networkconfig1_call_send_wake_up_request_finish: + * @proxy: A #Networkconfig1Proxy. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to networkconfig1_call_send_wake_up_request(). + * @error: Return location for error or %NULL. + * + * Finishes an operation started with networkconfig1_call_send_wake_up_request(). + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_send_wake_up_request_finish ( + Networkconfig1 *proxy, + gint *out_status, + GAsyncResult *res, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(i)", + out_status); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_call_send_wake_up_request_sync: + * @proxy: A #Networkconfig1Proxy. + * @arg_mac: Argument to pass with the method invocation. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the SendWakeUpRequest() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See networkconfig1_call_send_wake_up_request() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +networkconfig1_call_send_wake_up_request_sync ( + Networkconfig1 *proxy, + const gchar *arg_mac, + gint *out_status, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), + "SendWakeUpRequest", + g_variant_new ("(s)", + arg_mac), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(i)", + out_status); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/** + * networkconfig1_complete_get_interfaces: + * @object: A #Networkconfig1. + * @invocation: (transfer full): A #GDBusMethodInvocation. + * @count: Parameter to return. + * @ids: Parameter to return. + * + * Helper function used in service implementations to finish handling invocations of the GetInterfaces() D-Bus method. If you instead want to finish handling an invocation by returning an error, use g_dbus_method_invocation_return_error() or similar. + * + * This method will free @invocation, you cannot use it afterwards. + */ +void +networkconfig1_complete_get_interfaces ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + guint count, + const gchar *const *ids) +{ + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(u^as)", + count, + ids)); +} + +/** + * networkconfig1_complete_enable: + * @object: A #Networkconfig1. + * @invocation: (transfer full): A #GDBusMethodInvocation. + * @status: Parameter to return. + * + * Helper function used in service implementations to finish handling invocations of the Enable() D-Bus method. If you instead want to finish handling an invocation by returning an error, use g_dbus_method_invocation_return_error() or similar. + * + * This method will free @invocation, you cannot use it afterwards. + */ +void +networkconfig1_complete_enable ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status) +{ + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(i)", + status)); +} + +/** + * networkconfig1_complete_is_enabled: + * @object: A #Networkconfig1. + * @invocation: (transfer full): A #GDBusMethodInvocation. + * @status: Parameter to return. + * @enabled: Parameter to return. + * + * Helper function used in service implementations to finish handling invocations of the IsEnabled() D-Bus method. If you instead want to finish handling an invocation by returning an error, use g_dbus_method_invocation_return_error() or similar. + * + * This method will free @invocation, you cannot use it afterwards. + */ +void +networkconfig1_complete_is_enabled ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status, + gboolean enabled) +{ + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(ib)", + status, + enabled)); +} + +/** + * networkconfig1_complete_get_active_interface: + * @object: A #Networkconfig1. + * @invocation: (transfer full): A #GDBusMethodInvocation. + * @id: Parameter to return. + * @status: Parameter to return. + * + * Helper function used in service implementations to finish handling invocations of the GetActiveInterface() D-Bus method. If you instead want to finish handling an invocation by returning an error, use g_dbus_method_invocation_return_error() or similar. + * + * This method will free @invocation, you cannot use it afterwards. + */ +void +networkconfig1_complete_get_active_interface ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + const gchar *id, + gint status) +{ + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(si)", + id, + status)); +} + +/** + * networkconfig1_complete_set_active_interface: + * @object: A #Networkconfig1. + * @invocation: (transfer full): A #GDBusMethodInvocation. + * @status: Parameter to return. + * + * Helper function used in service implementations to finish handling invocations of the SetActiveInterface() D-Bus method. If you instead want to finish handling an invocation by returning an error, use g_dbus_method_invocation_return_error() or similar. + * + * This method will free @invocation, you cannot use it afterwards. + */ +void +networkconfig1_complete_set_active_interface ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status) +{ + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(i)", + status)); +} + +/** + * networkconfig1_complete_get_params: + * @object: A #Networkconfig1. + * @invocation: (transfer full): A #GDBusMethodInvocation. + * @status: Parameter to return. + * @count: Parameter to return. + * @params: Parameter to return. + * + * Helper function used in service implementations to finish handling invocations of the GetParams() D-Bus method. If you instead want to finish handling an invocation by returning an error, use g_dbus_method_invocation_return_error() or similar. + * + * This method will free @invocation, you cannot use it afterwards. + */ +void +networkconfig1_complete_get_params ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status, + guint count, + GVariant *params) +{ + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(iu@a{ss})", + status, + count, + params)); +} + +/** + * networkconfig1_complete_get_param: + * @object: A #Networkconfig1. + * @invocation: (transfer full): A #GDBusMethodInvocation. + * @status: Parameter to return. + * @paramValue: Parameter to return. + * + * Helper function used in service implementations to finish handling invocations of the GetParam() D-Bus method. If you instead want to finish handling an invocation by returning an error, use g_dbus_method_invocation_return_error() or similar. + * + * This method will free @invocation, you cannot use it afterwards. + */ +void +networkconfig1_complete_get_param ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status, + const gchar *paramValue) +{ + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(is)", + status, + paramValue)); +} + +/** + * networkconfig1_complete_set_param: + * @object: A #Networkconfig1. + * @invocation: (transfer full): A #GDBusMethodInvocation. + * @status: Parameter to return. + * + * Helper function used in service implementations to finish handling invocations of the SetParam() D-Bus method. If you instead want to finish handling an invocation by returning an error, use g_dbus_method_invocation_return_error() or similar. + * + * This method will free @invocation, you cannot use it afterwards. + */ +void +networkconfig1_complete_set_param ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status) +{ + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(i)", + status)); +} + +/** + * networkconfig1_complete_dhcprenew: + * @object: A #Networkconfig1. + * @invocation: (transfer full): A #GDBusMethodInvocation. + * @status: Parameter to return. + * + * Helper function used in service implementations to finish handling invocations of the DHCPRenew() D-Bus method. If you instead want to finish handling an invocation by returning an error, use g_dbus_method_invocation_return_error() or similar. + * + * This method will free @invocation, you cannot use it afterwards. + */ +void +networkconfig1_complete_dhcprenew ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status) +{ + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(i)", + status)); +} + +/** + * networkconfig1_complete_get_status: + * @object: A #Networkconfig1. + * @invocation: (transfer full): A #GDBusMethodInvocation. + * @status: Parameter to return. + * @ifaceStatus: Parameter to return. + * + * Helper function used in service implementations to finish handling invocations of the GetStatus() D-Bus method. If you instead want to finish handling an invocation by returning an error, use g_dbus_method_invocation_return_error() or similar. + * + * This method will free @invocation, you cannot use it afterwards. + */ +void +networkconfig1_complete_get_status ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status, + const gchar *ifaceStatus) +{ + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(is)", + status, + ifaceStatus)); +} + +/** + * networkconfig1_complete_reset_configuration: + * @object: A #Networkconfig1. + * @invocation: (transfer full): A #GDBusMethodInvocation. + * @status: Parameter to return. + * + * Helper function used in service implementations to finish handling invocations of the ResetConfiguration() D-Bus method. If you instead want to finish handling an invocation by returning an error, use g_dbus_method_invocation_return_error() or similar. + * + * This method will free @invocation, you cannot use it afterwards. + */ +void +networkconfig1_complete_reset_configuration ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status) +{ + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(i)", + status)); +} + +/** + * networkconfig1_complete_send_wake_up_request: + * @object: A #Networkconfig1. + * @invocation: (transfer full): A #GDBusMethodInvocation. + * @status: Parameter to return. + * + * Helper function used in service implementations to finish handling invocations of the SendWakeUpRequest() D-Bus method. If you instead want to finish handling an invocation by returning an error, use g_dbus_method_invocation_return_error() or similar. + * + * This method will free @invocation, you cannot use it afterwards. + */ +void +networkconfig1_complete_send_wake_up_request ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status) +{ + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(i)", + status)); +} + +/* ------------------------------------------------------------------------ */ + +/** + * Networkconfig1Proxy: + * + * The #Networkconfig1Proxy structure contains only private data and should only be accessed using the provided API. + */ + +/** + * Networkconfig1ProxyClass: + * @parent_class: The parent class. + * + * Class structure for #Networkconfig1Proxy. + */ + +struct _Networkconfig1ProxyPrivate +{ + GData *qdata; +}; + +static void networkconfig1_proxy_iface_init (Networkconfig1Iface *iface); + +#if GLIB_VERSION_MAX_ALLOWED >= GLIB_VERSION_2_38 +G_DEFINE_TYPE_WITH_CODE (Networkconfig1Proxy, networkconfig1_proxy, G_TYPE_DBUS_PROXY, + G_ADD_PRIVATE (Networkconfig1Proxy) + G_IMPLEMENT_INTERFACE (TYPE_NETWORKCONFIG1, networkconfig1_proxy_iface_init)) + +#else +G_DEFINE_TYPE_WITH_CODE (Networkconfig1Proxy, networkconfig1_proxy, G_TYPE_DBUS_PROXY, + G_IMPLEMENT_INTERFACE (TYPE_NETWORKCONFIG1, networkconfig1_proxy_iface_init)) + +#endif +static void +networkconfig1_proxy_finalize (GObject *object) +{ + Networkconfig1Proxy *proxy = NETWORKCONFIG1_PROXY (object); + g_datalist_clear (&proxy->priv->qdata); + G_OBJECT_CLASS (networkconfig1_proxy_parent_class)->finalize (object); +} + +static void +networkconfig1_proxy_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec G_GNUC_UNUSED) +{ +} + +static void +networkconfig1_proxy_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec G_GNUC_UNUSED) +{ +} + +static void +networkconfig1_proxy_g_signal (GDBusProxy *proxy, + const gchar *sender_name G_GNUC_UNUSED, + const gchar *signal_name, + GVariant *parameters) +{ + _ExtendedGDBusSignalInfo *info; + GVariantIter iter; + GVariant *child; + GValue *paramv; + gsize num_params; + gsize n; + guint signal_id; + info = (_ExtendedGDBusSignalInfo *) g_dbus_interface_info_lookup_signal ((GDBusInterfaceInfo *) &_networkconfig1_interface_info.parent_struct, signal_name); + if (info == NULL) + return; + num_params = g_variant_n_children (parameters); + paramv = g_new0 (GValue, num_params + 1); + g_value_init (¶mv[0], TYPE_NETWORKCONFIG1); + g_value_set_object (¶mv[0], proxy); + g_variant_iter_init (&iter, parameters); + n = 1; + while ((child = g_variant_iter_next_value (&iter)) != NULL) + { + _ExtendedGDBusArgInfo *arg_info = (_ExtendedGDBusArgInfo *) info->parent_struct.args[n - 1]; + if (arg_info->use_gvariant) + { + g_value_init (¶mv[n], G_TYPE_VARIANT); + g_value_set_variant (¶mv[n], child); + n++; + } + else + g_dbus_gvariant_to_gvalue (child, ¶mv[n++]); + g_variant_unref (child); + } + signal_id = g_signal_lookup (info->signal_name, TYPE_NETWORKCONFIG1); + g_signal_emitv (paramv, signal_id, 0, NULL); + for (n = 0; n < num_params + 1; n++) + g_value_unset (¶mv[n]); + g_free (paramv); +} + +static void +networkconfig1_proxy_g_properties_changed (GDBusProxy *_proxy, + GVariant *changed_properties, + const gchar *const *invalidated_properties) +{ + Networkconfig1Proxy *proxy = NETWORKCONFIG1_PROXY (_proxy); + guint n; + const gchar *key; + GVariantIter *iter; + _ExtendedGDBusPropertyInfo *info; + g_variant_get (changed_properties, "a{sv}", &iter); + while (g_variant_iter_next (iter, "{&sv}", &key, NULL)) + { + info = (_ExtendedGDBusPropertyInfo *) g_dbus_interface_info_lookup_property ((GDBusInterfaceInfo *) &_networkconfig1_interface_info.parent_struct, key); + g_datalist_remove_data (&proxy->priv->qdata, key); + if (info != NULL) + g_object_notify (G_OBJECT (proxy), info->hyphen_name); + } + g_variant_iter_free (iter); + for (n = 0; invalidated_properties[n] != NULL; n++) + { + info = (_ExtendedGDBusPropertyInfo *) g_dbus_interface_info_lookup_property ((GDBusInterfaceInfo *) &_networkconfig1_interface_info.parent_struct, invalidated_properties[n]); + g_datalist_remove_data (&proxy->priv->qdata, invalidated_properties[n]); + if (info != NULL) + g_object_notify (G_OBJECT (proxy), info->hyphen_name); + } +} + +static void +networkconfig1_proxy_init (Networkconfig1Proxy *proxy) +{ +#if GLIB_VERSION_MAX_ALLOWED >= GLIB_VERSION_2_38 + proxy->priv = networkconfig1_proxy_get_instance_private (proxy); +#else + proxy->priv = G_TYPE_INSTANCE_GET_PRIVATE (proxy, TYPE_NETWORKCONFIG1_PROXY, Networkconfig1ProxyPrivate); +#endif + + g_dbus_proxy_set_interface_info (G_DBUS_PROXY (proxy), networkconfig1_interface_info ()); +} + +static void +networkconfig1_proxy_class_init (Networkconfig1ProxyClass *klass) +{ + GObjectClass *gobject_class; + GDBusProxyClass *proxy_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = networkconfig1_proxy_finalize; + gobject_class->get_property = networkconfig1_proxy_get_property; + gobject_class->set_property = networkconfig1_proxy_set_property; + + proxy_class = G_DBUS_PROXY_CLASS (klass); + proxy_class->g_signal = networkconfig1_proxy_g_signal; + proxy_class->g_properties_changed = networkconfig1_proxy_g_properties_changed; + +#if GLIB_VERSION_MAX_ALLOWED < GLIB_VERSION_2_38 + g_type_class_add_private (klass, sizeof (Networkconfig1ProxyPrivate)); +#endif +} + +static void +networkconfig1_proxy_iface_init (Networkconfig1Iface *iface) +{ +} + +/** + * networkconfig1_proxy_new: + * @connection: A #GDBusConnection. + * @flags: Flags from the #GDBusProxyFlags enumeration. + * @name: (nullable): A bus name (well-known or unique) or %NULL if @connection is not a message bus connection. + * @object_path: An object path. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied. + * @user_data: User data to pass to @callback. + * + * Asynchronously creates a proxy for the D-Bus interface com.lgi.rdk.utils.networkconfig1. See g_dbus_proxy_new() for more details. + * + * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from. + * You can then call networkconfig1_proxy_new_finish() to get the result of the operation. + * + * See networkconfig1_proxy_new_sync() for the synchronous, blocking version of this constructor. + */ +void +networkconfig1_proxy_new ( + GDBusConnection *connection, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async (TYPE_NETWORKCONFIG1_PROXY, G_PRIORITY_DEFAULT, cancellable, callback, user_data, "g-flags", flags, "g-name", name, "g-connection", connection, "g-object-path", object_path, "g-interface-name", "com.lgi.rdk.utils.networkconfig1", NULL); +} + +/** + * networkconfig1_proxy_new_finish: + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to networkconfig1_proxy_new(). + * @error: Return location for error or %NULL + * + * Finishes an operation started with networkconfig1_proxy_new(). + * + * Returns: (transfer full) (type Networkconfig1Proxy): The constructed proxy object or %NULL if @error is set. + */ +Networkconfig1 * +networkconfig1_proxy_new_finish ( + GAsyncResult *res, + GError **error) +{ + GObject *ret; + GObject *source_object; + source_object = g_async_result_get_source_object (res); + ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error); + g_object_unref (source_object); + if (ret != NULL) + return NETWORKCONFIG1 (ret); + else + return NULL; +} + +/** + * networkconfig1_proxy_new_sync: + * @connection: A #GDBusConnection. + * @flags: Flags from the #GDBusProxyFlags enumeration. + * @name: (nullable): A bus name (well-known or unique) or %NULL if @connection is not a message bus connection. + * @object_path: An object path. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL + * + * Synchronously creates a proxy for the D-Bus interface com.lgi.rdk.utils.networkconfig1. See g_dbus_proxy_new_sync() for more details. + * + * The calling thread is blocked until a reply is received. + * + * See networkconfig1_proxy_new() for the asynchronous version of this constructor. + * + * Returns: (transfer full) (type Networkconfig1Proxy): The constructed proxy object or %NULL if @error is set. + */ +Networkconfig1 * +networkconfig1_proxy_new_sync ( + GDBusConnection *connection, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GError **error) +{ + GInitable *ret; + ret = g_initable_new (TYPE_NETWORKCONFIG1_PROXY, cancellable, error, "g-flags", flags, "g-name", name, "g-connection", connection, "g-object-path", object_path, "g-interface-name", "com.lgi.rdk.utils.networkconfig1", NULL); + if (ret != NULL) + return NETWORKCONFIG1 (ret); + else + return NULL; +} + + +/** + * networkconfig1_proxy_new_for_bus: + * @bus_type: A #GBusType. + * @flags: Flags from the #GDBusProxyFlags enumeration. + * @name: A bus name (well-known or unique). + * @object_path: An object path. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @callback: A #GAsyncReadyCallback to call when the request is satisfied. + * @user_data: User data to pass to @callback. + * + * Like networkconfig1_proxy_new() but takes a #GBusType instead of a #GDBusConnection. + * + * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread you are calling this method from. + * You can then call networkconfig1_proxy_new_for_bus_finish() to get the result of the operation. + * + * See networkconfig1_proxy_new_for_bus_sync() for the synchronous, blocking version of this constructor. + */ +void +networkconfig1_proxy_new_for_bus ( + GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async (TYPE_NETWORKCONFIG1_PROXY, G_PRIORITY_DEFAULT, cancellable, callback, user_data, "g-flags", flags, "g-name", name, "g-bus-type", bus_type, "g-object-path", object_path, "g-interface-name", "com.lgi.rdk.utils.networkconfig1", NULL); +} + +/** + * networkconfig1_proxy_new_for_bus_finish: + * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to networkconfig1_proxy_new_for_bus(). + * @error: Return location for error or %NULL + * + * Finishes an operation started with networkconfig1_proxy_new_for_bus(). + * + * Returns: (transfer full) (type Networkconfig1Proxy): The constructed proxy object or %NULL if @error is set. + */ +Networkconfig1 * +networkconfig1_proxy_new_for_bus_finish ( + GAsyncResult *res, + GError **error) +{ + GObject *ret; + GObject *source_object; + source_object = g_async_result_get_source_object (res); + ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error); + g_object_unref (source_object); + if (ret != NULL) + return NETWORKCONFIG1 (ret); + else + return NULL; +} + +/** + * networkconfig1_proxy_new_for_bus_sync: + * @bus_type: A #GBusType. + * @flags: Flags from the #GDBusProxyFlags enumeration. + * @name: A bus name (well-known or unique). + * @object_path: An object path. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL + * + * Like networkconfig1_proxy_new_sync() but takes a #GBusType instead of a #GDBusConnection. + * + * The calling thread is blocked until a reply is received. + * + * See networkconfig1_proxy_new_for_bus() for the asynchronous version of this constructor. + * + * Returns: (transfer full) (type Networkconfig1Proxy): The constructed proxy object or %NULL if @error is set. + */ +Networkconfig1 * +networkconfig1_proxy_new_for_bus_sync ( + GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GError **error) +{ + GInitable *ret; + ret = g_initable_new (TYPE_NETWORKCONFIG1_PROXY, cancellable, error, "g-flags", flags, "g-name", name, "g-bus-type", bus_type, "g-object-path", object_path, "g-interface-name", "com.lgi.rdk.utils.networkconfig1", NULL); + if (ret != NULL) + return NETWORKCONFIG1 (ret); + else + return NULL; +} + + +/* ------------------------------------------------------------------------ */ + +/** + * Networkconfig1Skeleton: + * + * The #Networkconfig1Skeleton structure contains only private data and should only be accessed using the provided API. + */ + +/** + * Networkconfig1SkeletonClass: + * @parent_class: The parent class. + * + * Class structure for #Networkconfig1Skeleton. + */ + +struct _Networkconfig1SkeletonPrivate +{ + GValue *properties; + GList *changed_properties; + GSource *changed_properties_idle_source; + GMainContext *context; + GMutex lock; +}; + +static void +_networkconfig1_skeleton_handle_method_call ( + GDBusConnection *connection G_GNUC_UNUSED, + const gchar *sender G_GNUC_UNUSED, + const gchar *object_path G_GNUC_UNUSED, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + Networkconfig1Skeleton *skeleton = NETWORKCONFIG1_SKELETON (user_data); + _ExtendedGDBusMethodInfo *info; + GVariantIter iter; + GVariant *child; + GValue *paramv; + gsize num_params; + guint num_extra; + gsize n; + guint signal_id; + GValue return_value = G_VALUE_INIT; + info = (_ExtendedGDBusMethodInfo *) g_dbus_method_invocation_get_method_info (invocation); + g_assert (info != NULL); + num_params = g_variant_n_children (parameters); + num_extra = info->pass_fdlist ? 3 : 2; paramv = g_new0 (GValue, num_params + num_extra); + n = 0; + g_value_init (¶mv[n], TYPE_NETWORKCONFIG1); + g_value_set_object (¶mv[n++], skeleton); + g_value_init (¶mv[n], G_TYPE_DBUS_METHOD_INVOCATION); + g_value_set_object (¶mv[n++], invocation); + if (info->pass_fdlist) + { +#ifdef G_OS_UNIX + g_value_init (¶mv[n], G_TYPE_UNIX_FD_LIST); + g_value_set_object (¶mv[n++], g_dbus_message_get_unix_fd_list (g_dbus_method_invocation_get_message (invocation))); +#else + g_assert_not_reached (); +#endif + } + g_variant_iter_init (&iter, parameters); + while ((child = g_variant_iter_next_value (&iter)) != NULL) + { + _ExtendedGDBusArgInfo *arg_info = (_ExtendedGDBusArgInfo *) info->parent_struct.in_args[n - num_extra]; + if (arg_info->use_gvariant) + { + g_value_init (¶mv[n], G_TYPE_VARIANT); + g_value_set_variant (¶mv[n], child); + n++; + } + else + g_dbus_gvariant_to_gvalue (child, ¶mv[n++]); + g_variant_unref (child); + } + signal_id = g_signal_lookup (info->signal_name, TYPE_NETWORKCONFIG1); + g_value_init (&return_value, G_TYPE_BOOLEAN); + g_signal_emitv (paramv, signal_id, 0, &return_value); + if (!g_value_get_boolean (&return_value)) + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, "Method %s is not implemented on interface %s", method_name, interface_name); + g_value_unset (&return_value); + for (n = 0; n < num_params + num_extra; n++) + g_value_unset (¶mv[n]); + g_free (paramv); +} + +static GVariant * +_networkconfig1_skeleton_handle_get_property ( + GDBusConnection *connection G_GNUC_UNUSED, + const gchar *sender G_GNUC_UNUSED, + const gchar *object_path G_GNUC_UNUSED, + const gchar *interface_name G_GNUC_UNUSED, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + Networkconfig1Skeleton *skeleton = NETWORKCONFIG1_SKELETON (user_data); + GValue value = G_VALUE_INIT; + GParamSpec *pspec; + _ExtendedGDBusPropertyInfo *info; + GVariant *ret; + ret = NULL; + info = (_ExtendedGDBusPropertyInfo *) g_dbus_interface_info_lookup_property ((GDBusInterfaceInfo *) &_networkconfig1_interface_info.parent_struct, property_name); + g_assert (info != NULL); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (skeleton), info->hyphen_name); + if (pspec == NULL) + { + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No property with name %s", property_name); + } + else + { + g_value_init (&value, pspec->value_type); + g_object_get_property (G_OBJECT (skeleton), info->hyphen_name, &value); + ret = g_dbus_gvalue_to_gvariant (&value, G_VARIANT_TYPE (info->parent_struct.signature)); + g_value_unset (&value); + } + return ret; +} + +static gboolean +_networkconfig1_skeleton_handle_set_property ( + GDBusConnection *connection G_GNUC_UNUSED, + const gchar *sender G_GNUC_UNUSED, + const gchar *object_path G_GNUC_UNUSED, + const gchar *interface_name G_GNUC_UNUSED, + const gchar *property_name, + GVariant *variant, + GError **error, + gpointer user_data) +{ + Networkconfig1Skeleton *skeleton = NETWORKCONFIG1_SKELETON (user_data); + GValue value = G_VALUE_INIT; + GParamSpec *pspec; + _ExtendedGDBusPropertyInfo *info; + gboolean ret; + ret = FALSE; + info = (_ExtendedGDBusPropertyInfo *) g_dbus_interface_info_lookup_property ((GDBusInterfaceInfo *) &_networkconfig1_interface_info.parent_struct, property_name); + g_assert (info != NULL); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (skeleton), info->hyphen_name); + if (pspec == NULL) + { + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No property with name %s", property_name); + } + else + { + if (info->use_gvariant) + g_value_set_variant (&value, variant); + else + g_dbus_gvariant_to_gvalue (variant, &value); + g_object_set_property (G_OBJECT (skeleton), info->hyphen_name, &value); + g_value_unset (&value); + ret = TRUE; + } + return ret; +} + +static const GDBusInterfaceVTable _networkconfig1_skeleton_vtable = +{ + _networkconfig1_skeleton_handle_method_call, + _networkconfig1_skeleton_handle_get_property, + _networkconfig1_skeleton_handle_set_property, + {NULL} +}; + +static GDBusInterfaceInfo * +networkconfig1_skeleton_dbus_interface_get_info (GDBusInterfaceSkeleton *skeleton G_GNUC_UNUSED) +{ + return networkconfig1_interface_info (); +} + +static GDBusInterfaceVTable * +networkconfig1_skeleton_dbus_interface_get_vtable (GDBusInterfaceSkeleton *skeleton G_GNUC_UNUSED) +{ + return (GDBusInterfaceVTable *) &_networkconfig1_skeleton_vtable; +} + +static GVariant * +networkconfig1_skeleton_dbus_interface_get_properties (GDBusInterfaceSkeleton *_skeleton) +{ + Networkconfig1Skeleton *skeleton = NETWORKCONFIG1_SKELETON (_skeleton); + + GVariantBuilder builder; + guint n; + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + if (_networkconfig1_interface_info.parent_struct.properties == NULL) + goto out; + for (n = 0; _networkconfig1_interface_info.parent_struct.properties[n] != NULL; n++) + { + GDBusPropertyInfo *info = _networkconfig1_interface_info.parent_struct.properties[n]; + if (info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE) + { + GVariant *value; + value = _networkconfig1_skeleton_handle_get_property (g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (skeleton)), NULL, g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)), "com.lgi.rdk.utils.networkconfig1", info->name, NULL, skeleton); + if (value != NULL) + { + g_variant_take_ref (value); + g_variant_builder_add (&builder, "{sv}", info->name, value); + g_variant_unref (value); + } + } + } +out: + return g_variant_builder_end (&builder); +} + +static void +networkconfig1_skeleton_dbus_interface_flush (GDBusInterfaceSkeleton *_skeleton) +{ +} + +static void +_networkconfig1_on_signal_network_configuration_service_started ( + Networkconfig1 *object) +{ + Networkconfig1Skeleton *skeleton = NETWORKCONFIG1_SKELETON (object); + + GList *connections, *l; + GVariant *signal_variant; + connections = g_dbus_interface_skeleton_get_connections (G_DBUS_INTERFACE_SKELETON (skeleton)); + + signal_variant = g_variant_ref_sink (g_variant_new ("()")); + for (l = connections; l != NULL; l = l->next) + { + GDBusConnection *connection = l->data; + g_dbus_connection_emit_signal (connection, + NULL, g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)), "com.lgi.rdk.utils.networkconfig1", "NetworkConfigurationServiceStarted", + signal_variant, NULL); + } + g_variant_unref (signal_variant); + g_list_free_full (connections, g_object_unref); +} + +static void +_networkconfig1_on_signal_ipv4_configuration_changed ( + Networkconfig1 *object, + const gchar *arg_id) +{ + Networkconfig1Skeleton *skeleton = NETWORKCONFIG1_SKELETON (object); + + GList *connections, *l; + GVariant *signal_variant; + connections = g_dbus_interface_skeleton_get_connections (G_DBUS_INTERFACE_SKELETON (skeleton)); + + signal_variant = g_variant_ref_sink (g_variant_new ("(s)", + arg_id)); + for (l = connections; l != NULL; l = l->next) + { + GDBusConnection *connection = l->data; + g_dbus_connection_emit_signal (connection, + NULL, g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)), "com.lgi.rdk.utils.networkconfig1", "IPv4ConfigurationChanged", + signal_variant, NULL); + } + g_variant_unref (signal_variant); + g_list_free_full (connections, g_object_unref); +} + +static void +_networkconfig1_on_signal_ipv6_configuration_changed ( + Networkconfig1 *object, + const gchar *arg_id) +{ + Networkconfig1Skeleton *skeleton = NETWORKCONFIG1_SKELETON (object); + + GList *connections, *l; + GVariant *signal_variant; + connections = g_dbus_interface_skeleton_get_connections (G_DBUS_INTERFACE_SKELETON (skeleton)); + + signal_variant = g_variant_ref_sink (g_variant_new ("(s)", + arg_id)); + for (l = connections; l != NULL; l = l->next) + { + GDBusConnection *connection = l->data; + g_dbus_connection_emit_signal (connection, + NULL, g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)), "com.lgi.rdk.utils.networkconfig1", "IPv6ConfigurationChanged", + signal_variant, NULL); + } + g_variant_unref (signal_variant); + g_list_free_full (connections, g_object_unref); +} + +static void +_networkconfig1_on_signal_status_changed ( + Networkconfig1 *object, + const gchar *arg_id, + const gchar *arg_ifaceStatus) +{ + Networkconfig1Skeleton *skeleton = NETWORKCONFIG1_SKELETON (object); + + GList *connections, *l; + GVariant *signal_variant; + connections = g_dbus_interface_skeleton_get_connections (G_DBUS_INTERFACE_SKELETON (skeleton)); + + signal_variant = g_variant_ref_sink (g_variant_new ("(ss)", + arg_id, + arg_ifaceStatus)); + for (l = connections; l != NULL; l = l->next) + { + GDBusConnection *connection = l->data; + g_dbus_connection_emit_signal (connection, + NULL, g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)), "com.lgi.rdk.utils.networkconfig1", "StatusChanged", + signal_variant, NULL); + } + g_variant_unref (signal_variant); + g_list_free_full (connections, g_object_unref); +} + +static void +_networkconfig1_on_signal_networking_event ( + Networkconfig1 *object, + const gchar *arg_id, + const gchar *arg_event, + guint arg_count, + GVariant *arg_params) +{ + Networkconfig1Skeleton *skeleton = NETWORKCONFIG1_SKELETON (object); + + GList *connections, *l; + GVariant *signal_variant; + connections = g_dbus_interface_skeleton_get_connections (G_DBUS_INTERFACE_SKELETON (skeleton)); + + signal_variant = g_variant_ref_sink (g_variant_new ("(ssu@a{ss})", + arg_id, + arg_event, + arg_count, + arg_params)); + for (l = connections; l != NULL; l = l->next) + { + GDBusConnection *connection = l->data; + g_dbus_connection_emit_signal (connection, + NULL, g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)), "com.lgi.rdk.utils.networkconfig1", "NetworkingEvent", + signal_variant, NULL); + } + g_variant_unref (signal_variant); + g_list_free_full (connections, g_object_unref); +} + +static void networkconfig1_skeleton_iface_init (Networkconfig1Iface *iface); +#if GLIB_VERSION_MAX_ALLOWED >= GLIB_VERSION_2_38 +G_DEFINE_TYPE_WITH_CODE (Networkconfig1Skeleton, networkconfig1_skeleton, G_TYPE_DBUS_INTERFACE_SKELETON, + G_ADD_PRIVATE (Networkconfig1Skeleton) + G_IMPLEMENT_INTERFACE (TYPE_NETWORKCONFIG1, networkconfig1_skeleton_iface_init)) + +#else +G_DEFINE_TYPE_WITH_CODE (Networkconfig1Skeleton, networkconfig1_skeleton, G_TYPE_DBUS_INTERFACE_SKELETON, + G_IMPLEMENT_INTERFACE (TYPE_NETWORKCONFIG1, networkconfig1_skeleton_iface_init)) + +#endif +static void +networkconfig1_skeleton_finalize (GObject *object) +{ + Networkconfig1Skeleton *skeleton = NETWORKCONFIG1_SKELETON (object); + g_list_free_full (skeleton->priv->changed_properties, (GDestroyNotify) _changed_property_free); + if (skeleton->priv->changed_properties_idle_source != NULL) + g_source_destroy (skeleton->priv->changed_properties_idle_source); + g_main_context_unref (skeleton->priv->context); + g_mutex_clear (&skeleton->priv->lock); + G_OBJECT_CLASS (networkconfig1_skeleton_parent_class)->finalize (object); +} + +static void +networkconfig1_skeleton_init (Networkconfig1Skeleton *skeleton) +{ +#if GLIB_VERSION_MAX_ALLOWED >= GLIB_VERSION_2_38 + skeleton->priv = networkconfig1_skeleton_get_instance_private (skeleton); +#else + skeleton->priv = G_TYPE_INSTANCE_GET_PRIVATE (skeleton, TYPE_NETWORKCONFIG1_SKELETON, Networkconfig1SkeletonPrivate); +#endif + + g_mutex_init (&skeleton->priv->lock); + skeleton->priv->context = g_main_context_ref_thread_default (); +} + +static void +networkconfig1_skeleton_class_init (Networkconfig1SkeletonClass *klass) +{ + GObjectClass *gobject_class; + GDBusInterfaceSkeletonClass *skeleton_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = networkconfig1_skeleton_finalize; + + skeleton_class = G_DBUS_INTERFACE_SKELETON_CLASS (klass); + skeleton_class->get_info = networkconfig1_skeleton_dbus_interface_get_info; + skeleton_class->get_properties = networkconfig1_skeleton_dbus_interface_get_properties; + skeleton_class->flush = networkconfig1_skeleton_dbus_interface_flush; + skeleton_class->get_vtable = networkconfig1_skeleton_dbus_interface_get_vtable; + +#if GLIB_VERSION_MAX_ALLOWED < GLIB_VERSION_2_38 + g_type_class_add_private (klass, sizeof (Networkconfig1SkeletonPrivate)); +#endif +} + +static void +networkconfig1_skeleton_iface_init (Networkconfig1Iface *iface) +{ + iface->network_configuration_service_started = _networkconfig1_on_signal_network_configuration_service_started; + iface->ipv4_configuration_changed = _networkconfig1_on_signal_ipv4_configuration_changed; + iface->ipv6_configuration_changed = _networkconfig1_on_signal_ipv6_configuration_changed; + iface->status_changed = _networkconfig1_on_signal_status_changed; + iface->networking_event = _networkconfig1_on_signal_networking_event; +} + +/** + * networkconfig1_skeleton_new: + * + * Creates a skeleton object for the D-Bus interface com.lgi.rdk.utils.networkconfig1. + * + * Returns: (transfer full) (type Networkconfig1Skeleton): The skeleton object. + */ +Networkconfig1 * +networkconfig1_skeleton_new (void) +{ + return NETWORKCONFIG1 (g_object_new (TYPE_NETWORKCONFIG1_SKELETON, NULL)); +} diff --git a/LgiNetwork/dbus/gdbus-codegen-impl/lginetwork_dbus_api.h b/LgiNetwork/dbus/gdbus-codegen-impl/lginetwork_dbus_api.h new file mode 100644 index 0000000000..2b313d50e8 --- /dev/null +++ b/LgiNetwork/dbus/gdbus-codegen-impl/lginetwork_dbus_api.h @@ -0,0 +1,583 @@ +/* + * Generated by gdbus-codegen 2.56.4. DO NOT EDIT. + * + * The license of this code is the same as for the D-Bus interface description + * it was derived from. + */ + +#ifndef __LGINETWORK_DBUS_API_H__ +#define __LGINETWORK_DBUS_API_H__ + +#include + +G_BEGIN_DECLS + + +/* ------------------------------------------------------------------------ */ +/* Declarations for com.lgi.rdk.utils.networkconfig1 */ + +#define TYPE_NETWORKCONFIG1 (networkconfig1_get_type ()) +#define NETWORKCONFIG1(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TYPE_NETWORKCONFIG1, Networkconfig1)) +#define IS_NETWORKCONFIG1(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TYPE_NETWORKCONFIG1)) +#define NETWORKCONFIG1_GET_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), TYPE_NETWORKCONFIG1, Networkconfig1Iface)) + +struct _Networkconfig1; +typedef struct _Networkconfig1 Networkconfig1; +typedef struct _Networkconfig1Iface Networkconfig1Iface; + +struct _Networkconfig1Iface +{ + GTypeInterface parent_iface; + + + gboolean (*handle_dhcprenew) ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation); + + gboolean (*handle_enable) ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + const gchar *arg_id, + gboolean arg_enable); + + gboolean (*handle_get_active_interface) ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation); + + gboolean (*handle_get_interfaces) ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation); + + gboolean (*handle_get_param) ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + const gchar *arg_id, + const gchar *arg_paramName); + + gboolean (*handle_get_params) ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + const gchar *arg_id); + + gboolean (*handle_get_status) ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + const gchar *arg_id); + + gboolean (*handle_is_enabled) ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + const gchar *arg_id); + + gboolean (*handle_reset_configuration) ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation); + + gboolean (*handle_send_wake_up_request) ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + const gchar *arg_mac); + + gboolean (*handle_set_active_interface) ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + const gchar *arg_id); + + gboolean (*handle_set_param) ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + const gchar *arg_id, + const gchar *arg_paramName, + const gchar *arg_paramValue); + + void (*ipv4_configuration_changed) ( + Networkconfig1 *object, + const gchar *arg_id); + + void (*ipv6_configuration_changed) ( + Networkconfig1 *object, + const gchar *arg_id); + + void (*network_configuration_service_started) ( + Networkconfig1 *object); + + void (*networking_event) ( + Networkconfig1 *object, + const gchar *arg_id, + const gchar *arg_event, + guint arg_count, + GVariant *arg_params); + + void (*status_changed) ( + Networkconfig1 *object, + const gchar *arg_id, + const gchar *arg_ifaceStatus); + +}; + +GType networkconfig1_get_type (void) G_GNUC_CONST; + +GDBusInterfaceInfo *networkconfig1_interface_info (void); +guint networkconfig1_override_properties (GObjectClass *klass, guint property_id_begin); + + +/* D-Bus method call completion functions: */ +void networkconfig1_complete_get_interfaces ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + guint count, + const gchar *const *ids); + +void networkconfig1_complete_enable ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status); + +void networkconfig1_complete_is_enabled ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status, + gboolean enabled); + +void networkconfig1_complete_get_active_interface ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + const gchar *id, + gint status); + +void networkconfig1_complete_set_active_interface ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status); + +void networkconfig1_complete_get_params ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status, + guint count, + GVariant *params); + +void networkconfig1_complete_get_param ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status, + const gchar *paramValue); + +void networkconfig1_complete_set_param ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status); + +void networkconfig1_complete_dhcprenew ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status); + +void networkconfig1_complete_get_status ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status, + const gchar *ifaceStatus); + +void networkconfig1_complete_reset_configuration ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status); + +void networkconfig1_complete_send_wake_up_request ( + Networkconfig1 *object, + GDBusMethodInvocation *invocation, + gint status); + + + +/* D-Bus signal emissions functions: */ +void networkconfig1_emit_network_configuration_service_started ( + Networkconfig1 *object); + +void networkconfig1_emit_ipv4_configuration_changed ( + Networkconfig1 *object, + const gchar *arg_id); + +void networkconfig1_emit_ipv6_configuration_changed ( + Networkconfig1 *object, + const gchar *arg_id); + +void networkconfig1_emit_status_changed ( + Networkconfig1 *object, + const gchar *arg_id, + const gchar *arg_ifaceStatus); + +void networkconfig1_emit_networking_event ( + Networkconfig1 *object, + const gchar *arg_id, + const gchar *arg_event, + guint arg_count, + GVariant *arg_params); + + + +/* D-Bus method calls: */ +void networkconfig1_call_get_interfaces ( + Networkconfig1 *proxy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean networkconfig1_call_get_interfaces_finish ( + Networkconfig1 *proxy, + guint *out_count, + gchar ***out_ids, + GAsyncResult *res, + GError **error); + +gboolean networkconfig1_call_get_interfaces_sync ( + Networkconfig1 *proxy, + guint *out_count, + gchar ***out_ids, + GCancellable *cancellable, + GError **error); + +void networkconfig1_call_enable ( + Networkconfig1 *proxy, + const gchar *arg_id, + gboolean arg_enable, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean networkconfig1_call_enable_finish ( + Networkconfig1 *proxy, + gint *out_status, + GAsyncResult *res, + GError **error); + +gboolean networkconfig1_call_enable_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gboolean arg_enable, + gint *out_status, + GCancellable *cancellable, + GError **error); + +void networkconfig1_call_is_enabled ( + Networkconfig1 *proxy, + const gchar *arg_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean networkconfig1_call_is_enabled_finish ( + Networkconfig1 *proxy, + gint *out_status, + gboolean *out_enabled, + GAsyncResult *res, + GError **error); + +gboolean networkconfig1_call_is_enabled_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gint *out_status, + gboolean *out_enabled, + GCancellable *cancellable, + GError **error); + +void networkconfig1_call_get_active_interface ( + Networkconfig1 *proxy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean networkconfig1_call_get_active_interface_finish ( + Networkconfig1 *proxy, + gchar **out_id, + gint *out_status, + GAsyncResult *res, + GError **error); + +gboolean networkconfig1_call_get_active_interface_sync ( + Networkconfig1 *proxy, + gchar **out_id, + gint *out_status, + GCancellable *cancellable, + GError **error); + +void networkconfig1_call_set_active_interface ( + Networkconfig1 *proxy, + const gchar *arg_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean networkconfig1_call_set_active_interface_finish ( + Networkconfig1 *proxy, + gint *out_status, + GAsyncResult *res, + GError **error); + +gboolean networkconfig1_call_set_active_interface_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gint *out_status, + GCancellable *cancellable, + GError **error); + +void networkconfig1_call_get_params ( + Networkconfig1 *proxy, + const gchar *arg_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean networkconfig1_call_get_params_finish ( + Networkconfig1 *proxy, + gint *out_status, + guint *out_count, + GVariant **out_params, + GAsyncResult *res, + GError **error); + +gboolean networkconfig1_call_get_params_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gint *out_status, + guint *out_count, + GVariant **out_params, + GCancellable *cancellable, + GError **error); + +void networkconfig1_call_get_param ( + Networkconfig1 *proxy, + const gchar *arg_id, + const gchar *arg_paramName, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean networkconfig1_call_get_param_finish ( + Networkconfig1 *proxy, + gint *out_status, + gchar **out_paramValue, + GAsyncResult *res, + GError **error); + +gboolean networkconfig1_call_get_param_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + const gchar *arg_paramName, + gint *out_status, + gchar **out_paramValue, + GCancellable *cancellable, + GError **error); + +void networkconfig1_call_set_param ( + Networkconfig1 *proxy, + const gchar *arg_id, + const gchar *arg_paramName, + const gchar *arg_paramValue, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean networkconfig1_call_set_param_finish ( + Networkconfig1 *proxy, + gint *out_status, + GAsyncResult *res, + GError **error); + +gboolean networkconfig1_call_set_param_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + const gchar *arg_paramName, + const gchar *arg_paramValue, + gint *out_status, + GCancellable *cancellable, + GError **error); + +void networkconfig1_call_dhcprenew ( + Networkconfig1 *proxy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean networkconfig1_call_dhcprenew_finish ( + Networkconfig1 *proxy, + gint *out_status, + GAsyncResult *res, + GError **error); + +gboolean networkconfig1_call_dhcprenew_sync ( + Networkconfig1 *proxy, + gint *out_status, + GCancellable *cancellable, + GError **error); + +void networkconfig1_call_get_status ( + Networkconfig1 *proxy, + const gchar *arg_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean networkconfig1_call_get_status_finish ( + Networkconfig1 *proxy, + gint *out_status, + gchar **out_ifaceStatus, + GAsyncResult *res, + GError **error); + +gboolean networkconfig1_call_get_status_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gint *out_status, + gchar **out_ifaceStatus, + GCancellable *cancellable, + GError **error); + +void networkconfig1_call_reset_configuration ( + Networkconfig1 *proxy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean networkconfig1_call_reset_configuration_finish ( + Networkconfig1 *proxy, + gint *out_status, + GAsyncResult *res, + GError **error); + +gboolean networkconfig1_call_reset_configuration_sync ( + Networkconfig1 *proxy, + gint *out_status, + GCancellable *cancellable, + GError **error); + +void networkconfig1_call_send_wake_up_request ( + Networkconfig1 *proxy, + const gchar *arg_mac, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean networkconfig1_call_send_wake_up_request_finish ( + Networkconfig1 *proxy, + gint *out_status, + GAsyncResult *res, + GError **error); + +gboolean networkconfig1_call_send_wake_up_request_sync ( + Networkconfig1 *proxy, + const gchar *arg_mac, + gint *out_status, + GCancellable *cancellable, + GError **error); + + + +/* ---- */ + +#define TYPE_NETWORKCONFIG1_PROXY (networkconfig1_proxy_get_type ()) +#define NETWORKCONFIG1_PROXY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TYPE_NETWORKCONFIG1_PROXY, Networkconfig1Proxy)) +#define NETWORKCONFIG1_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), TYPE_NETWORKCONFIG1_PROXY, Networkconfig1ProxyClass)) +#define NETWORKCONFIG1_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TYPE_NETWORKCONFIG1_PROXY, Networkconfig1ProxyClass)) +#define IS_NETWORKCONFIG1_PROXY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TYPE_NETWORKCONFIG1_PROXY)) +#define IS_NETWORKCONFIG1_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TYPE_NETWORKCONFIG1_PROXY)) + +typedef struct _Networkconfig1Proxy Networkconfig1Proxy; +typedef struct _Networkconfig1ProxyClass Networkconfig1ProxyClass; +typedef struct _Networkconfig1ProxyPrivate Networkconfig1ProxyPrivate; + +struct _Networkconfig1Proxy +{ + /*< private >*/ + GDBusProxy parent_instance; + Networkconfig1ProxyPrivate *priv; +}; + +struct _Networkconfig1ProxyClass +{ + GDBusProxyClass parent_class; +}; + +GType networkconfig1_proxy_get_type (void) G_GNUC_CONST; + +#if GLIB_CHECK_VERSION(2, 44, 0) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (Networkconfig1Proxy, g_object_unref) +#endif + +void networkconfig1_proxy_new ( + GDBusConnection *connection, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +Networkconfig1 *networkconfig1_proxy_new_finish ( + GAsyncResult *res, + GError **error); +Networkconfig1 *networkconfig1_proxy_new_sync ( + GDBusConnection *connection, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GError **error); + +void networkconfig1_proxy_new_for_bus ( + GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +Networkconfig1 *networkconfig1_proxy_new_for_bus_finish ( + GAsyncResult *res, + GError **error); +Networkconfig1 *networkconfig1_proxy_new_for_bus_sync ( + GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GError **error); + + +/* ---- */ + +#define TYPE_NETWORKCONFIG1_SKELETON (networkconfig1_skeleton_get_type ()) +#define NETWORKCONFIG1_SKELETON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TYPE_NETWORKCONFIG1_SKELETON, Networkconfig1Skeleton)) +#define NETWORKCONFIG1_SKELETON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), TYPE_NETWORKCONFIG1_SKELETON, Networkconfig1SkeletonClass)) +#define NETWORKCONFIG1_SKELETON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TYPE_NETWORKCONFIG1_SKELETON, Networkconfig1SkeletonClass)) +#define IS_NETWORKCONFIG1_SKELETON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TYPE_NETWORKCONFIG1_SKELETON)) +#define IS_NETWORKCONFIG1_SKELETON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TYPE_NETWORKCONFIG1_SKELETON)) + +typedef struct _Networkconfig1Skeleton Networkconfig1Skeleton; +typedef struct _Networkconfig1SkeletonClass Networkconfig1SkeletonClass; +typedef struct _Networkconfig1SkeletonPrivate Networkconfig1SkeletonPrivate; + +struct _Networkconfig1Skeleton +{ + /*< private >*/ + GDBusInterfaceSkeleton parent_instance; + Networkconfig1SkeletonPrivate *priv; +}; + +struct _Networkconfig1SkeletonClass +{ + GDBusInterfaceSkeletonClass parent_class; +}; + +GType networkconfig1_skeleton_get_type (void) G_GNUC_CONST; + +#if GLIB_CHECK_VERSION(2, 44, 0) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (Networkconfig1Skeleton, g_object_unref) +#endif + +Networkconfig1 *networkconfig1_skeleton_new (void); + + +G_END_DECLS + +#endif /* __LGINETWORK_DBUS_API_H__ */ diff --git a/LgiNetwork/dbus/lginetwork_client.cpp b/LgiNetwork/dbus/lginetwork_client.cpp new file mode 100644 index 0000000000..f781214491 --- /dev/null +++ b/LgiNetwork/dbus/lginetwork_client.cpp @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2022, 2023 LIBERTY GLOBAL all rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lginetwork_client.hpp" +#include "UtilsLogging.h" + +namespace lginet +{ + +using namespace WPEFramework; + +class DbusHandlerCallbacks { + DbusHandlerCallbacks() = delete; + +public: + static LgiNetworkClient* castUserData(gpointer aUserData) + { + return reinterpret_cast(aUserData); + } + + static void cbHandleStatusChanged(LgiNetworkClient* aNetworkConfigProxy, + const gchar *aId, + const gchar *aIfaceStatus, + gpointer aUserData) + { + return reinterpret_cast(aUserData)->onHandleStatusChanged(aNetworkConfigProxy, aId, aIfaceStatus); + } + + static void cbHandleIPv4ConfigurationChanged(LgiNetworkClient* aNetworkConfigProxy, + const gchar* aId, + gpointer aUserData) + { + return reinterpret_cast(aUserData)->onHandleIpv4ConfigurationChanged(aNetworkConfigProxy, aId); + } + + static void cbHandleIPv6ConfigurationChanged(LgiNetworkClient* aNetworkConfigProxy, + const gchar* aId, + gpointer aUserData) + { + return reinterpret_cast(aUserData)->onHandleIpv6ConfigurationChanged(aNetworkConfigProxy, aId); + } + + static void cbHandleNetworkingEvent(LgiNetworkClient* aNetworkConfigProxy, + const gchar* aId, + const gchar* aEvent, + guint aCount, + GVariant* aParams, + gpointer aUserData) + { + return reinterpret_cast(aUserData)->onHandleNetworkingEvent(aNetworkConfigProxy, aId, aEvent, aCount, aParams); + } +}; + +LgiNetworkClient::LgiNetworkClient() +{ + LOGINFO("user_data: %p", this); +#ifdef GDBUS_USE_CODEGEN_IMPL + LOGINFO("using gdbus-codegen impl"); +#else + LOGINFO("using gdbusproxy impl"); +#endif +} + +LgiNetworkClient::~LgiNetworkClient() +{ + if (m_interface) + Stop(); + LOGINFO("user_data: %p", this); +} + +void LgiNetworkClient::connectSignal(const char* signalName, + GCallback callback) +{ +#ifdef GDBUS_USE_CODEGEN_IMPL + auto object = m_interface; +#else + auto object = m_interface->proxy; +#endif + + auto handle = g_signal_connect(object, signalName, callback, this); + if (!handle) + { + LOGERR("Cannot connect to signal %s", signalName); + } + else + { + m_signalHandles.push_back(handle); + } +} + +void LgiNetworkClient::disconnectAllSignals() +{ +#ifdef GDBUS_USE_CODEGEN_IMPL + auto object = m_interface; +#else + auto object = m_interface->proxy; +#endif + for (auto handle : m_signalHandles) + g_signal_handler_disconnect(object, handle); + m_signalHandles.clear(); +} + +// returns vector containing all network interfaces +std::vector* LgiNetworkClient::getInterfaces() +{ + GError* error = nullptr; + guint count = 0; + guint idx = 0; + gchar** out_ids = nullptr; + std::vector* result = nullptr; + + if (networkconfig1_call_get_interfaces_sync(m_interface, &count, &out_ids, nullptr, &error)) + { + result = new std::vector(); + while (idx < count) + { + result->push_back(string(out_ids[idx++])); + } + } + else + { + LOGERR("Failed to call networkconfig1_call_get_interfaces_sync - %s", error ? error->message : "(unknown)"); // ? + if (error) + g_error_free(error); + } + + if (out_ids) { + for (idx = 0; idx < count; ++idx) { + g_free(out_ids[idx]); + } + g_free(out_ids); + } + + return result; +} + +// populates vector of pairs containing all parameters for specified iface +bool LgiNetworkClient::getParamsForInterface(const std::string iface, std::vector>& params) +{ + GError* error = nullptr; + guint out_count = 0; + gint out_status = 1; + GVariant* out_params = nullptr; + + if (networkconfig1_call_get_params_sync(m_interface, iface.c_str(), &out_status, &out_count, &out_params, nullptr, &error)) + { + if (out_status) + { + LOGERR("interface %s doesn't exist", iface.c_str()); + if (out_params) + { + g_variant_unref(out_params); + } + return false; + } + GVariantIter iter; + gchar* key; + gchar* value; + + if (out_params) + { + g_variant_iter_init(&iter, out_params); + while (g_variant_iter_loop (&iter, "{ss}", &key, &value)) + { + params.push_back(make_pair(std::string(key), std::string(value))); + } + g_variant_unref(out_params); + } + + return true; + } + else + { + LOGERR("Failed to call networkconfig1_call_get_params_sync - %s", error ? error->message : "(unknown)"); // ? + if (error) + g_error_free(error); + return false; + } +} + +// populates a map containing specified parameters for specified iface +bool LgiNetworkClient::getSpecificParamsForInterface(const std::string iface, std::map& params) +{ + GError* error = nullptr; + guint out_count = 0; + gint out_status = 1; + GVariant* out_params = nullptr; + + if (!params.empty() && networkconfig1_call_get_params_sync(m_interface, iface.c_str(), &out_status, &out_count, &out_params, nullptr, &error)) + { + if (out_status) + { + LOGERR("interface %s doesn't exist", iface.c_str()); + if (out_params) + g_variant_unref(out_params); + return false; + } + GVariantIter iter; + gchar* key; + gchar* value; + + if (out_params) + { + g_variant_iter_init(&iter, out_params); + while (g_variant_iter_loop (&iter, "{ss}", &key, &value)) + { + std::string skey(key); + auto iter = params.find(skey); + if (iter != params.end()) + iter->second = string(value); + } + g_variant_unref(out_params); + } + + return true; + } + else + { + return false; + } +} + +std::string LgiNetworkClient::getDefaultInterface() +{ + std::string iface; + GError* error = nullptr; + gchar* out_id = nullptr; + gint out_status = 0; + if (networkconfig1_call_get_active_interface_sync(m_interface, &out_id, &out_status, nullptr, &error)) + { + iface.assign(out_id); + g_free(out_id); + } + else + { + LOGERR("Failed to get default interface: %s", error ? error->message : "(unknown)"); + if (error) + g_error_free(error); + } + return iface; +} + +bool LgiNetworkClient::isInterfaceEnabled(const std::string iface) +{ + gint out_status = 0; + gboolean out_enabled = false; + GError* error = nullptr; + if (networkconfig1_call_is_enabled_sync(m_interface, iface.c_str(), &out_status, &out_enabled, nullptr, &error)) + return out_enabled; + else + { + if (error) + { + LOGERR("is_enabled_sync for %s failed: %s", iface.c_str(), error->message); + g_error_free(error); + } + return false; + } +} + +bool LgiNetworkClient::setInterfaceEnabled(const std::string iface, bool newstate) +{ + gint out_status = 0; + GError* error = nullptr; + if (networkconfig1_call_enable_sync(m_interface, iface.c_str(), newstate, &out_status, nullptr, &error)) + return true; + else + { + if (error) + { + LOGERR("Failed to %s interface %s: %s", newstate ? "enable" : "disable", iface.c_str(), error->message); + g_error_free(error); + } + return false; + } +} + +void LgiNetworkClient::onHandleNetworkingEvent(LgiNetworkClient* aNetworkConfigProxy, + const gchar* aId, + const gchar* aEvent, + guint aCount, + GVariant* aParams) +{ + if (aCount && aEvent && onNetworkingEvent) + { + std::map params; + GVariantIter lIter; + gchar* lKey = nullptr; + gchar* lValue = nullptr; + g_variant_iter_init(&lIter, aParams); + while (g_variant_iter_loop (&lIter, "{ss}", &lKey, &lValue)) + { + string key(lKey); + string value(lValue); + params[lKey] = lValue; + } + onNetworkingEvent(std::string(aId), std::string(aEvent), params); + } +} + +void LgiNetworkClient::onHandleIpv4ConfigurationChanged(LgiNetworkClient* aNetworkConfigProxy, + const gchar* aId) +{ + if (onHandleIpv4ConfigurationChangedEvent) + onHandleIpv4ConfigurationChangedEvent(aId); +} + +void LgiNetworkClient::onHandleIpv6ConfigurationChanged(LgiNetworkClient* aNetworkConfigProxy, + const gchar* aId) +{ + if (onHandleIpv6ConfigurationChangedEvent) + onHandleIpv6ConfigurationChangedEvent(aId); +} + +void LgiNetworkClient::onHandleStatusChanged(LgiNetworkClient* aNetworkConfigProxy, + const gchar *aId, + const gchar *aIfaceStatus) +{ + if (onStatusChangeEvent) + onStatusChangeEvent(std::string(aId), std::string(aIfaceStatus)); +} + +#ifndef GDBUS_USE_CODEGEN_IMPL +static void handle_dbus_event(GDBusProxy *proxy, + char *sender_name, + char *_signal_name, + GVariant *parameters, + gpointer user_data) +{ + std::string signal_name{_signal_name}; + + const gsize num_params = g_variant_n_children(parameters); + GVariantIter iter; + g_variant_iter_init(&iter, parameters); + + LgiNetworkClient *client = static_cast(user_data); + + // store the return values from g_variant_iter_next_value to release them later + std::vector variants; + + for (gsize i=0; imessage); + g_error_free(error); + } + m_initialized.set_value(m_interface != nullptr); + + LOGINFO("LgiNetworkClient::Worker() start main loop TID: %u", gettid()); + g_main_loop_run(m_mainLoop); // blocks + LOGINFO("LgiNetworkClient::Worker() main loop finished TID: %u", gettid()); + g_main_context_pop_thread_default(m_mainContext); + g_main_loop_unref(m_mainLoop); + g_main_context_unref(m_mainContext); + m_mainLoop = nullptr; + m_mainContext = nullptr; + } + else { + LOGERR("Failed to create glib main loop"); + m_initialized.set_value(false); + } +} + +static void release_networkconfig1(Networkconfig1* interface) +{ +#ifdef GDBUS_USE_CODEGEN_IMPL + g_object_unref(interface); +#else + if (interface->proxy) g_object_unref(interface->proxy); + g_dbus_connection_flush_sync(interface->connection, NULL, NULL); + g_object_unref(interface->connection); + g_free(interface); +#endif +} + +void LgiNetworkClient::Stop() +{ + if (m_interface) + { + disconnectAllSignals(); + + g_main_context_invoke(m_mainContext, +[](gpointer ptr) -> gboolean { + LOGINFO("LgiNetworkClient::Stop() quit main loop TID: %u", gettid()); + g_main_loop_quit((GMainLoop*)ptr); + return FALSE; + }, (gpointer)m_mainLoop); + + if(m_loopThread.joinable()) { + m_loopThread.join(); + } + else { + LOGERR("Worker thread should be joinable"); + } + release_networkconfig1(m_interface); + m_interface = nullptr; + LOGINFO("signals disconnected"); + } +} + +} // namespace lginet + diff --git a/LgiNetwork/dbus/lginetwork_client.hpp b/LgiNetwork/dbus/lginetwork_client.hpp new file mode 100644 index 0000000000..44cdd4137e --- /dev/null +++ b/LgiNetwork/dbus/lginetwork_client.hpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2022, 2023 LIBERTY GLOBAL all rights reserved. + */ + +#ifndef LGINETWORK_CLIENT_HPP_ +#define LGINETWORK_CLIENT_HPP_ + +#include +#include +#include +#include +#include +#include +#ifdef GDBUS_USE_CODEGEN_IMPL + #include "gdbus-codegen-impl/lginetwork_dbus_api.h" +#else + #include "lginetwork_dbus_api.h" +#endif + +#include "../Module.h" + +#define NETWORK_CONFIG_DBUS_INTERFACE_NAME "com.lgi.rdk.utils.networkconfig1" +#define NETWORK_CONFIG_DBUS_INTERFACE_OBJECT_PATH "/com/lgi/rdk/utils/networkconfig1" + +namespace lginet +{ + +/** Interface configuration parameters **/ +static const std::string ParamType = "type"; +static const std::string ParamMac = "mac"; +static const std::string ParamIpv4Ip = "ipv4.ip"; +static const std::string ParamIpv4Mask = "ipv4.mask"; +static const std::string ParamIpv4PrefixLength = "ipv4.prefix_length"; +static const std::string ParamIpv4Gateway = "ipv4.gateway"; +static const std::string ParamIpv4DhcpServer = "ipv4.dhcpserver"; +static const std::string ParamIpv4DhcpOptionPrefix = "ipv4.dhcpoption."; +static const std::string ParamIpv6Ip = "ipv6.ip"; +static const std::string ParamIpv6PrefixLength = "ipv6.prefix_length"; +static const std::string ParamIpv6Assignment = "ipv6.assignment"; +static const std::string ParamIpv6AssignmentDHCPv6 = "DHCPv6"; +static const std::string ParamIpv6AssignmentSLAAC = "SLAAC"; +static const std::string ParamIpv6Gateway = "ipv6.gateway"; +static const std::string ParamIpv6DhcpServer = "ipv6.dhcpserver"; +static const std::string ParamIpv6DhcpOptionPrefix = "ipv6.dhcpoption."; +static const std::string ParamDns = "dns"; +static const std::string ParamNetid = "netid"; +static const std::string ParamStrength = "strength"; +static const std::string ParamLinkSpeed = "link_speed"; +static const std::string ParamWakeOn = "wakeon"; +static const std::string ParamIpTimeout = "ip.timeout"; +static const std::string ParamWifiTimeout = "wifi.timeout"; +static const std::string ParamWifiCountry = "wifi.country"; +static const std::string ParamWifiCountryLoader = "wifi.country.loader"; +static const std::string ParamWifiLinkSpeedRx = "wifi.link_speed.rx"; +static const std::string ParamWifiLinkSpeedTx = "wifi.link_speed.tx"; +static const std::string ParamWifiRssi = "wifi.rssi"; +static const std::string ParamWifiOperatingStandards = "wifi.operating_standards"; +static const std::string ParamHwTemperature = "hw.temperature"; +static const std::string ParamArpEntries = "arp.entries"; +static const std::string ParamDuplex = "duplex"; +static const std::string ParamHostname = "hostname"; +static const std::string ParamConnectivity = "connectivity"; +static const std::string ParamNetworkChanged = "network.changed"; +static const std::string ParamWolProcessTimeout = "wol_process.timeout"; + +typedef void (*StatusChangeEventHandler)(const std::string id, const std::string status); +typedef void (*NetworkingEventEventHandler)(const std::string id, const std::string status, + const std::map params); +typedef void (*IpConfigurationChangedEvent)(const std::string id); + +class LgiNetworkClient +{ + friend class DbusHandlerCallbacks; + +public: + LgiNetworkClient(); + ~LgiNetworkClient(); + LgiNetworkClient(const LgiNetworkClient&) = delete; + LgiNetworkClient& operator=(LgiNetworkClient const&) = delete; + LgiNetworkClient(const LgiNetworkClient&&) = delete; + LgiNetworkClient& operator=(LgiNetworkClient const&&) = delete; + int Run(); + void Stop(); + + std::vector* getInterfaces(); + bool getParamsForInterface(const std::string iface, std::vector>& params); + bool getSpecificParamsForInterface(const std::string iface, std::map& params); + bool isInterfaceEnabled(const std::string iface); + bool setInterfaceEnabled(const std::string iface, bool newstate); + std::string getDefaultInterface(); + + StatusChangeEventHandler onStatusChangeEvent; + NetworkingEventEventHandler onNetworkingEvent; + IpConfigurationChangedEvent onHandleIpv4ConfigurationChangedEvent; + IpConfigurationChangedEvent onHandleIpv6ConfigurationChangedEvent; + +private: + + void onHandleNetworkingEvent(LgiNetworkClient* aNetworkConfigProxy, + const gchar* aId, + const gchar* aEvent, + guint aCount, + GVariant* aParams); + void onHandleIpv4ConfigurationChanged(LgiNetworkClient* aNetworkConfigProxy, + const gchar* aId); + void onHandleIpv6ConfigurationChanged(LgiNetworkClient* aNetworkConfigProxy, + const gchar* aId); + void onHandleStatusChanged(LgiNetworkClient* aNetworkConfigProxy, + const gchar *aId, + const gchar *aIfaceStatus); + + void connectSignal(const char* signalName, GCallback callback); + void disconnectAllSignals(); + void Worker(); + + Networkconfig1* m_interface; + std::vector m_signalHandles; + std::thread m_loopThread; + std::promise m_initialized; + GMainContext *m_mainContext; + GMainLoop *m_mainLoop; +}; + +} // namespace lginet + +#endif // #ifdef LGINETWORK_CLIENT_HPP_ diff --git a/LgiNetwork/dbus/lginetwork_dbus_api.c b/LgiNetwork/dbus/lginetwork_dbus_api.c new file mode 100644 index 0000000000..ef32c0e167 --- /dev/null +++ b/LgiNetwork/dbus/lginetwork_dbus_api.c @@ -0,0 +1,193 @@ + +#include "lginetwork_dbus_api.h" + +Networkconfig1 *networkconfig1_proxy_new_for_bus_sync( + GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GError **error) +{ + static const gchar *INTERFACE_NAME = "com.lgi.rdk.utils.networkconfig1"; + + GDBusConnection *connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, cancellable, error); + if (!connection) + { + printf("networkconfig1_proxy_new_for_bus_sync: failed g_bus_get_sync; error: '%s'\n", (*error) ? (*error)->message : "?"); + return NULL; + } + else + { + GDBusProxy *proxy = g_dbus_proxy_new_sync( + connection, + flags, + NULL, // A GDBusInterfaceInfo specifying the minimal interface that proxy conforms to or NULL + name, + object_path, + INTERFACE_NAME, + NULL, // cancellable + error); + if (!proxy) + { + printf("networkconfig1_proxy_new_for_bus_sync: failed g_dbus_proxy_new_sync; error: '%s'\n", (*error) ? (*error)->message : "?"); + g_object_unref(connection); + return NULL; + } + else + { + Networkconfig1 *ret = g_malloc(sizeof(Networkconfig1)); + ret->connection = connection; + ret->proxy = proxy; + return ret; + } + } +} + +/* networkconfig1_call_get_interfaces_sync method is almost directly copied from gdbus-codegen generated version + the only change is in first arg to g_dbus_proxy_call_sync */ +gboolean networkconfig1_call_get_interfaces_sync( + Networkconfig1 *proxy, + guint *out_count, + gchar ***out_ids, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync(proxy->proxy, + "GetInterfaces", + g_variant_new("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get(_ret, + "(u^as)", + out_count, + out_ids); + g_variant_unref(_ret); +_out: + return _ret != NULL; +} + +/* networkconfig1_call_get_params_sync method is almost directly copied from gdbus-codegen generated version + the only change is in first arg to g_dbus_proxy_call_sync */ +gboolean networkconfig1_call_get_params_sync( + Networkconfig1 *proxy, + const gchar *arg_id, + gint *out_status, + guint *out_count, + GVariant **out_params, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync(proxy->proxy, + "GetParams", + g_variant_new("(s)", + arg_id), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get(_ret, + "(iu@a{ss})", + out_status, + out_count, + out_params); + g_variant_unref(_ret); +_out: + return _ret != NULL; +} + +/* networkconfig1_call_get_active_interface_sync method is almost directly copied from gdbus-codegen generated version + the only change is in first arg to g_dbus_proxy_call_sync */ +gboolean networkconfig1_call_get_active_interface_sync( + Networkconfig1 *proxy, + gchar **out_id, + gint *out_status, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync(proxy->proxy, + "GetActiveInterface", + g_variant_new("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get(_ret, + "(si)", + out_id, + out_status); + g_variant_unref(_ret); +_out: + return _ret != NULL; +} + +/* networkconfig1_call_is_enabled_sync method is almost directly copied from gdbus-codegen generated version + the only change is in first arg to g_dbus_proxy_call_sync */ +gboolean networkconfig1_call_is_enabled_sync( + Networkconfig1 *proxy, + const gchar *arg_id, + gint *out_status, + gboolean *out_enabled, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync(proxy->proxy, + "IsEnabled", + g_variant_new("(s)", + arg_id), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get(_ret, + "(ib)", + out_status, + out_enabled); + g_variant_unref(_ret); +_out: + return _ret != NULL; +} + +/* networkconfig1_call_enable_sync method is almost directly copied from gdbus-codegen generated version + the only change is in first arg to g_dbus_proxy_call_sync */ +gboolean networkconfig1_call_enable_sync( + Networkconfig1 *proxy, + const gchar *arg_id, + gboolean arg_enable, + gint *out_status, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync(proxy->proxy, + "Enable", + g_variant_new("(sb)", + arg_id, + arg_enable), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get(_ret, + "(i)", + out_status); + g_variant_unref(_ret); +_out: + return _ret != NULL; +} diff --git a/LgiNetwork/dbus/lginetwork_dbus_api.h b/LgiNetwork/dbus/lginetwork_dbus_api.h new file mode 100644 index 0000000000..1f7e1d1ae4 --- /dev/null +++ b/LgiNetwork/dbus/lginetwork_dbus_api.h @@ -0,0 +1,68 @@ +#ifndef __LGINETWORK_DBUS_API_H__ +#define __LGINETWORK_DBUS_API_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + GDBusConnection* connection; + GDBusProxy* proxy; +} Networkconfig1; + +Networkconfig1 *networkconfig1_proxy_new_for_bus_sync ( + GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GError **error); + +gboolean networkconfig1_call_get_interfaces_sync ( + Networkconfig1 *proxy, + guint *out_count, + gchar ***out_ids, + GCancellable *cancellable, + GError **error); + +gboolean networkconfig1_call_get_params_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gint *out_status, + guint *out_count, + GVariant **out_params, + GCancellable *cancellable, + GError **error); + + +gboolean networkconfig1_call_get_active_interface_sync ( + Networkconfig1 *proxy, + gchar **out_id, + gint *out_status, + GCancellable *cancellable, + GError **error); + +gboolean networkconfig1_call_is_enabled_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gint *out_status, + gboolean *out_enabled, + GCancellable *cancellable, + GError **error); + +gboolean networkconfig1_call_enable_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gboolean arg_enable, + gint *out_status, + GCancellable *cancellable, + GError **error); + +#ifdef __cplusplus // extern "C" +} +#endif + +#endif diff --git a/LgiNetwork/doc/NetworkPlugin.md b/LgiNetwork/doc/NetworkPlugin.md new file mode 100644 index 0000000000..bbb86cd81b --- /dev/null +++ b/LgiNetwork/doc/NetworkPlugin.md @@ -0,0 +1,5 @@ +The RDK Services documentation is hosted as a static web site. + +[View Latest Documentation](https://rdkcentral.github.io/rdkservices/#/README) + +This file will be removed in a future release. The API markdown files for RDK services are located in the [docs/api](https://github.com/rdkcentral/rdkservices/tree/main/docs/api) folder. Please update your bookmarks accordingly. diff --git a/LgiSystemServices/CMakeLists.txt b/LgiSystemServices/CMakeLists.txt new file mode 100644 index 0000000000..66dc6e3ffc --- /dev/null +++ b/LgiSystemServices/CMakeLists.txt @@ -0,0 +1,53 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(PLUGIN_NAME LgiSystemServices) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) + +if(DEFINED LGISYSTEMSERVICE_DEVICEINFO_FILENAME) + add_definitions(-DLGISYSTEMSERVICE_DEVICEINFO_FILENAME="${LGISYSTEMSERVICE_DEVICEINFO_FILENAME}") +endif() + +if(DEFINED LGISYSTEMSERVICE_DEVICEINFO_ENVVAR_PREFIX) + add_definitions(-DLGISYSTEMSERVICE_DEVICEINFO_ENVVAR_PREFIX="${LGISYSTEMSERVICE_DEVICEINFO_ENVVAR_PREFIX}") +endif() + +set(PLUGIN_LGISYSTEMSERVICE_AUTOSTART "false" CACHE STRING "Automatically start LgiSystemServices plugin") +set(PLUGIN_LGISYSTEMSERVICE_STARTUPORDER "" CACHE STRING "To configure startup order of LgiSystemServices plugin") + +find_package(${NAMESPACE}Plugins REQUIRED) + +add_library(${MODULE_NAME} SHARED + LgiSystemServices.cpp + SystemServicesHelper.cpp + Module.cpp + ) + +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +target_compile_definitions(${MODULE_NAME} PRIVATE MODULE_NAME=Plugin_${PLUGIN_NAME}) + +target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins ) +target_include_directories(${MODULE_NAME} PRIVATE ../helpers) + +install(TARGETS ${MODULE_NAME} + DESTINATION lib/${STORAGE_DIRECTORY}/plugins) + +write_config(${PLUGIN_NAME}) + diff --git a/LgiSystemServices/LgiSystem.json b/LgiSystemServices/LgiSystem.json new file mode 100644 index 0000000000..b4220edf82 --- /dev/null +++ b/LgiSystemServices/LgiSystem.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://raw.githubusercontent.com/rdkcentral/rdkservices/main/Tools/json_generator/schemas/interface.schema.json", + "jsonrpc": "2.0", + "info": { + "title": "System API", + "class": "SystemServices", + "description": "The `System` plugin is used to manage various system-level features such as power settings and firmware updates." + }, + "common": { + "$ref": "../common/common.json" + }, + "methods": { + "getDeviceInfo":{ + "summary": "Collects device details. Sample keys include: \n* bluetooth_mac \n* boxIP \n* build_type \n* estb_mac \n* imageVersion \n* rf4ce_mac \n* wifi_mac.", + "params": { + "type": "object", + "properties": { + "params": { + "summary": "A list of supported device keys", + "type": "array", + "items": { + "type": "string", + "example": "estb_mac" + } + } + }, + "required": [ + "params" + ] + }, + "result": { + "type": "object", + "properties": { + "estb_mac": { + "summary": "Value for the specified key name", + "type": "string", + "example": "20:F1:9E:EE:62:08" + }, + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "estb_mac", + "success" + ] + } + } + } +} diff --git a/LgiSystemServices/LgiSystemPlugin.json b/LgiSystemServices/LgiSystemPlugin.json new file mode 100644 index 0000000000..c652e87094 --- /dev/null +++ b/LgiSystemServices/LgiSystemPlugin.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/rdkcentral/rdkservices/main/Tools/json_generator/schemas/plugin.schema.json", + "info": { + "title": "System Plugin", + "callsign": "org.rdk.System", + "locator": "libWPEFrameworkLgiSystemServices.so", + "status": "production", + "description": "The `System` plugin is used to manage various system-level features such as power settings and firmware updates." + }, + "interface": { + "$ref": "LgiSystem.json#" + } +} \ No newline at end of file diff --git a/LgiSystemServices/LgiSystemServices.config b/LgiSystemServices/LgiSystemServices.config new file mode 100644 index 0000000000..e01acced92 --- /dev/null +++ b/LgiSystemServices/LgiSystemServices.config @@ -0,0 +1,8 @@ +set (autostart ${PLUGIN_LGISYSTEMSERVICE_AUTOSTART}) +set (preconditions Platform) +set (callsign "org.rdk.System") + +if(PLUGIN_LGISYSTEMSERVICE_STARTUPORDER) +set (startuporder ${PLUGIN_LGISYSTEMSERVICE_STARTUPORDER}) +endif() + diff --git a/LgiSystemServices/LgiSystemServices.cpp b/LgiSystemServices/LgiSystemServices.cpp new file mode 100644 index 0000000000..e8d04a0833 --- /dev/null +++ b/LgiSystemServices/LgiSystemServices.cpp @@ -0,0 +1,244 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ +#include "LgiSystemServices.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "UtilsString.h" +#include "UtilsJsonRpc.h" +#include "UtilsfileExists.h" +#include "SystemServicesHelper.h" + +#define API_VERSION_NUMBER_MAJOR 1 +#define API_VERSION_NUMBER_MINOR 1 +#define API_VERSION_NUMBER_PATCH 5 + +#ifndef LGISYSTEMSERVICE_DEVICEINFO_FILENAME +#define LGISYSTEMSERVICE_DEVICEINFO_FILENAME "/etc/WPEFramework/device.info" +#endif + +#ifndef LGISYSTEMSERVICE_DEVICEINFO_ENVVAR_PREFIX +#define LGISYSTEMSERVICE_DEVICEINFO_ENVVAR_PREFIX "RDK_ENV_" +#endif + +static std::vector getDeviceInfoFromFile() +{ + std::vector lines; + std::ifstream ifs = std::ifstream(LGISYSTEMSERVICE_DEVICEINFO_FILENAME, std::ios_base::in); + if (!ifs.is_open()) + { + LOGWARN("could not read data file: %s", LGISYSTEMSERVICE_DEVICEINFO_FILENAME); + return lines; + } + + std::stringstream ss; + ss << ifs.rdbuf(); + ifs.close(); + + std::string line; + while (std::getline(ss, line, '\n')) + { + lines.push_back(line); + } + + return lines; +} + +static std::vector getDeviceInfoFromEnv(char** environ_ = environ) +{ + std::vector lines; + char** env = environ_; + while (*env) + { + std::string s(*env); + env++; + + size_t n = s.find(LGISYSTEMSERVICE_DEVICEINFO_ENVVAR_PREFIX); + if (n == std::string::npos || n > 0) + continue; + + lines.push_back(s.substr(strlen(LGISYSTEMSERVICE_DEVICEINFO_ENVVAR_PREFIX))); + } + + return lines; +} + +static bool parseLine(const std::string& line, std::map& map) +{ + size_t n = line.find_first_of("="); + + if (n == std::string::npos) + { + LOGERR("no '=' found in: %s", line.c_str()); + return false; + } + + std::string key = line.substr(0, n); + std::string val = line.substr(n + 1); + + Utils::String::trim(key); + Utils::String::trim(val); + + if (val.empty()) + { + LOGWARN("found empty value in: %s", line.c_str()); + } + + if (key.empty()) + { + LOGERR("empty key is forbidden: %s", line.c_str()); + return false; + } + + map[key] = val; + + return true; +} + +static void populateDeviceInfo(std::map& map) +{ + std::vector lines = getDeviceInfoFromFile(); + std::vector envLines = getDeviceInfoFromEnv(); + lines.insert(lines.end(), envLines.begin(), envLines.end()); + + map.clear(); + std::for_each(lines.begin(), lines.end(), [&](const string& l){parseLine(l, map);}); +} + + +/** + * @brief WPEFramework class for LgiSystemServices + */ +namespace WPEFramework { + + namespace { + + static Plugin::Metadata metadata( + // Version (Major, Minor, Patch) + API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH, + // Preconditions + {}, + // Terminations + {}, + // Controls + {} + ); + } + + namespace Plugin { + SERVICE_REGISTRATION(LgiSystemServices, API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH); + + /** + * Register SystemService module as wpeframework plugin + */ + LgiSystemServices::LgiSystemServices() + : PluginHost::JSONRPC() + { + Register("getDeviceInfo", &LgiSystemServices::getDeviceInfo, this); + Register("getTimeZoneDST", &LgiSystemServices::getTimeZoneDST, this); + } + + + LgiSystemServices::~LgiSystemServices() + { + } + + const string LgiSystemServices::Initialize(PluginHost::IShell* service) + { + populateDeviceInfo(m_deviceInfo); + + LOGINFO("Device info includes %zu items:", m_deviceInfo.size()); + for (const auto& p : m_deviceInfo) + LOGINFO("'%s' : '%s'", p.first.c_str(), p.second.c_str()); + + return std::string(); + } + + void LgiSystemServices::Deinitialize(PluginHost::IShell*) + { + } + + + uint32_t LgiSystemServices::getDeviceInfo(const JsonObject& parameters, + JsonObject& response) + { + if (!parameters.HasLabel("params") //there is no list of keys + || parameters["params"].Content() != WPEFramework::Core::JSON::Variant::type::ARRAY //keys list is not a list + || !parameters["params"].Array().IsSet()) //keys list is a list, but is empty + { + LOGINFO("Keys list is empty - sending full map"); + std::for_each(m_deviceInfo.begin(), m_deviceInfo.end(), + [&](const std::pair& p){response[p.first.c_str()] = p.second;}); + returnResponse(true); + } + + const auto& keys = parameters["params"].Array(); + for (int i = 0; i < keys.Length(); i++) + { + if (keys[i].Content() != WPEFramework::Core::JSON::Variant::type::STRING) + { + LOGWARN("key have to be 'string' type: %s", keys[i].String().c_str()); + returnResponse(false); + } + + auto e = m_deviceInfo.find(keys[i].String()); + if (e == m_deviceInfo.end()) + { + LOGWARN("no key found in map: %s", keys[i].String().c_str()); + returnResponse(false); + } + + response[e->first.c_str()] = e->second; + } + + returnResponse(true); + } + + uint32_t LgiSystemServices::getTimeZoneDST(const JsonObject& parameters, + JsonObject& response) + { + std::string timezone; + bool resp = false; + + if (Utils::fileExists(TZ_FILE)) { + if(readFromFile(TZ_FILE, timezone)) { + LOGWARN("Fetch TimeZone: %s\n", timezone.c_str()); + response["timeZone"] = timezone; + response["accuracy"] = "FINAL"; + resp = true; + } else { + LOGERR("Unable to open %s file.\n", TZ_FILE); + response["timeZone"] = "null"; + resp = false; + } + } else { + LOGERR("File not found %s.\n", TZ_FILE); + populateResponseWithError(SysSrv_FileAccessFailed, response); + resp = false; + } + returnResponse(resp); + } + } +} diff --git a/LgiSystemServices/LgiSystemServices.h b/LgiSystemServices/LgiSystemServices.h new file mode 100644 index 0000000000..8d5ebb5b36 --- /dev/null +++ b/LgiSystemServices/LgiSystemServices.h @@ -0,0 +1,73 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#ifndef LGISYSTEMSERVICES_H +#define LGISYSTEMSERVICES_H + +#include + +#include "Module.h" +#include "tracing/Logging.h" + +namespace WPEFramework { + namespace Plugin { + + // This is a server for a JSONRPC communication channel. + // For a plugin to be capable to handle JSONRPC, inherit from PluginHost::JSONRPC. + // By inheriting from this class, the plugin realizes the interface PluginHost::IDispatcher. + // This realization of this interface implements, by default, the following methods on this plugin + // - exists + // - register + // - unregister + // Any other methood to be handled by this plugin can be added can be added by using the + // templated methods Register on the PluginHost::JSONRPC class. + // As the registration/unregistration of notifications is realized by the class PluginHost::JSONRPC, + // this class exposes a public method called, Notify(), using this methods, all subscribed clients + // will receive a JSONRPC message as a notification, in case this method is called. + + class LgiSystemServices : public PluginHost::IPlugin, public PluginHost::JSONRPC { + + public: + LgiSystemServices(); + virtual ~LgiSystemServices(); + + // We do not allow this plugin to be copied !! + LgiSystemServices(const LgiSystemServices&) = delete; + LgiSystemServices& operator=(const LgiSystemServices&) = delete; + + virtual const string Initialize(PluginHost::IShell* service) override; + virtual void Deinitialize(PluginHost::IShell* service) override; + virtual string Information() const override { return {}; } + + BEGIN_INTERFACE_MAP(LgiSystemServices) + INTERFACE_ENTRY(PluginHost::IPlugin) + INTERFACE_ENTRY(PluginHost::IDispatcher) + END_INTERFACE_MAP + + uint32_t getDeviceInfo(const JsonObject& parameters, JsonObject& response); + uint32_t getTimeZoneDST(const JsonObject& parameters, JsonObject& response); + + private: + std::map m_deviceInfo; + + }; /* end of system service class */ + } /* end of plugin */ +} /* end of wpeframework */ + +#endif //LGISYSTEMSERVICES_H diff --git a/LgiSystemServices/Module.cpp b/LgiSystemServices/Module.cpp new file mode 100644 index 0000000000..69ecca0532 --- /dev/null +++ b/LgiSystemServices/Module.cpp @@ -0,0 +1,22 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/LgiSystemServices/Module.h b/LgiSystemServices/Module.h new file mode 100644 index 0000000000..11eba2133a --- /dev/null +++ b/LgiSystemServices/Module.h @@ -0,0 +1,29 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once +#ifndef MODULE_NAME +#define MODULE_NAME Plugin_LgiSystemServices +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/LgiSystemServices/SystemServicesHelper.cpp b/LgiSystemServices/SystemServicesHelper.cpp new file mode 100644 index 0000000000..ac3cdaae88 --- /dev/null +++ b/LgiSystemServices/SystemServicesHelper.cpp @@ -0,0 +1,618 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include +#include +#include +#include +#include +#include +#include + +#include "SystemServicesHelper.h" + +#include "UtilsLogging.h" +#include "UtilsfileExists.h" + +/* Helper Functions */ +using namespace std; + +std::map ErrCodeMap = { + {SysSrv_OK, "Processed Successfully"}, + {SysSrv_MethodNotFound, "Method not found"}, + {SysSrv_MissingKeyValues, "Missing required key/value(s)"}, + {SysSrv_UnSupportedFormat, "Unsupported or malformed format"}, + {SysSrv_FileNotPresent, "Expected file not found"}, + {SysSrv_FileAccessFailed, "File access failed"}, + {SysSrv_FileContentUnsupported, "Unsupported file content"}, + {SysSrv_Unexpected, "Unexpected error"}, + {SysSrv_SupportNotAvailable, "Support not available/enabled"}, + {SysSrv_LibcurlError, "LIbCurl service error"}, + {SysSrv_DynamicMemoryAllocationFailed, "Dynamic Memory Allocation Failed"}, + {SysSrv_ManufacturerDataReadFailed, "Manufacturer Data Read Failed"}, + {SysSrv_KeyNotFound, "Key not found"} +}; + +std::string getErrorDescription(int errCode) +{ + std::string errMsg = "Unexpected Error"; + + auto it = ErrCodeMap.find(errCode); + if (ErrCodeMap.end() != it) { + errMsg = it->second; + } + return errMsg; +} + +std::string dirnameOf(const std::string& fname) +{ + size_t pos = fname.find_last_of("/"); + return (std::string::npos == pos) ? "" : fname.substr(0, pos+1); +} + +/*** + * @brief : Used to check if directory exists + * @param1[in] : Complete file name with path + * @param2[in] : Destination string to be filled with file contents + * @return : ; TRUE if operation success; else FALSE. + */ +bool dirExists(std::string fname) +{ + bool status = false; + struct stat sb; + if (stat((dirnameOf(fname)).c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) { + status = true; + } else { + // Do nothing. + } + return status; +} + +/*** + * @brief : Used to read file contents into a string + * @param1[in] : Complete file name with path + * @param2[in] : Destination string to be filled with file contents + * @return : ; TRUE if operation success; else FALSE. + */ +bool readFromFile(const char* filename, string &content) +{ + bool retStatus = false; + + if (!Utils::fileExists(filename)) { + return retStatus; + } + + ifstream ifile(filename, ios::in); + if (ifile.is_open()) { + std::getline(ifile, content); + ifile.close(); + retStatus = true; + } else { + retStatus = false; + } + + return retStatus; +} + +namespace WPEFramework { + namespace Plugin { + /*** + * @brief : Used to construct response with module error status. + * @param1[in] : Error Code + * @param2[out]: "response" JSON Object which is returned by the API + with updated module error status. + */ + void populateResponseWithError(int errorCode, JsonObject& response) + { + if (errorCode) { + LOGWARN("Method %s failed; reason : %s\n", __FUNCTION__, + getErrorDescription(errorCode).c_str()); + response["SysSrv_Status"] = static_cast(errorCode); + response["errorMessage"] = getErrorDescription(errorCode); + } + } + + string caseInsensitive(string str7) + { + string status = "ERROR"; + regex r("model=([^\r\n]*)\n?", + std::regex_constants::ECMAScript | std::regex_constants::icase); + regex r1("model_number=([^\r\n]*)\n?", + std::regex_constants::ECMAScript | std::regex_constants::icase); + smatch match; + + if (regex_search(str7, match, r) == true) { + status = match.str(1); + } else if (regex_search(str7, match, r1) == true) { + status = match.str(1); + } else { + // Do nothing + ; + } + return status; + } + + string ltrim(const string& s) + { + const string WHITESPACE = " \n\r\t\f\v"; + size_t start = s.find_first_not_of(WHITESPACE); + return (start == string::npos) ? "" : s.substr(start); + } + + string rtrim(const string& s) + { + const string WHITESPACE = " \n\r\t\f\v"; + size_t end = s.find_last_not_of(WHITESPACE); + return (end == string::npos) ? "" : s.substr(0, end + 1); + } + + string trim(const string& s) + { + return rtrim(ltrim(s)); + } + + string getModel() + { + const char * pipeName = "PATH=${PATH}:/sbin:/usr/sbin /lib/rdk/getDeviceDetails.sh read"; + FILE* pipe = popen(pipeName, "r"); + LOGWARN("%s: opened pipe for command '%s', with result %s : %s\n", + __FUNCTION__ , pipeName, pipe ? "sucess" : "failure", strerror(errno)); + if (!pipe) { + LOGERR("%s: SERVICEMANAGER_FILE_ERROR: Can't open pipe for command '%s' for read mode: %s\n" + , __FUNCTION__, pipeName, strerror(errno)); + return "ERROR"; + } + + char buffer[128] = {'\0'}; + string result; + + while (!feof(pipe)) { + if (fgets(buffer, 128, pipe) != NULL) { + result += buffer; + } + } + pclose(pipe); + + string tri = caseInsensitive(result); + string ret = tri.c_str(); + ret = trim(ret); + LOGWARN("%s: ret=%s\n", __FUNCTION__, ret.c_str()); + return ret; + } + + string convertCase(string str) + { + std::string bufferString = str; + transform(bufferString.begin(), bufferString.end(), + bufferString.begin(), ::toupper); + LOGWARN("%s: after transform to upper :%s\n", __FUNCTION__, + bufferString.c_str()); + return bufferString; + } + + bool convert(string str3, string firm) + { + LOGWARN("INSIDE CONVERT\n"); + bool status = false; + string firmware = convertCase(firm); + string str = firmware.c_str(); + size_t found = str.find(str3); + if (found != string::npos) { + status = true; + } else { + status = false; + } + return status; + } + } //namespace Plugin +} //namespace WPEFramework + +/*** + * @brief : Used to construct JSON response from Vector. + * @param1[in] : Destination JSON response buffer + * @param2[in] : JSON "Key" + * @param3[in] : Source Vector. + * @return : ; TRUE if operation success; else FALSE. + */ +void setJSONResponseArray(JsonObject& response, const char* key, + const vector& items) +{ + JsonArray arr; + + for (auto& i : items) { + arr.Add(JsonValue(i)); + } + response[key] = arr; +} + +/*** + * @brief : Used to read file contents into a string + * @param1[in] : Complete file name with path + * @param2[out] : Destination string object filled with file contents + * @return : ; TRUE if operation success; else FALSE. + */ +bool getFileContent(std::string fileName, std::string& fileContent) +{ + std::ifstream inFile(fileName.c_str(), ios::in); + if (!inFile.is_open()) return false; + + std::stringstream buffer; + buffer << inFile.rdbuf(); + fileContent = buffer.str(); + inFile.close(); + + return true; +} + +/*** + * @brief : Used to read file contents into a vector + * @param1[in] : Complete file name with path + * @param2[in] : Destination vector buffer to be filled with file contents + * @return : ; TRUE if operation success; else FALSE. + */ +bool getFileContent(std::string fileName, std::vector & vecOfStrs) +{ + bool retStatus = false; + std::ifstream inFile(fileName.c_str(), ios::in); + + if (!inFile.is_open()) + return retStatus; + + std::string line; + retStatus = true; + while (std::getline(inFile, line)) { + if (line.size() > 0) { + vecOfStrs.push_back(line); + } + } + inFile.close(); + return retStatus; +} + +/*** + * @brief : Used to search for files in the given directory + * @param1[in] : Directory on which the search has to be performed + * @param2[in] : Filter for the search command + * @return : >; Vector of file names. + */ +std::vector searchAndGetFilesList(std::string path, std::string filter) +{ + int retStat = -1; + std::string command,totalStr; + std::vector FileList; + + command = "find "+path+" -iname "+filter+" > /tmp/tempBuffer.dat"; + retStat = system(command.c_str()); + fprintf(stdout, "searchAndGetFilesList : retStat = %d\n", retStat); + getFileContent("/tmp/tempBuffer.dat", FileList); + + return FileList; +} + +/*** + * @brief : compare two C string case insensitively + * @param1[in] : c string 1 + * @param2[in] : c string 2 + * @return : ; 0 is strings are same, some number if strings are different. + */ +int strcicmp(char const *a, char const *b) +{ + int d = -1; + for (;; a++, b++) { + d = tolower((unsigned char)*a) - tolower((unsigned char)*b); + if (d != 0 || !*a) { + return d; + } + } + return -1; +} + +/** + * @brief : To find case insensitive substring in a given string. + * @param1[in] : Haystack buffer + * @param2[in] : Needle to be searched in Haystack + * @return : ; TRUE if match found; else FALSE. + */ +bool findCaseInsensitive(std::string data, std::string toSearch, size_t pos) +{ + /* Convert case to same type (lowercase). */ + std::transform(data.begin(), data.end(), data.begin(), ::tolower); + std::transform(toSearch.begin(), toSearch.end(), toSearch.begin(), ::tolower); + return ((data.find(toSearch, pos) != std::string::npos)? true : false); +} + +/*** + * @brief : To retrieve Xconf version of URL to override + * @param1[out] : bFileExists - Returns true if /opt/swupdate.conf is present + * @return : string + */ +string getXconfOverrideUrl(bool& bFileExists) +{ + string xconfUrl = ""; + vector lines; + bFileExists = false; + + if (!Utils::fileExists(XCONF_OVERRIDE_FILE)) { + return xconfUrl; + } + + bFileExists = true; + + if (getFileContent(XCONF_OVERRIDE_FILE, lines)) { + if (lines.size()) { + for (int i = 0; i < (int)lines.size(); ++i) { + string line = lines.at(i); + if (!line.empty() && (line[0] != '#')) { + xconfUrl = line; + } + } + } + } + return xconfUrl; +} + +/*** + * @brief : To retrieve TimeZone + * @return : string; TimeZone + */ +string getTimeZoneDSTHelper(void) +{ + string timeZone = ""; + vector lines; + + if (!Utils::fileExists(TZ_FILE)) { + return timeZone; + } + + if (getFileContent(TZ_FILE, lines)) { + if (lines.size() > 0) { + timeZone = lines.front(); + } + } + + return timeZone; +} + +/*** + * @brief : To retrieve system time in requested format + * @param1[in] : requested format conversion info + * @return : string; + */ +string currentDateTimeUtc(const char *fmt) +{ + char timeStringBuffer[128] = {0}; + time_t rawTime = time(0); + struct tm *gmt = gmtime(&rawTime); + + if (fmt) { + strftime(timeStringBuffer, sizeof(timeStringBuffer), fmt, gmt); + } else { + strftime(timeStringBuffer, sizeof(timeStringBuffer), + "%a %B %e %I:%M:%S %Z %Y", gmt); + } + + std::string utcDateTime(timeStringBuffer); + return utcDateTime; +} + +/*** + * @brief : To construct url encoded from string passed + * @param1[in] : string; url to be encoded + * @return : string; encoded url + */ +std::string url_encode(std::string urlIn) +{ + std::string retval = ""; + CURL *c_url = NULL; + + if (!urlIn.length()) + return retval; + + c_url = curl_easy_init(); + if (c_url) { + char *encoded = curl_easy_escape(c_url, urlIn.c_str(), urlIn.length()); + retval = encoded; + curl_free(encoded); + curl_easy_cleanup(c_url); + } + return retval; +} + +std::string urlEncodeField(CURL *curl_handle, std::string &data) +{ + std::string encString = ""; + + if (curl_handle) { + char* encoded = curl_easy_escape(curl_handle, data.c_str(), data.length()); + encString = encoded; + curl_free(encoded); + } + return encString; +} + +/** + * @brief : curl write handler + */ +size_t writeCurlResponse(void *ptr, size_t size, size_t nmemb, std::string stream) +{ + size_t realsize = size * nmemb; + std::string temp(static_cast(ptr), realsize); + stream.append(temp); + return realsize; +} + +/*** + * @brief : extract mac address value to each key + * @param1[in] : the entire string from which makc addresses can be extracted + * @param2[in] : Key to each mac address + * @param2[out]: mac address value to each key + */ +void findMacInString(std::string totalStr, std::string macId, std::string& mac) +{ + const std::regex re("^([0-9A-F]{2}[:]){5}([0-9A-F]{2})$"); + std::size_t found = totalStr.find(macId); + mac = totalStr.substr(found + macId.length(), 17); + std::string defMac = "00:00:00:00:00:00"; + if (!std::regex_match(mac, re)) { + mac = defMac; + } +} + +/** + * @brief Used to create/remove XREConnection Retention status file. + * @return true if status file updation is success; else false. + */ +uint32_t enableXREConnectionRetentionHelper(bool enable) +{ + uint32_t result = SysSrv_Unexpected; + + if (enable) { + if (!Utils::fileExists(RECEIVER_STANDBY_PREFS)) { + FILE *fp = NULL; + if ((fp = fopen(RECEIVER_STANDBY_PREFS, "w"))) { + fclose(fp); + result = SysSrv_OK; + } else { + return SysSrv_FileAccessFailed; + } + } else { + result=SysSrv_OK; + + } + } else { + if (Utils::fileExists(RECEIVER_STANDBY_PREFS)) { + remove(RECEIVER_STANDBY_PREFS); + result = SysSrv_OK ; + } else { + result = SysSrv_OK; + + } + } + return result; +} + +std::string stringTodate(char *pBuffer) +{ + std::string str = ""; + struct tm result; + + if (strptime(pBuffer, "%Y-%m-%d %H:%M:%S", &result) == NULL) { + return str; + } else { + char tempBuff[128] = {'\0'}; + + strftime(tempBuff, sizeof(tempBuff), "%a %d %b %Y %H:%M:%S AP UTC", &result); + str = tempBuff; + } + return str; +} + +/** + * @brief Used to used to remove characters from string. + * @param1[in] : The string that has to striped of the given characters + * @param2[in] : char pointer too character arrya that contains all the + * characters to be removed + * @param2[out]: String striped of given characters + */ +void removeCharsFromString(string &str, const char *charsToRemove) +{ + for ( unsigned int i = 0; i < strlen(charsToRemove); ++i ) + { + str.erase( remove(str.begin(), str.end(), charsToRemove[i]), str.end() ); + } +} + +/** + * @brief : Curl API assistance function + */ +size_t curl_write(void *ptr, size_t size, size_t nmemb, void *stream) +{ + struct write_result *result = (struct write_result *)stream; + /* Will we overflow on this write? */ + if (result->pos + size * nmemb >= CURL_BUFFER_SIZE - 1) { + return -1; + } + /* Copy curl's stream buffer into our own buffer */ + memcpy(result->data + result->pos, ptr, size * nmemb); + /* Advance the position */ + result->pos += size * nmemb; + + return size * nmemb; +} + +/* Utility API for parsing the DCM/Device properties file */ +bool parseConfigFile(const char* filename, string findkey, string &value) +{ + vector lines; + bool found=false; + getFileContent(filename,lines); + for (vector::const_iterator i = lines.begin(); + i != lines.end(); ++i){ + string line = *i; + size_t eq = line.find_first_of("="); + if (std::string::npos != eq) { + std::string key = line.substr(0, eq); + if (key == findkey) { + value = line.substr(eq + 1); + found=true; + break; + } + } + } + + if(found){ + return true; + } + else{ + return false; + } +} + +const std::map FwFailReasonToText = + { + {FwFailReasonNone, "None"}, + {FwFailReasonNotFound, "Not found"}, + {FwFailReasonNetworkFailure, "Network failure"}, + {FwFailReasonServerUnreachable, "Server unreachable"}, + {FwFailReasonCorruptDownloadFile, "Corrupt download file"}, + {FwFailReasonFailureInFlashWrite, "Failure in flash write"}, + {FwFailReasonUpgradeFailedAfterFlashWrite, "Upgrade failed after flash write"}, + }; + +const std::map FwFailReasonFromText = + { + {"ESTB Download Failure", FwFailReasonServerUnreachable}, + {"Image Download Failed - Unable to connect", FwFailReasonNetworkFailure}, + {"Image Download Failed - Server not Found", FwFailReasonNotFound}, + {"Image Download Failed - Error response from server", FwFailReasonServerUnreachable}, + {"Image Download Failed - Unknown", FwFailReasonServerUnreachable}, + {"Image download failed from server", FwFailReasonServerUnreachable}, // firmwareDwnld.sh only + {"RCDL Upgrade Failed", FwFailReasonFailureInFlashWrite}, + {"ECM trigger failed", FwFailReasonFailureInFlashWrite}, + {"Failed in flash write", FwFailReasonFailureInFlashWrite}, + {"Flashing failed", FwFailReasonFailureInFlashWrite}, // userInitiatedFWDnld.sh only + {"Versions Match", FwFailReasonNone}, // XConf + {"Cloud FW Version is empty", FwFailReasonNone}, // XConf + {"Cloud FW Version is invalid", FwFailReasonNone}, // XConf + {"Invalid Request", FwFailReasonNone}, // XConf + {"Network Communication Error", FwFailReasonNone}, // XConf + {"Previous Upgrade In Progress", FwFailReasonNone}, // firmwareDwnld.sh only + {"Empty image name from CDL server", FwFailReasonNone}, // firmwareDwnld.sh only + {"Upgrade failed after flash write", FwFailReasonUpgradeFailedAfterFlashWrite}, + }; \ No newline at end of file diff --git a/LgiSystemServices/SystemServicesHelper.h b/LgiSystemServices/SystemServicesHelper.h new file mode 100644 index 0000000000..1ad2aab2eb --- /dev/null +++ b/LgiSystemServices/SystemServicesHelper.h @@ -0,0 +1,320 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#ifndef __SYSTEM_SERVICE_HELPER_H__ +#define __SYSTEM_SERVICE_HELPER_H__ + +#include "Module.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Status-keeper files */ + +#if defined (PLATFORM_BROADCOM) || defined (PLATFORM_BROADCOM_REF) || defined (PLATFORM_REALTEK) || defined (PLATFORM_AMLOGIC) +#define SYSTEM_SERVICE_REBOOT_INFO_FILE "/opt/secure/reboot/reboot.info" +#define SYSTEM_SERVICE_PREVIOUS_REBOOT_INFO_FILE "/opt/secure/reboot/previousreboot.info" +#define SYSTEM_SERVICE_HARD_POWER_INFO_FILE "/opt/secure/reboot/hardpower.info" +#else +#define SYSTEM_SERVICE_REBOOT_INFO_FILE "/opt/persistent/reboot.info" +#define SYSTEM_SERVICE_PREVIOUS_REBOOT_INFO_FILE "/opt/persistent/previousreboot.info" +#define SYSTEM_SERVICE_HARD_POWER_INFO_FILE "/opt/persistent/hardpower.info" +#endif + + + +#define SYSTEM_UP_TIME_FILE "/proc/uptime" +#define MOCA_FILE "/opt/enablemoca" +#define VERSION_FILE_NAME "/version.txt" +#define SYSTEM_SERVICE_SETTINGS_FILE "/opt/system_service_settings.conf" +#define SYSTEM_SERVICE_TEMP_FILE "/tmp/system_service_temp.conf" +#define FWDNLDSTATUS_FILE_NAME "/opt/fwdnldstatus.txt" +#define REBOOT_INFO_LOG_FILE "/opt/logs/rebootInfo.log" +#define STANDBY_REASON_FILE "/opt/standbyReason.txt" +#define TMP_SERIAL_NUMBER_FILE "/tmp/.STB_SER_NO" +#define WAREHOUSE_MODE_FILE "/opt/warehouse_mode_active" +#define PREVIOUS_REBOOT_INFO2_ONE_CALL_FILE "/tmp/previousRebootInfoOneCall" +#define MILESTONES_LOG_FILE "/opt/logs/rdk_milestones.log" +#define RECEIVER_STANDBY_PREFS "/tmp/retainConnection" +#define TZ_REGEX "^[0-9a-zA-Z/-+_]*$" +#define PREVIOUS_KEYPRESS_INFO_FILE "/opt/persistent/previouskeypress.info" +#define XCONF_OVERRIDE_FILE "/opt/swupdate.conf" +#define URL_XCONF "http://xconf.xcal.tv/xconf/swu/stb" +#define TZ_FILE "/mnt/secure_storage/tzdata/timezone" +#define DEVICE_PROPERTIES "/etc/device.properties" +#define OPT_DCM_PROPERTIES "/opt/dcm.properties" +#define ETC_DCM_PROPERTIES "/etc/dcm.properties" +#define TMP_DCM_SETTINGS "/tmp/DCMSettings.conf" + + +#define MODE_TIMER_UPDATE_INTERVAL 1000 +#define CURL_BUFFER_SIZE (64 * 1024) /* 256kB */ + +#define IARM_BUS_PWRMGR_NAME "PWRMgr" /*!< Power manager IARM bus name */ +#define IARM_BUS_PWRMGR_API_SetDeepSleepTimeOut "SetDeepSleepTimeOut" /*!< Sets the timeout for deep sleep*/ + +#define MODE_NORMAL "NORMAL" +#define MODE_EAS "EAS" +#define MODE_WAREHOUSE "WAREHOUSE" + +#define CAT_DWNLDPROGRESSFILE_AND_GET_INFO "cat /opt/curl_progress | tr -s '\r' '\n' | tail -n 1 | sed 's/^ *//g' | sed '/^[^M/G]*$/d' | tr -s ' ' | cut -d ' ' -f3" + +enum eRetval { E_NOK = -1, + E_OK }; + +enum SysSrv_ErrorCode { + SysSrv_OK = 0, + SysSrv_MethodNotFound, + SysSrv_MissingKeyValues, + SysSrv_UnSupportedFormat, + SysSrv_FileNotPresent, + SysSrv_FileAccessFailed, + SysSrv_FileContentUnsupported, + SysSrv_Unexpected, + SysSrv_SupportNotAvailable, + SysSrv_LibcurlError, + SysSrv_DynamicMemoryAllocationFailed, + SysSrv_ManufacturerDataReadFailed, + SysSrv_KeyNotFound +}; + +enum FirmwareUpdateState { + FirmwareUpdateStateUninitialized = 0, + FirmwareUpdateStateRequesting, + FirmwareUpdateStateDownloading, + FirmwareUpdateStateFailed, + FirmwareUpdateStateDownloadComplete, + FirmwareUpdateStateValidationComplete, + FirmwareUpdateStatePreparingReboot, + FirmwareUpdateStateNoUpgradeNeeded +}; + +#define GZ_STATUS "/opt/gzenabled"; + +/* Used as CURL Data buffer */ +struct write_result { + char *data; + int pos; +}; + +/* Forward Declaration */ +uint32_t enableXREConnectionRetentionHelper(bool); + +/* Helper Functions */ +using namespace std; + +/** + * @brief : To map the error code with matching error message. + * @param1[in] : error code of type SysSrv_ErrorCode. + * @return : string; error message. + */ +std::string getErrorDescription(int errCode); + +std::string dirnameOf(const std::string& fname); + +/*** + * @brief : Used to check if directory exists + * @param1[in] : Complete file name with path + * @param2[in] : Destination string to be filled with file contents + * @return : ; TRUE if operation success; else FALSE. + */ +bool dirExists(std::string fname); + +/*** + * @brief : Used to read file contents into a string + * @param1[in] : Complete file name with path + * @param2[in] : Destination string to be filled with file contents + * @return : ; TRUE if operation success; else FALSE. + */ +bool readFromFile(const char* filename, string &content); + +namespace WPEFramework { + namespace Plugin { + /*** + * @brief : Used to construct response with module error status. + * @param1[in] : Error Code + * @param2[out]: "response" JSON Object which is returned by the API + with updated module error status. + */ + void populateResponseWithError(int errorCode, JsonObject& response); + string caseInsensitive(string str7); + string ltrim(const string& s); + string rtrim(const string& s); + string trim(const string& s); + string getModel(); + string convertCase(string str); + bool convert(string str3,string firm); + } //namespace Plugin +} //namespace WPEFramework + +/*** + * @brief : Used to construct JSON response from Vector. + * @param1[in] : Destination JSON response buffer + * @param2[in] : JSON "Key" + * @param3[in] : Source Vector. + * @return : ; TRUE if operation success; else FALSE. + */ +void setJSONResponseArray(JsonObject& response, const char* key, + const vector& items); + +/*** + * @brief : Used to read file contents into a string + * @param1[in] : Complete file name with path + * @param2[out] : Destination string object filled with file contents + * @return : ; TRUE if operation success; else FALSE. + */ +bool getFileContent(std::string fileName, std::string& fileContent); + +/*** + * @brief : Used to read file contents into a vector + * @param1[in] : Complete file name with path + * @param2[in] : Destination vector buffer to be filled with file contents + * @return : ; TRUE if operation success; else FALSE. + */ +bool getFileContent(std::string fileName, std::vector & vecOfStrs); + +/*** + * @brief : Used to search for files in the given directory + * @param1[in] : Directory on which the search has to be performed + * @param2[in] : Filter for the search command + * @return : >; Vector of file names. + */ +std::vector searchAndGetFilesList(std::string path, std::string filter); + +/*** + * @brief : compare two C string case insensitively + * @param1[in] : c string 1 + * @param2[in] : c string 2 + * @return : ; 0 is strings are same, some number if strings are different. + */ +int strcicmp(char const *a, char const *b); + +/** + * @brief : To find case insensitive substring in a given string. + * @param1[in] : Haystack buffer + * @param2[in] : Needle to be searched in Haystack + * @return : ; TRUE if match found; else FALSE. + */ +bool findCaseInsensitive(std::string data, std::string toSearch, size_t pos = 0); + +/*** + * @brief : To retrieve Xconf version of URL to override + * @param1[out] : bFileExists - Returns true if /opt/swupdate.conf is present + * @return : string + */ +string getXconfOverrideUrl(bool& bFileExists); + +/*** + * @brief : To retrieve TimeZone + * @return : string; TimeZone + */ +string getTimeZoneDSTHelper(void); + +/*** + * @brief : To retrieve system time in requested format + * @param1[in] : requested format conversion info + * @return : string; + */ +string currentDateTimeUtc(const char *fmt); + +/*** + * @brief : To construct url encoded from string passed + * @param1[in] : string; url to be encoded + * @return : string; encoded url + */ +std::string url_encode(std::string urlIn); + +/*** + * @brief : To construct url encoded from string passed + * @param1[in] : CURL *; poinetr to curl init handle + * @param2[in] : string; url to be encoded + * @return : string; encoded url + */ +std::string urlEncodeField(CURL *curl_handle, std::string &data); + +/*** + * @brief : To retrieve model details + * @return : string + */ +std::string getModel(void); + +/** + * @brief : CURL write handle call back. + * @reference : + */ +size_t writeCurlResponse(void *ptr, size_t size, size_t nmemb, std::string stream); + +/*** + * @brief : extract mac address value to each key + * @param1[in] : the entire string from which makc addresses can be extracted + * @param2[in] : Key to each mac address + * @param2[out]: mac address value to each key + */ +void findMacInString(std::string totalStr, std::string macId, std::string& mac); + +std::string stringTodate(char *pBuffer); + +/** + * @brief Used to used to remove characters from string. + * @param1[in] : The string that has to striped of the given characters + * @param2[in] : char pointer too character arrya that contains all the + * characters to be removed + * @param2[out]: String striped of given characters + */ +void removeCharsFromString(string &str, const char* charsToRemove); + +/** + * @brief Used to parse and get value of a particular key from config file + * seperated by '=' (eg: abc=xyz, where abc is key and xyz is value). + * @param1[in] : Filename(with absolute path) of the confiuration file + * @param2[in] : Key which we need to find the value + * @param3[in] : An empty buffer where the value will be updated + * + * @param2[out]: Boolean value either true/false + */ + +bool parseConfigFile(const char* filename, string findkey, string &value); + +/** + * @brief Curl write request callback handler. + */ +size_t curl_write(void *ptr, size_t size, size_t nmemb, void *stream); + +enum FwFailReason +{ + FwFailReasonNone = 0, + FwFailReasonNotFound, + FwFailReasonNetworkFailure, + FwFailReasonServerUnreachable, + FwFailReasonCorruptDownloadFile, + FwFailReasonFailureInFlashWrite, + FwFailReasonUpgradeFailedAfterFlashWrite, +}; + +extern const std::map FwFailReasonToText; +extern const std::map FwFailReasonFromText; + +#endif /* __SYSTEM_SERVICE_HELPER_H__ */ + diff --git a/LgiTextToSpeech/CMakeLists.txt b/LgiTextToSpeech/CMakeLists.txt new file mode 100644 index 0000000000..52b64c87a4 --- /dev/null +++ b/LgiTextToSpeech/CMakeLists.txt @@ -0,0 +1,39 @@ +set(PLUGIN_NAME TextToSpeech) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) + +#one time operation in case of change in ITextToSpeech.h or generator change (Thunder upgrade) - regenerate ProxyStubs_TextToSpeech.cpp) +#find_package(ProxyStubGenerator REQUIRED) +#ProxyStubGenerator(NAMESPACE "WPEFramework::Exchange" INPUT "${CMAKE_CURRENT_SOURCE_DIR}/ITextToSpeech.h" OUTDIR "${CMAKE_CURRENT_SOURCE_DIR}") + +set(PLUGIN_TEXTTOSPEECH_AUTOSTART "true" CACHE STRING "Automatically start TestToSpeech plugin") +set(PLUGIN_TEXTTOSPEECH_STARTUPORDER "" CACHE STRING "Automatically start TextToSpeech plugin") +set(PLUGIN_TEXTTOSPEECH_MODE "Local" CACHE STRING "Controls if the plugin should run in its own process, in process or remote") + +find_package(${NAMESPACE}Plugins REQUIRED) +find_package(PkgConfig) +pkg_search_module(GIO REQUIRED "gio-2.0 gio-unix-2.0") + +add_library(${MODULE_NAME} SHARED + Module.cpp + TextToSpeech.cpp + TextToSpeechJsonRpc.cpp + ProxyStubs_TextToSpeech.cpp + TextToSpeechImplementation.cpp + impl/TTSManager.cpp + impl/logger.cpp + ) +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +list(APPEND CMAKE_MODULE_PATH + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") + +target_include_directories(${MODULE_NAME} PRIVATE ${GIO_INCLUDE_DIRS} ../helpers) +target_link_libraries(${MODULE_NAME} PRIVATE + ${NAMESPACE}Plugins::${NAMESPACE}Plugins ttsclient) + +install(TARGETS ${MODULE_NAME} + DESTINATION lib/${STORAGE_DIRECTORY}/plugins) + +write_config( TextToSpeech ) diff --git a/LgiTextToSpeech/ITextToSpeech.h b/LgiTextToSpeech/ITextToSpeech.h new file mode 100644 index 0000000000..cff9ed6f22 --- /dev/null +++ b/LgiTextToSpeech/ITextToSpeech.h @@ -0,0 +1,56 @@ + +#ifndef __ITEXTTOSPEECH_H +#define __ITEXTTOSPEECH_H + +#include "Module.h" +#include + +namespace WPEFramework { +namespace Exchange { + + struct EXTERNAL ITextToSpeech : virtual public Core::IUnknown { + enum { ID = ID_BROWSER + 0x10000 }; + + struct INotification : virtual public Core::IUnknown { + enum { ID = ITextToSpeech::ID + 1}; + + virtual ~INotification() {} + + virtual void StateChanged(const string &data) = 0; + virtual void VoiceChanged(const string &data) = 0; + virtual void WillSpeak(const string &data) = 0; + virtual void SpeechStart(const string &data) = 0; + virtual void SpeechPause(const string &data) = 0; + virtual void SpeechResume(const string &data) = 0; + virtual void SpeechCancelled(const string &data) = 0; + virtual void SpeechInterrupted(const string &data) = 0; + virtual void NetworkError(const string &data) = 0; + virtual void PlaybackError(const string &data) = 0; + virtual void SpeechComplete(const string &data) = 0; + }; + + virtual ~ITextToSpeech() {} + + virtual uint32_t Configure(PluginHost::IShell* service) = 0; + virtual void Register(INotification* sink) = 0; + virtual void Unregister(INotification* sink) = 0; + + virtual uint32_t Enable(const string &input, string &output /* @out */) = 0; + virtual uint32_t ListVoices(const string &input, string &output /* @out */) = 0; + virtual uint32_t SetConfiguration(const string &input, string &output /* @out */) = 0; + virtual uint32_t GetConfiguration(const string &input, string &output /* @out */) = 0; + virtual uint32_t IsEnabled(const string &input, string &output /* @out */) = 0; + virtual uint32_t Speak(const string &input, string &output /* @out */) = 0; + virtual uint32_t Cancel(const string &input, string &output /* @out */) = 0; + virtual uint32_t Pause(const string &input, string &output /* @out */) = 0; + virtual uint32_t Resume(const string &input, string &output /* @out */) = 0; + virtual uint32_t IsSpeaking(const string &input, string &output /* @out */) = 0; + virtual uint32_t GetSpeechState(const string &input, string &output /* @out */) = 0; + + }; + +} // Exchange +} // WPEFramework + +#endif //__ITEXTTOSPEECH_H + diff --git a/LgiTextToSpeech/Module.cpp b/LgiTextToSpeech/Module.cpp new file mode 100644 index 0000000000..ce759b615f --- /dev/null +++ b/LgiTextToSpeech/Module.cpp @@ -0,0 +1,22 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/LgiTextToSpeech/Module.h b/LgiTextToSpeech/Module.h new file mode 100644 index 0000000000..3fa00a9e56 --- /dev/null +++ b/LgiTextToSpeech/Module.h @@ -0,0 +1,29 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once +#ifndef MODULE_NAME +#define MODULE_NAME TextToSpeech +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/LgiTextToSpeech/ProxyStubs_TextToSpeech.cpp b/LgiTextToSpeech/ProxyStubs_TextToSpeech.cpp new file mode 100644 index 0000000000..b0bb6a4445 --- /dev/null +++ b/LgiTextToSpeech/ProxyStubs_TextToSpeech.cpp @@ -0,0 +1,1045 @@ +// +// generated automatically from "ITextToSpeech.h" +// +// implements RPC proxy stubs for: +// - class ITextToSpeech +// - class ITextToSpeech::INotification +// + +#include "Module.h" +#include "ITextToSpeech.h" + +#include + +namespace WPEFramework { + +namespace ProxyStubs { + + using namespace Exchange; + + // ----------------------------------------------------------------- + // STUB + // ----------------------------------------------------------------- + + // + // ITextToSpeech interface stub definitions + // + // Methods: + // (0) virtual uint32_t Configure(PluginHost::IShell*) = 0 + // (1) virtual void Register(ITextToSpeech::INotification*) = 0 + // (2) virtual void Unregister(ITextToSpeech::INotification*) = 0 + // (3) virtual uint32_t Enable(const string&, string&) = 0 + // (4) virtual uint32_t ListVoices(const string&, string&) = 0 + // (5) virtual uint32_t SetConfiguration(const string&, string&) = 0 + // (6) virtual uint32_t GetConfiguration(const string&, string&) = 0 + // (7) virtual uint32_t IsEnabled(const string&, string&) = 0 + // (8) virtual uint32_t Speak(const string&, string&) = 0 + // (9) virtual uint32_t Cancel(const string&, string&) = 0 + // (10) virtual uint32_t Pause(const string&, string&) = 0 + // (11) virtual uint32_t Resume(const string&, string&) = 0 + // (12) virtual uint32_t IsSpeaking(const string&, string&) = 0 + // (13) virtual uint32_t GetSpeechState(const string&, string&) = 0 + // + + ProxyStub::MethodHandler TextToSpeechStubMethods[] = { + // virtual uint32_t Configure(PluginHost::IShell*) = 0 + // + [](Core::ProxyType& channel, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + Core::instance_id param0 = reader.Number(); + PluginHost::IShell* param0_proxy = nullptr; + ProxyStub::UnknownProxy* param0_proxy_inst = nullptr; + if (param0 != 0) { + param0_proxy_inst = RPC::Administrator::Instance().ProxyInstance(channel, param0, false, param0_proxy); + ASSERT((param0_proxy_inst != nullptr) && (param0_proxy != nullptr) && "Failed to get instance of PluginHost::IShell proxy"); + + if ((param0_proxy_inst == nullptr) || (param0_proxy == nullptr)) { + TRACE_L1("Failed to get instance of PluginHost::IShell proxy"); + } + } + + // write return value + RPC::Data::Frame::Writer writer(message->Response().Writer()); + + // call implementation + ITextToSpeech* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech implementation pointer"); + const uint32_t output = implementation->Configure(param0_proxy); + writer.Number(output); + + if (param0_proxy_inst != nullptr) { + RPC::Administrator::Instance().Release(param0_proxy_inst, message->Response()); + } + }, + + // virtual void Register(ITextToSpeech::INotification*) = 0 + // + [](Core::ProxyType& channel, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + Core::instance_id param0 = reader.Number(); + ITextToSpeech::INotification* param0_proxy = nullptr; + ProxyStub::UnknownProxy* param0_proxy_inst = nullptr; + if (param0 != 0) { + param0_proxy_inst = RPC::Administrator::Instance().ProxyInstance(channel, param0, false, param0_proxy); + ASSERT((param0_proxy_inst != nullptr) && (param0_proxy != nullptr) && "Failed to get instance of ITextToSpeech::INotification proxy"); + + if ((param0_proxy_inst == nullptr) || (param0_proxy == nullptr)) { + TRACE_L1("Failed to get instance of ITextToSpeech::INotification proxy"); + } + } + + // call implementation + ITextToSpeech* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech implementation pointer"); + implementation->Register(param0_proxy); + + if (param0_proxy_inst != nullptr) { + RPC::Administrator::Instance().Release(param0_proxy_inst, message->Response()); + } + }, + + // virtual void Unregister(ITextToSpeech::INotification*) = 0 + // + [](Core::ProxyType& channel, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + Core::instance_id param0 = reader.Number(); + ITextToSpeech::INotification* param0_proxy = nullptr; + ProxyStub::UnknownProxy* param0_proxy_inst = nullptr; + if (param0 != 0) { + param0_proxy_inst = RPC::Administrator::Instance().ProxyInstance(channel, param0, false, param0_proxy); + ASSERT((param0_proxy_inst != nullptr) && (param0_proxy != nullptr) && "Failed to get instance of ITextToSpeech::INotification proxy"); + + if ((param0_proxy_inst == nullptr) || (param0_proxy == nullptr)) { + TRACE_L1("Failed to get instance of ITextToSpeech::INotification proxy"); + } + } + + // call implementation + ITextToSpeech* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech implementation pointer"); + implementation->Unregister(param0_proxy); + + if (param0_proxy_inst != nullptr) { + RPC::Administrator::Instance().Release(param0_proxy_inst, message->Response()); + } + }, + + // virtual uint32_t Enable(const string&, string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + string param1{}; // storage + + // call implementation + ITextToSpeech* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech implementation pointer"); + const uint32_t output = implementation->Enable(param0, param1); + + // write return values + RPC::Data::Frame::Writer writer(message->Response().Writer()); + writer.Number(output); + writer.Text(param1); + }, + + // virtual uint32_t ListVoices(const string&, string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + string param1{}; // storage + + // call implementation + ITextToSpeech* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech implementation pointer"); + const uint32_t output = implementation->ListVoices(param0, param1); + + // write return values + RPC::Data::Frame::Writer writer(message->Response().Writer()); + writer.Number(output); + writer.Text(param1); + }, + + // virtual uint32_t SetConfiguration(const string&, string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + string param1{}; // storage + + // call implementation + ITextToSpeech* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech implementation pointer"); + const uint32_t output = implementation->SetConfiguration(param0, param1); + + // write return values + RPC::Data::Frame::Writer writer(message->Response().Writer()); + writer.Number(output); + writer.Text(param1); + }, + + // virtual uint32_t GetConfiguration(const string&, string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + string param1{}; // storage + + // call implementation + ITextToSpeech* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech implementation pointer"); + const uint32_t output = implementation->GetConfiguration(param0, param1); + + // write return values + RPC::Data::Frame::Writer writer(message->Response().Writer()); + writer.Number(output); + writer.Text(param1); + }, + + // virtual uint32_t IsEnabled(const string&, string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + string param1{}; // storage + + // call implementation + ITextToSpeech* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech implementation pointer"); + const uint32_t output = implementation->IsEnabled(param0, param1); + + // write return values + RPC::Data::Frame::Writer writer(message->Response().Writer()); + writer.Number(output); + writer.Text(param1); + }, + + // virtual uint32_t Speak(const string&, string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + string param1{}; // storage + + // call implementation + ITextToSpeech* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech implementation pointer"); + const uint32_t output = implementation->Speak(param0, param1); + + // write return values + RPC::Data::Frame::Writer writer(message->Response().Writer()); + writer.Number(output); + writer.Text(param1); + }, + + // virtual uint32_t Cancel(const string&, string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + string param1{}; // storage + + // call implementation + ITextToSpeech* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech implementation pointer"); + const uint32_t output = implementation->Cancel(param0, param1); + + // write return values + RPC::Data::Frame::Writer writer(message->Response().Writer()); + writer.Number(output); + writer.Text(param1); + }, + + // virtual uint32_t Pause(const string&, string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + string param1{}; // storage + + // call implementation + ITextToSpeech* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech implementation pointer"); + const uint32_t output = implementation->Pause(param0, param1); + + // write return values + RPC::Data::Frame::Writer writer(message->Response().Writer()); + writer.Number(output); + writer.Text(param1); + }, + + // virtual uint32_t Resume(const string&, string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + string param1{}; // storage + + // call implementation + ITextToSpeech* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech implementation pointer"); + const uint32_t output = implementation->Resume(param0, param1); + + // write return values + RPC::Data::Frame::Writer writer(message->Response().Writer()); + writer.Number(output); + writer.Text(param1); + }, + + // virtual uint32_t IsSpeaking(const string&, string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + string param1{}; // storage + + // call implementation + ITextToSpeech* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech implementation pointer"); + const uint32_t output = implementation->IsSpeaking(param0, param1); + + // write return values + RPC::Data::Frame::Writer writer(message->Response().Writer()); + writer.Number(output); + writer.Text(param1); + }, + + // virtual uint32_t GetSpeechState(const string&, string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + string param1{}; // storage + + // call implementation + ITextToSpeech* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech implementation pointer"); + const uint32_t output = implementation->GetSpeechState(param0, param1); + + // write return values + RPC::Data::Frame::Writer writer(message->Response().Writer()); + writer.Number(output); + writer.Text(param1); + }, + + nullptr + }; // TextToSpeechStubMethods[] + + // + // ITextToSpeech::INotification interface stub definitions + // + // Methods: + // (0) virtual void StateChanged(const string&) = 0 + // (1) virtual void VoiceChanged(const string&) = 0 + // (2) virtual void WillSpeak(const string&) = 0 + // (3) virtual void SpeechStart(const string&) = 0 + // (4) virtual void SpeechPause(const string&) = 0 + // (5) virtual void SpeechResume(const string&) = 0 + // (6) virtual void SpeechCancelled(const string&) = 0 + // (7) virtual void SpeechInterrupted(const string&) = 0 + // (8) virtual void NetworkError(const string&) = 0 + // (9) virtual void PlaybackError(const string&) = 0 + // (10) virtual void SpeechComplete(const string&) = 0 + // + + ProxyStub::MethodHandler TextToSpeechNotificationStubMethods[] = { + // virtual void StateChanged(const string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + + // call implementation + ITextToSpeech::INotification* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech::INotification implementation pointer"); + implementation->StateChanged(param0); + }, + + // virtual void VoiceChanged(const string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + + // call implementation + ITextToSpeech::INotification* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech::INotification implementation pointer"); + implementation->VoiceChanged(param0); + }, + + // virtual void WillSpeak(const string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + + // call implementation + ITextToSpeech::INotification* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech::INotification implementation pointer"); + implementation->WillSpeak(param0); + }, + + // virtual void SpeechStart(const string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + + // call implementation + ITextToSpeech::INotification* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech::INotification implementation pointer"); + implementation->SpeechStart(param0); + }, + + // virtual void SpeechPause(const string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + + // call implementation + ITextToSpeech::INotification* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech::INotification implementation pointer"); + implementation->SpeechPause(param0); + }, + + // virtual void SpeechResume(const string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + + // call implementation + ITextToSpeech::INotification* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech::INotification implementation pointer"); + implementation->SpeechResume(param0); + }, + + // virtual void SpeechCancelled(const string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + + // call implementation + ITextToSpeech::INotification* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech::INotification implementation pointer"); + implementation->SpeechCancelled(param0); + }, + + // virtual void SpeechInterrupted(const string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + + // call implementation + ITextToSpeech::INotification* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech::INotification implementation pointer"); + implementation->SpeechInterrupted(param0); + }, + + // virtual void NetworkError(const string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + + // call implementation + ITextToSpeech::INotification* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech::INotification implementation pointer"); + implementation->NetworkError(param0); + }, + + // virtual void PlaybackError(const string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + + // call implementation + ITextToSpeech::INotification* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech::INotification implementation pointer"); + implementation->PlaybackError(param0); + }, + + // virtual void SpeechComplete(const string&) = 0 + // + [](Core::ProxyType& channel VARIABLE_IS_NOT_USED, Core::ProxyType& message) { + RPC::Data::Input& input(message->Parameters()); + + // read parameters + RPC::Data::Frame::Reader reader(input.Reader()); + const string param0 = reader.Text(); + + // call implementation + ITextToSpeech::INotification* implementation = reinterpret_cast(input.Implementation()); + ASSERT((implementation != nullptr) && "Null ITextToSpeech::INotification implementation pointer"); + implementation->SpeechComplete(param0); + }, + + nullptr + }; // TextToSpeechNotificationStubMethods[] + + // ----------------------------------------------------------------- + // PROXY + // ----------------------------------------------------------------- + + // + // ITextToSpeech interface proxy definitions + // + // Methods: + // (0) virtual uint32_t Configure(PluginHost::IShell*) = 0 + // (1) virtual void Register(ITextToSpeech::INotification*) = 0 + // (2) virtual void Unregister(ITextToSpeech::INotification*) = 0 + // (3) virtual uint32_t Enable(const string&, string&) = 0 + // (4) virtual uint32_t ListVoices(const string&, string&) = 0 + // (5) virtual uint32_t SetConfiguration(const string&, string&) = 0 + // (6) virtual uint32_t GetConfiguration(const string&, string&) = 0 + // (7) virtual uint32_t IsEnabled(const string&, string&) = 0 + // (8) virtual uint32_t Speak(const string&, string&) = 0 + // (9) virtual uint32_t Cancel(const string&, string&) = 0 + // (10) virtual uint32_t Pause(const string&, string&) = 0 + // (11) virtual uint32_t Resume(const string&, string&) = 0 + // (12) virtual uint32_t IsSpeaking(const string&, string&) = 0 + // (13) virtual uint32_t GetSpeechState(const string&, string&) = 0 + // + + class TextToSpeechProxy final : public ProxyStub::UnknownProxyType { + public: + TextToSpeechProxy(const Core::ProxyType& channel, Core::instance_id implementation, const bool otherSideInformed) + : BaseClass(channel, implementation, otherSideInformed) + { + } + + uint32_t Configure(PluginHost::IShell* param0) override + { + IPCMessage newMessage(BaseClass::Message(0)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Number(RPC::instance_cast(param0)); + + // invoke the method handler + uint32_t output{}; + if ((output = ProxyStub::UnknownProxyType::Invoke(newMessage)) == Core::ERROR_NONE) { + // read return value + RPC::Data::Frame::Reader reader(newMessage->Response().Reader()); + output = reader.Number(); + + Complete(reader); + } + + return output; + } + + void Register(ITextToSpeech::INotification* param0) override + { + IPCMessage newMessage(BaseClass::Message(1)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Number(RPC::instance_cast(param0)); + + // invoke the method handler + if (ProxyStub::UnknownProxyType::Invoke(newMessage) == Core::ERROR_NONE) { + // read return value + RPC::Data::Frame::Reader reader(newMessage->Response().Reader()); + Complete(reader); + } + } + + void Unregister(ITextToSpeech::INotification* param0) override + { + IPCMessage newMessage(BaseClass::Message(2)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Number(RPC::instance_cast(param0)); + + // invoke the method handler + if (ProxyStub::UnknownProxyType::Invoke(newMessage) == Core::ERROR_NONE) { + // read return value + RPC::Data::Frame::Reader reader(newMessage->Response().Reader()); + Complete(reader); + } + } + + uint32_t Enable(const string& param0, string& /* out */ param1) override + { + IPCMessage newMessage(BaseClass::Message(3)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + uint32_t output{}; + if ((output = ProxyStub::UnknownProxyType::Invoke(newMessage)) == Core::ERROR_NONE) { + // read return values + RPC::Data::Frame::Reader reader(newMessage->Response().Reader()); + output = reader.Number(); + param1 = reader.Text(); + } + + return output; + } + + uint32_t ListVoices(const string& param0, string& /* out */ param1) override + { + IPCMessage newMessage(BaseClass::Message(4)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + uint32_t output{}; + if ((output = ProxyStub::UnknownProxyType::Invoke(newMessage)) == Core::ERROR_NONE) { + // read return values + RPC::Data::Frame::Reader reader(newMessage->Response().Reader()); + output = reader.Number(); + param1 = reader.Text(); + } + + return output; + } + + uint32_t SetConfiguration(const string& param0, string& /* out */ param1) override + { + IPCMessage newMessage(BaseClass::Message(5)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + uint32_t output{}; + if ((output = ProxyStub::UnknownProxyType::Invoke(newMessage)) == Core::ERROR_NONE) { + // read return values + RPC::Data::Frame::Reader reader(newMessage->Response().Reader()); + output = reader.Number(); + param1 = reader.Text(); + } + + return output; + } + + uint32_t GetConfiguration(const string& param0, string& /* out */ param1) override + { + IPCMessage newMessage(BaseClass::Message(6)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + uint32_t output{}; + if ((output = ProxyStub::UnknownProxyType::Invoke(newMessage)) == Core::ERROR_NONE) { + // read return values + RPC::Data::Frame::Reader reader(newMessage->Response().Reader()); + output = reader.Number(); + param1 = reader.Text(); + } + + return output; + } + + uint32_t IsEnabled(const string& param0, string& /* out */ param1) override + { + IPCMessage newMessage(BaseClass::Message(7)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + uint32_t output{}; + if ((output = ProxyStub::UnknownProxyType::Invoke(newMessage)) == Core::ERROR_NONE) { + // read return values + RPC::Data::Frame::Reader reader(newMessage->Response().Reader()); + output = reader.Number(); + param1 = reader.Text(); + } + + return output; + } + + uint32_t Speak(const string& param0, string& /* out */ param1) override + { + IPCMessage newMessage(BaseClass::Message(8)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + uint32_t output{}; + if ((output = ProxyStub::UnknownProxyType::Invoke(newMessage)) == Core::ERROR_NONE) { + // read return values + RPC::Data::Frame::Reader reader(newMessage->Response().Reader()); + output = reader.Number(); + param1 = reader.Text(); + } + + return output; + } + + uint32_t Cancel(const string& param0, string& /* out */ param1) override + { + IPCMessage newMessage(BaseClass::Message(9)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + uint32_t output{}; + if ((output = ProxyStub::UnknownProxyType::Invoke(newMessage)) == Core::ERROR_NONE) { + // read return values + RPC::Data::Frame::Reader reader(newMessage->Response().Reader()); + output = reader.Number(); + param1 = reader.Text(); + } + + return output; + } + + uint32_t Pause(const string& param0, string& /* out */ param1) override + { + IPCMessage newMessage(BaseClass::Message(10)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + uint32_t output{}; + if ((output = ProxyStub::UnknownProxyType::Invoke(newMessage)) == Core::ERROR_NONE) { + // read return values + RPC::Data::Frame::Reader reader(newMessage->Response().Reader()); + output = reader.Number(); + param1 = reader.Text(); + } + + return output; + } + + uint32_t Resume(const string& param0, string& /* out */ param1) override + { + IPCMessage newMessage(BaseClass::Message(11)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + uint32_t output{}; + if ((output = ProxyStub::UnknownProxyType::Invoke(newMessage)) == Core::ERROR_NONE) { + // read return values + RPC::Data::Frame::Reader reader(newMessage->Response().Reader()); + output = reader.Number(); + param1 = reader.Text(); + } + + return output; + } + + uint32_t IsSpeaking(const string& param0, string& /* out */ param1) override + { + IPCMessage newMessage(BaseClass::Message(12)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + uint32_t output{}; + if ((output = ProxyStub::UnknownProxyType::Invoke(newMessage)) == Core::ERROR_NONE) { + // read return values + RPC::Data::Frame::Reader reader(newMessage->Response().Reader()); + output = reader.Number(); + param1 = reader.Text(); + } + + return output; + } + + uint32_t GetSpeechState(const string& param0, string& /* out */ param1) override + { + IPCMessage newMessage(BaseClass::Message(13)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + uint32_t output{}; + if ((output = ProxyStub::UnknownProxyType::Invoke(newMessage)) == Core::ERROR_NONE) { + // read return values + RPC::Data::Frame::Reader reader(newMessage->Response().Reader()); + output = reader.Number(); + param1 = reader.Text(); + } + + return output; + } + }; // class TextToSpeechProxy + + // + // ITextToSpeech::INotification interface proxy definitions + // + // Methods: + // (0) virtual void StateChanged(const string&) = 0 + // (1) virtual void VoiceChanged(const string&) = 0 + // (2) virtual void WillSpeak(const string&) = 0 + // (3) virtual void SpeechStart(const string&) = 0 + // (4) virtual void SpeechPause(const string&) = 0 + // (5) virtual void SpeechResume(const string&) = 0 + // (6) virtual void SpeechCancelled(const string&) = 0 + // (7) virtual void SpeechInterrupted(const string&) = 0 + // (8) virtual void NetworkError(const string&) = 0 + // (9) virtual void PlaybackError(const string&) = 0 + // (10) virtual void SpeechComplete(const string&) = 0 + // + + class TextToSpeechNotificationProxy final : public ProxyStub::UnknownProxyType { + public: + TextToSpeechNotificationProxy(const Core::ProxyType& channel, Core::instance_id implementation, const bool otherSideInformed) + : BaseClass(channel, implementation, otherSideInformed) + { + } + + void StateChanged(const string& param0) override + { + IPCMessage newMessage(BaseClass::Message(0)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + ProxyStub::UnknownProxyType::Invoke(newMessage); + } + + void VoiceChanged(const string& param0) override + { + IPCMessage newMessage(BaseClass::Message(1)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + ProxyStub::UnknownProxyType::Invoke(newMessage); + } + + void WillSpeak(const string& param0) override + { + IPCMessage newMessage(BaseClass::Message(2)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + ProxyStub::UnknownProxyType::Invoke(newMessage); + } + + void SpeechStart(const string& param0) override + { + IPCMessage newMessage(BaseClass::Message(3)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + ProxyStub::UnknownProxyType::Invoke(newMessage); + } + + void SpeechPause(const string& param0) override + { + IPCMessage newMessage(BaseClass::Message(4)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + ProxyStub::UnknownProxyType::Invoke(newMessage); + } + + void SpeechResume(const string& param0) override + { + IPCMessage newMessage(BaseClass::Message(5)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + ProxyStub::UnknownProxyType::Invoke(newMessage); + } + + void SpeechCancelled(const string& param0) override + { + IPCMessage newMessage(BaseClass::Message(6)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + ProxyStub::UnknownProxyType::Invoke(newMessage); + } + + void SpeechInterrupted(const string& param0) override + { + IPCMessage newMessage(BaseClass::Message(7)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + ProxyStub::UnknownProxyType::Invoke(newMessage); + } + + void NetworkError(const string& param0) override + { + IPCMessage newMessage(BaseClass::Message(8)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + ProxyStub::UnknownProxyType::Invoke(newMessage); + } + + void PlaybackError(const string& param0) override + { + IPCMessage newMessage(BaseClass::Message(9)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + ProxyStub::UnknownProxyType::Invoke(newMessage); + } + + void SpeechComplete(const string& param0) override + { + IPCMessage newMessage(BaseClass::Message(10)); + + // write parameters + RPC::Data::Frame::Writer writer(newMessage->Parameters().Writer()); + writer.Text(param0); + + // invoke the method handler + ProxyStub::UnknownProxyType::Invoke(newMessage); + } + }; // class TextToSpeechNotificationProxy + + // ----------------------------------------------------------------- + // REGISTRATION + // ----------------------------------------------------------------- + + namespace { + + typedef ProxyStub::UnknownStubType TextToSpeechStub; + typedef ProxyStub::UnknownStubType TextToSpeechNotificationStub; + + static class Instantiation { + public: + Instantiation() + { + RPC::Administrator::Instance().Announce(); + RPC::Administrator::Instance().Announce(); + } + ~Instantiation() + { + RPC::Administrator::Instance().Recall(); + RPC::Administrator::Instance().Recall(); + } + } ProxyStubRegistration; + + } // namespace + +} // namespace ProxyStubs + +} diff --git a/LgiTextToSpeech/TextToSpeech.config b/LgiTextToSpeech/TextToSpeech.config new file mode 100644 index 0000000000..79fafa103b --- /dev/null +++ b/LgiTextToSpeech/TextToSpeech.config @@ -0,0 +1,34 @@ +set(callsign "org.rdk.TextToSpeech") +set(preconditions Platform) +set(autostart ${PLUGIN_TEXTTOSPEECH_AUTOSTART}) + +if(PLUGIN_TEXTTOSPEECH_STARTUPORDER) +set (startuporder ${PLUGIN_TEXTTOSPEECH_STARTUPORDER}) +endif() + +map() + kv(mode ${PLUGIN_TEXTTOSPEECH_MODE}) +end() +ans(rootobject) + +map() + kv(en-US ${PLUGIN_TEXTTOSPEECH_VOICE_FOR_EN}) + kv(es-MX ${PLUGIN_TEXTTOSPEECH_VOICE_FOR_ES}) + kv(fr-CA ${PLUGIN_TEXTTOSPEECH_VOICE_FOR_FR}) + kv(en-GB ${PLUGIN_TEXTTOSPEECH_VOICE_FOR_GB}) + kv(de-DE ${PLUGIN_TEXTTOSPEECH_VOICE_FOR_DE}) + kv(it-IT ${PLUGIN_TEXTTOSPEECH_VOICE_FOR_IT}) +end() +ans(voices) + +map() + kv(endpoint ${PLUGIN_TEXTTOSPEECH_ENDPOINT}) + kv(secureendpoint ${PLUGIN_TEXTTOSPEECH_SECURE_ENDPOINT}) + kv(language ${PLUGIN_TEXTTOSPEECH_LANGUAGE}) + kv(volume ${PLUGIN_TEXTTOSPEECH_VOLUME}) + kv(rate ${PLUGIN_TEXTTOSPEECH_RATE}) +end() +ans(configuration) + +map_append(${configuration} voices ${voices}) +map_append(${configuration} root ${rootobject}) diff --git a/LgiTextToSpeech/TextToSpeech.cpp b/LgiTextToSpeech/TextToSpeech.cpp new file mode 100644 index 0000000000..3c62b547bd --- /dev/null +++ b/LgiTextToSpeech/TextToSpeech.cpp @@ -0,0 +1,131 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TextToSpeech.h" + +#define API_VERSION_NUMBER_MAJOR 1 +#define API_VERSION_NUMBER_MINOR 0 +#define API_VERSION_NUMBER_PATCH 5 +#define API_VERSION_NUMBER 1 + +namespace WPEFramework { + +namespace { + + static Plugin::Metadata metadata( + // Version (Major, Minor, Patch) + API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH, + // Preconditions + {}, + // Terminations + {}, + // Controls + {} + ); +} + +namespace Plugin { + + /* + *Register TextToSpeech module as wpeframework plugin + **/ + SERVICE_REGISTRATION(TextToSpeech, API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH); + + const string TextToSpeech::Initialize(PluginHost::IShell* service) + { + ASSERT(_service == nullptr); + + _connectionId = 0; + _service = service; + _skipURL = static_cast(_service->WebPrefix().length()); + + _service->Register(&_notification); + + _tts = _service->Root(_connectionId, 5000, _T("TextToSpeechImplementation")); + + std::string message; + if(_tts != nullptr) { + ASSERT(_connectionId != 0); + + _tts->Configure(_service); + _tts->Register(&_notification); + RegisterAll(); + } else { + message = _T("TextToSpeech could not be instantiated."); + _service->Unregister(&_notification); + _service = nullptr; + } + + return message; + } + + void TextToSpeech::Deinitialize(PluginHost::IShell* service) + { + ASSERT(_service == service); + ASSERT(_tts != nullptr); + + if (!_tts) + return; + + _tts->Unregister(&_notification); + _service->Unregister(&_notification); + + if(_tts->Release() != Core::ERROR_DESTRUCTION_SUCCEEDED) { + ASSERT(_connectionId != 0); + TRACE_L1("TextToSpeech Plugin is not properly destructed. %d", _connectionId); + + RPC::IRemoteConnection* connection(_service->RemoteConnection(_connectionId)); + + // The process can disappear in the meantime... + if (connection != nullptr) { + // But if it did not dissapear in the meantime, forcefully terminate it. Shoot to kill :-) + connection->Terminate(); + connection->Release(); + } + } + + // Deinitialize what we initialized.. + _service = nullptr; + _tts = nullptr; + m_AclCalled = false; + } + + TextToSpeech::TextToSpeech() + : PluginHost::JSONRPC() + , _notification(this) + , _apiVersionNumber(API_VERSION_NUMBER) + , m_AclCalled(false) + { + } + + TextToSpeech::~TextToSpeech() + { + } + + void TextToSpeech::Deactivated(RPC::IRemoteConnection* connection) + { + if (connection->Id() == _connectionId) { + ASSERT(_service != nullptr); + TTSLOG_WARNING("TextToSpeech::Deactivated - %p", this); + Core::IWorkerPool::Instance().Submit(PluginHost::IShell::Job::Create(_service, PluginHost::IShell::DEACTIVATED, PluginHost::IShell::FAILURE)); + } + } + +} // namespace Plugin +} // namespace WPEFramework diff --git a/LgiTextToSpeech/TextToSpeech.h b/LgiTextToSpeech/TextToSpeech.h new file mode 100644 index 0000000000..1f10ab304e --- /dev/null +++ b/LgiTextToSpeech/TextToSpeech.h @@ -0,0 +1,186 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file TextToSpeech.h + * @brief Thunder Plugin based Implementation for TTS service API's (RDK-27957). + */ + +/** + @mainpage Text To Speech (TTS) + + TextToSpeech TTS Thunder Service provides APIs for the arbitrators + * (ex: Native application such as Cobalt) to use TTS resource. + */ + +#pragma once + +#include "Module.h" +#include "tracing/Logging.h" + +#include "TextToSpeechImplementation.h" + +namespace WPEFramework { +namespace Plugin { + + class TextToSpeech: public PluginHost::IPlugin, public PluginHost::JSONRPC { + public: + class Notification : public RPC::IRemoteConnection::INotification, + public Exchange::ITextToSpeech::INotification { + private: + Notification() = delete; + Notification(const Notification&) = delete; + Notification& operator=(const Notification&) = delete; + + public: + explicit Notification(TextToSpeech* parent) + : _parent(*parent) { + ASSERT(parent != nullptr); + } + + virtual ~Notification() { + } + + public: + virtual void StateChanged(const string &data) { + _parent.dispatchJsonEvent("onttsstatechanged", data); + } + + virtual void VoiceChanged(const string &data) { + _parent.dispatchJsonEvent("onvoicechanged", data); + } + + virtual void WillSpeak(const string &data) { + _parent.dispatchJsonEvent("onwillspeak", data); + } + + virtual void SpeechStart(const string &data) { + _parent.dispatchJsonEvent("onspeechstart", data); + } + + virtual void SpeechPause(const string &data) { + _parent.dispatchJsonEvent("onspeechpause", data); + } + + virtual void SpeechResume(const string &data) { + _parent.dispatchJsonEvent("onspeechresume", data); + } + + virtual void SpeechCancelled(const string &data) { + _parent.dispatchJsonEvent("onspeechcancelled", data); + } + + virtual void SpeechInterrupted(const string &data) { + _parent.dispatchJsonEvent("onspeechinterrupted", data); + } + + virtual void NetworkError(const string &data) { + _parent.dispatchJsonEvent("onnetworkerror", data); + } + + virtual void PlaybackError(const string &data) { + _parent.dispatchJsonEvent("onplaybackerror", data); + } + + virtual void SpeechComplete(const string &data) { + _parent.dispatchJsonEvent("onspeechcomplete", data); + } + + virtual void Activated(RPC::IRemoteConnection* /* connection */) final + { + TTSLOG_WARNING("TextToSpeech::Notification::Activated - %p", this); + } + + virtual void Deactivated(RPC::IRemoteConnection* connection) final + { + TTSLOG_WARNING("TextToSpeech::Notification::Deactivated - %p", this); + _parent.Deactivated(connection); + } + + BEGIN_INTERFACE_MAP(Notification) + INTERFACE_ENTRY(Exchange::ITextToSpeech::INotification) + INTERFACE_ENTRY(RPC::IRemoteConnection::INotification) + END_INTERFACE_MAP + + private: + TextToSpeech& _parent; + }; + + BEGIN_INTERFACE_MAP(TextToSpeech) + INTERFACE_ENTRY(PluginHost::IPlugin) + INTERFACE_ENTRY(PluginHost::IDispatcher) + INTERFACE_AGGREGATE(Exchange::ITextToSpeech, _tts) + END_INTERFACE_MAP + + public: + TextToSpeech(); + virtual ~TextToSpeech(); + virtual const string Initialize(PluginHost::IShell* service) override; + virtual void Deinitialize(PluginHost::IShell* service) override; + virtual string Information() const override { return {}; } + + private: + // We do not allow this plugin to be copied !! + TextToSpeech(const TextToSpeech&) = delete; + TextToSpeech& operator=(const TextToSpeech&) = delete; + + void RegisterAll(); + bool AddToAccessList(const string &key,const string &value); + bool HasAccess(const string &method,string &app); + + //TTS Global APIS for Resident application + uint32_t Enable(const JsonObject& parameters, JsonObject& response); + uint32_t ListVoices(const JsonObject& parameters, JsonObject& response); + uint32_t SetConfiguration(const JsonObject& parameters, JsonObject& response); + uint32_t GetConfiguration(const JsonObject& parameters, JsonObject& response); + + // Mandotory TTS APIs for client application + uint32_t IsEnabled(const JsonObject& parameters, JsonObject& response); + uint32_t Speak(const JsonObject& parameters, JsonObject& response); + uint32_t Cancel(const JsonObject& parameters, JsonObject& response); + + // These extended APIS can be used by Client application if needed + uint32_t Pause(const JsonObject& parameters, JsonObject& response); + uint32_t Resume(const JsonObject& parameters, JsonObject& response); + uint32_t IsSpeaking(const JsonObject& parameters, JsonObject& response); + uint32_t GetSpeechState(const JsonObject& parameters, JsonObject& response); + uint32_t SetACL(const JsonObject& parameters, JsonObject& response); + + //version number API's + uint32_t getapiversion(const JsonObject& parameters, JsonObject& response); + + void dispatchJsonEvent(const char *event, const string &data); + void Deactivated(RPC::IRemoteConnection* connection); + + private: + uint8_t _skipURL{}; + uint32_t _connectionId{}; + PluginHost::IShell* _service{}; + Exchange::ITextToSpeech* _tts{}; + Core::Sink _notification; + uint32_t _apiVersionNumber; + bool m_AclCalled; + std::map m_AccessList; + std::mutex m_AccessMutex; + + friend class Notification; + }; + +} // namespace Plugin +} // namespace WPEFramework diff --git a/LgiTextToSpeech/TextToSpeech.json b/LgiTextToSpeech/TextToSpeech.json new file mode 100644 index 0000000000..1df6cc9cb8 --- /dev/null +++ b/LgiTextToSpeech/TextToSpeech.json @@ -0,0 +1,738 @@ +{ + "$schema": "https://raw.githubusercontent.com/rdkcentral/rdkservices/main/Tools/json_generator/schemas/interface.schema.json", + "jsonrpc": "2.0", + "info": { + "title": "TextToSpeech API", + "class": "TextToSpeech", + "description": "The `TextToSpeech` plugin provides text-to-speech (TTS) functionality (Voice Guidance & Speech Synthesis) for the client application." + }, + "common": { + "$ref": "../common/common.json" + }, + "definitions": { + "speechid":{ + "summary":"The speech ID", + "type": "number", + "example": "1" + }, + "TTS_Status": { + "type": "number", + "enum": [ + "TTS_OK(0)", + "TTS_FAIL(1)", + "TTS_NOT_ENABLED(2)", + "TTS_INVALID_CONFIGURATION(3)" + ], + "example": "0" + }, + "ttsendpoint": { + "summary": "The TTS engine URL", + "type": "string", + "example": "http://url_for_the_text_to_speech_processing_unit" + }, + "ttsendpointsecured": { + "summary": "The TTS engine secured URL", + "type": "string", + "example": "https://url_for_the_text_to_speech_processing_unit" + }, + "language": { + "summary": "The TTS language", + "type": "string", + "example": "en-US" + }, + "voice": { + "summary": "The TTS Voice", + "type": "string", + "example": "carol" + }, + "volume": { + "summary": "The TTS Volume", + "type": "string", + "example": "100.000000" + }, + "primvolduckpercent" : { + "summary": "The TTS Primary Volumeduckpercentage", + "type": "number", + "example": 25 + }, + "rate":{ + "summary": "The TTS Rate", + "type": "number", + "example": 50 + }, + "apikey": { + "summary": "x api key(secret key) required for GCD Endpoints", + "type": "string", + "example": "XXXXXXXXXXXX" + }, + "text":{ + "summary": "The text input", + "type": "string", + "example": "speech_1" + }, + + "callsign": { + "summary": " Callsign of the application. This is mandatory when setACL is called prior to speak ", + "type": "string", + "example":"WebApp" + }, + "method": { + "summary": "Method of TTS function to be performed", + "type": "string", + "example": "speak" + }, + "apps": { + "summary": "Name of client application", + "type": "string", + "example": "WebApp" + } + }, + "methods": { + "cancel": { + "summary": "Cancels the speech. Triggers the `onspeechinterrupted` event.", + "events": { + "onspeechinterrupted" : "Triggered when ongoing speech is cancelled. Event is not triggered: if TTS is not enabled; if ongoing Speech is completed" + }, + "params": { + "type": "object", + "properties": { + "speechid":{ + "$ref": "#/definitions/speechid" + } + }, + "required": [ + "speechid" + ] + }, + "result": { + "type": "object", + "properties": { + "TTS_Status": { + "$ref": "#/definitions/TTS_Status" + }, + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "TTS_Status", + "success" + ] + } + }, + "enabletts": { + "summary": "(For Resident App) Enables or disables the TTS conversion processing. Triggered `onttsstatechanged` event when state changes and `onspeechinterrupted` event when disabling TTS while speech is in-progress.", + "events": { + "onttsstatechanged" : "state : true Triggered when TTS is enabled; state : false Triggered when TTS is disabled; otherwise No event When TTS enable or disable is in-progress", + "onspeechinterrupted" : "Triggered when disabling TTS while speech is in-progress." + }, + "params": { + "type": "object", + "properties": { + "enabletts":{ + "summary": "Enable or Disable TTS.", + "type": "boolean", + "example": true + } + }, + "required": [ + "enabletts" + ] + }, + "result": { + "type": "object", + "properties": { + "TTS_Status": { + "$ref": "#/definitions/TTS_Status" + }, + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "TTS_Status", + "success" + ] + } + }, + "getapiversion":{ + "summary": "Gets the API Version.", + "result": { + "type": "object", + "properties": { + "version": { + "summary": "Indicates the API Version", + "type": "number", + "example": 1 + }, + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "version", + "success" + ] + } + }, + "getspeechstate":{ + "summary": "Returns the current state of the speech request.", + "params": { + "type": "object", + "properties": { + "speechid":{ + "$ref": "#/definitions/speechid" + } + }, + "required": [ + "speechid" + ] + }, + "result": { + "type": "object", + "properties": { + "speechstate": { + "summary": "The speech state", + "type": "string", + "enum": [ + "SPEECH_PENDING(0)", + "SPEECH_IN_PROGRESS(1)", + "SPEECH_PAUSED(2)", + "SPEECH_NOT_FOUND(3)" + ], + "example":"SPEECH_IN_PROGRESS" + }, + "TTS_Status": { + "$ref": "#/definitions/TTS_Status" + }, + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "speechstate", + "TTS_Status", + "success" + ] + } + }, + "getttsconfiguration": { + "summary": "Gets the current TTS configuration.", + "result": { + "type": "object", + "properties": { + "ttsendpoint": { + "$ref": "#/definitions/ttsendpoint" + }, + "ttsendpointsecured": { + "$ref": "#/definitions/ttsendpointsecured" + }, + "language": { + "$ref": "#/definitions/language" + }, + "voice": { + "$ref": "#/definitions/voice" + }, + "volume": { + "$ref": "#/definitions/volume" + }, + "rate":{ + "$ref": "#/definitions/rate" + }, + "TTS_Status": { + "$ref": "#/definitions/TTS_Status" + }, + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "ttsendpoint", + "ttsendpointsecured", + "language", + "voice", + "volume", + "rate", + "TTS_Status", + "success" + ] + } + }, + "isspeaking": { + "summary": "Checks if speech is in progress.", + "params": { + "type": "object", + "properties": { + "speechid":{ + "$ref": "#/definitions/speechid" + } + }, + "required": [ + "speechid" + ] + }, + "result": { + "type": "object", + "properties": { + "speaking": { + "summary": "`true` if the passed speech is in progress (that is, audio was playing), `false` if speech is completed or speech ID not found", + "type": "boolean", + "example": "true" + }, + "TTS_Status": { + "$ref": "#/definitions/TTS_Status" + }, + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "speaking", + "TTS_Status", + "success" + ] + } + }, + "isttsenabled": { + "summary": "Returns whether the TTS engine is enabled or disabled. By default the TTS engine is disabled.", + "result": { + "type": "object", + "properties": { + "isenabled": { + "summary": "`true` if the TTS engine is enabled, otherwise `false`", + "type": "boolean", + "example": true + }, + "TTS_Status": { + "$ref": "#/definitions/TTS_Status" + }, + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "isenabled", + "TTS_Status", + "success" + ] + } + }, + "listvoices": { + "summary": "Lists the available voices for the specified language. For every language there is a set of pre-defined voices.", + "params": { + "type": "object", + "properties": { + "language": { + "$ref": "#/definitions/language" + } + }, + "required": [ + "language" + ] + }, + "result": { + "type": "object", + "properties": { + "voices": { + "summary": "Array of available voice", + "type": "string", + "example": "carol" + }, + "TTS_Status": { + "$ref": "#/definitions/TTS_Status" + }, + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "voices", + "TTS_Status", + "success" + ] + } + }, + "pause": { + "summary": "Pauses the speech. Triggers the `onspeechpause` event.", + "events": { + "onspeechpause" : "Triggered when ongoing speech is paused. Event not triggered on following conditions: TTS is not enabled; Speech is already in pause; or Speech is completed" + }, + "params": { + "type": "object", + "properties": { + "speechid":{ + "$ref": "#/definitions/speechid" + } + }, + "required": [ + "speechid" + ] + }, + "result": { + "type": "object", + "properties": { + "TTS_Status": { + "$ref": "#/definitions/TTS_Status" + }, + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "TTS_Status", + "success" + ] + } + }, + "resume": { + "summary": "Resumes the speech. Triggers the `onspeechresume` event.", + "events": { + "onspeechresume" : "Triggered when speech is resumed and speech output is available. Event not triggered under following conditions: TTS is not enabled; Speech is resumed already; or Speech is completed" + }, + "params": { + "type": "object", + "properties": { + "speechid":{ + "$ref": "#/definitions/speechid" + } + }, + "required": [ + "speechid" + ] + }, + "result": { + "type": "object", + "properties": { + "TTS_Status": { + "$ref": "#/definitions/TTS_Status" + }, + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "TTS_Status", + "success" + ] + } + }, + "setttsconfiguration": { + "summary": "Sets the TTS configuration. Triggers the `onvoicechanged` event.", + "events":{ + "onvoicechanged" : "Triggered only when the voice configuration is changed" + }, + "params": { + "type": "object", + "properties": { + "ttsendpoint": { + "$ref": "#/definitions/ttsendpoint" + }, + "ttsendpointsecured": { + "$ref": "#/definitions/ttsendpointsecured" + }, + "language": { + "$ref": "#/definitions/language" + }, + "voice": { + "$ref": "#/definitions/voice" + }, + "volume": { + "$ref": "#/definitions/volume" + }, + "primvolduckpercent": { + "$ref": "#/definitions/primvolduckpercent" + }, + "rate":{ + "$ref": "#/definitions/rate" + }, + "fallbacktext":{ + "type": "object", + "properties": { + "scenario": { + "summary": "Describes the scenario where fallback text is to be used . At present, only `offline` is supported.", + "type": "string", + "example": "offline" + }, + "value": { + "summary": "The Text which is to be spoken when the scenario is met", + "type": "string", + "example": "No Internet connection" + } + }, + "required": [] + }, + "authinfo":{ + "type": "object", + "properties": { + "type": { + "summary": "The type of authentication. At present, only `apikey` is supported.", + "type": "string", + "example": "apikey" + }, + "value": { + "$ref": "#/definitions/apikey" + } + }, + "required": [] + } + }, + "required": [] + }, + "result": { + "type": "object", + "properties": { + "TTS_Status": { + "$ref": "#/definitions/TTS_Status" + }, + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "TTS_Status", + "success" + ] + } + }, + "speak": { + "summary": "Converts the input text to speech when TTS is enabled. Any ongoing speech is interrupted and the newly requested speech is processed. The clients of the previous speech is sent an `onspeechinterrupted` event. Upon success, this API returns an ID, which is used as input to other API methods for controlling the speech (for example, `pause`, `resume`, and `cancel`)", + "events": { + "onwillspeak" : "Triggered when speech conversion is about to start", + "onspeechstart" : "Triggered when conversion of text to speech is started", + "onspeechinterrupted" : "Current speech is interrupted either by a next speech request; by calling the cancel method; or by disabling TTS, when speech is in-progress", + "onspeechcomplete" : "Triggered when conversion from text to speech is completed", + "onnetworkerror" : "Triggered when failed to fetch audio from the endpoint", + "onplaybackerror" : "Triggered when an error occurs during playback including pipeline failures; Triggered when speak is called during TTS disabled" + }, + "params": { + "type": "object", + "properties": { + "text":{ + "$ref": "#/definitions/text" + }, + "callsign":{ + "$ref": "#/definitions/callsign" + } + }, + "required": [ + "text" + ] + }, + "result": { + "type": "object", + "properties": { + "speechid":{ + "$ref": "#/definitions/speechid" + }, + "TTS_Status": { + "$ref": "#/definitions/TTS_Status" + }, + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "speechid", + "TTS_Status", + "success" + ] + } + }, + "setACL": { + "summary":"Configures app to speak. Allows the ResidentAPP to configure the particular app and provides access to `speak` method. If not configured any then gives access to all apps to speak. Configuration does not retained after reboot.", + "params":{ + "type": "object", + "properties": { + "accesslist": { + "type": "array", + "items": { + "type": "object", + "properties": { + "method": { + "$ref": "#/definitions/method" + }, + "apps": { + "$ref": "#/definitions/apps" + } + }, + "required": [ + "method", + "apps" + ] + } + } + }, + "required":[ + "accesslist" + ] + }, + "result": { + "type": "object", + "properties": { + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "success" + ] + } + } + }, + "events": { + "onnetworkerror": { + "summary": "Triggered when a network error occurs while fetching the audio from the endpoint", + "params": { + "type" :"object", + "properties": { + "speechid": { + "$ref": "#/definitions/speechid" + } + }, + "required": [ + "speechid" + ] + } + }, + "onplaybackerror": { + "summary": "Triggered when an error occurs during playback including pipeline failures", + "params": { + "type" :"object", + "properties": { + "speechid": { + "$ref": "#/definitions/speechid" + } + }, + "required": [ + "speechid" + ] + } + }, + "onspeechcomplete": { + "summary": "Triggered when the speech completes", + "params": { + "type" :"object", + "properties": { + "speechid": { + "$ref": "#/definitions/speechid" + }, + "text": { + "$ref": "#/definitions/text" + } + }, + "required": [ + "speechid", + "text" + ] + } + }, + "onspeechinterrupted": { + "summary": "Triggered when the current speech is interrupted either by a next speech request, by calling `cancel` or by disabling TTS, when speech is in progress", + "params": { + "type" :"object", + "properties": { + "speechid": { + "$ref": "#/definitions/speechid" + } + }, + "required": [ + "speechid" + ] + } + }, + "onspeechpause": { + "summary": "Triggered when the ongoing speech pauses", + "params": { + "type" :"object", + "properties": { + "speechid": { + "$ref": "#/definitions/speechid" + } + }, + "required": [ + "speechid" + ] + } + }, + "onspeechresume": { + "summary": "Triggered when any paused speech resumes", + "params": { + "type" :"object", + "properties": { + "speechid": { + "$ref": "#/definitions/speechid" + } + }, + "required": [ + "speechid" + ] + } + }, + "onspeechstart": { + "summary": "Triggered when the speech start", + "params": { + "type" :"object", + "properties": { + "speechid": { + "$ref": "#/definitions/speechid" + }, + "text": { + "$ref": "#/definitions/text" + } + }, + "required": [ + "speechid", + "text" + ] + } + }, + "onttsstatechanged": { + "summary": "Triggered when TTS is enabled or disabled", + "params": { + "type" :"object", + "properties": { + "state": { + "summary": "`True` if TTS is enabled, otherwise `False`", + "type": "boolean", + "example": true + } + }, + "required": [ + "state" + ] + } + }, + "onvoicechanged": { + "summary": "Triggered when the configured voice changes", + "params": { + "type" :"object", + "properties": { + "voice": { + "$ref": "#/definitions/voice" + } + }, + "required": [ + "voice" + ] + } + }, + "onwillspeak": { + "summary": "Triggered when the text to speech conversion is about to start. It provides the speech ID, generated for the text input given in the speak method", + "params": { + "type" :"object", + "properties": { + "speechid": { + "$ref": "#/definitions/speechid" + }, + "text": { + "$ref": "#/definitions/text" + } + }, + "required": [ + "speechid", + "text" + ] + } + } + } +} diff --git a/LgiTextToSpeech/TextToSpeechImplementation.cpp b/LgiTextToSpeech/TextToSpeechImplementation.cpp new file mode 100644 index 0000000000..a5d9a7be6f --- /dev/null +++ b/LgiTextToSpeech/TextToSpeechImplementation.cpp @@ -0,0 +1,589 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TextToSpeechImplementation.h" +#include +#include "UtilsJsonRpc.h" +#include + +#define TTS_MAJOR_VERSION 1 +#define TTS_MINOR_VERSION 0 + +#define GET_STR(map, key, def) ((map.HasLabel(key) && !map[key].String().empty() && map[key].String() != "null") ? map[key].String() : def) +#define CONVERT_PARAMETERS_TOJSON() JsonObject parameters, response; parameters.FromString(input); +#define CONVERT_PARAMETERS_FROMJSON() response.ToString(output); + +#undef returnResponse +#define returnResponse(success) \ + response["success"] = success; \ + CONVERT_PARAMETERS_FROMJSON(); \ + LOGTRACEMETHODFIN(); \ + return (Core::ERROR_NONE); + +namespace WPEFramework { +namespace Plugin { + + SERVICE_REGISTRATION(TextToSpeechImplementation, TTS_MAJOR_VERSION, TTS_MINOR_VERSION); + + TTS::TTSManager* TextToSpeechImplementation::_ttsManager = NULL; + + TextToSpeechImplementation::TextToSpeechImplementation() : _adminLock() + { + // we can't create _ttsManager yet as it will immediately call Dispatch() on this object + // before it gets the change to finish construction; it will be created in ::Configure + } + + TextToSpeechImplementation::~TextToSpeechImplementation() + { + if(_ttsManager) { + delete _ttsManager; + _ttsManager = NULL; + } + } + + uint32_t TextToSpeechImplementation::Configure(PluginHost::IShell* service) + { + if(!_ttsManager) + _ttsManager = TTS::TTSManager::create(this); + + JsonObject config; + config.FromString(service->ConfigLine()); + + TTS::TTSConfiguration *ttsConfig = _ttsManager->configuration(); + ttsConfig->setEndPoint(GET_STR(config, "endpoint", "")); + ttsConfig->setSecureEndPoint(GET_STR(config, "secureendpoint", "")); + ttsConfig->setLanguage(GET_STR(config, "language", "en-US")); + ttsConfig->setVoice(GET_STR(config, "voice", "")); + ttsConfig->setVolume(std::stod(GET_STR(config, "volume", "100"))); + ttsConfig->setRate(std::stoi(GET_STR(config, "rate", "50"))); + ttsConfig->setPrimVolDuck(std::stoi(GET_STR(config,"primvolduckpercent", "25"))); + + if(config.HasLabel("voices")) { + JsonObject voices = config["voices"].Object(); + JsonObject::Iterator it = voices.Variants(); + while(it.Next()) + ttsConfig->m_others["voice_for_" + string(it.Label())] = it.Current().String(); + + if(!config.HasLabel("voice")) + ttsConfig->setVoice(ttsConfig->voice()); + } else { + TTSLOG_WARNING("Doesn't find default voice configuration"); + } + ttsConfig->loadFromConfigStore(); + TTSLOG_INFO("TTSEndPoint : %s", ttsConfig->endPoint().c_str()); + TTSLOG_INFO("SecureTTSEndPoint : %s", ttsConfig->secureEndPoint().c_str()); + TTSLOG_INFO("Language : %s", ttsConfig->language().c_str()); + TTSLOG_INFO("Voice : %s", ttsConfig->voice().c_str()); + TTSLOG_INFO("Volume : %lf", ttsConfig->volume()); + TTSLOG_INFO("Rate : %u", ttsConfig->rate()); + TTSLOG_INFO("PrimaryVolumeDuck percentage : %d", ttsConfig->primVolDuck()); + TTSLOG_INFO("TTS is %s", ttsConfig->enabled()? "Enabled" : "Disabled"); + + auto it = ttsConfig->m_others.begin(); + while( it != ttsConfig->m_others.end()) { + TTSLOG_INFO("%s : %s", it->first.c_str(), it->second.c_str()); + ++it; + } + + _ttsManager->enableTTS(ttsConfig->enabled()); + return 0; + } + + void TextToSpeechImplementation::Register(Exchange::ITextToSpeech::INotification* sink) + { + _adminLock.Lock(); + + // Make sure a sink is not registered multiple times. + ASSERT(std::find(_notificationClients.begin(), _notificationClients.end(), sink) == _notificationClients.end()); + + _notificationClients.push_back(sink); + sink->AddRef(); + + _adminLock.Unlock(); + + TRACE_L1("Registered a sink on the browser %p", sink); + } + + void TextToSpeechImplementation::Unregister(Exchange::ITextToSpeech::INotification* sink) + { + _adminLock.Lock(); + + std::list::iterator index(std::find(_notificationClients.begin(), _notificationClients.end(), sink)); + + // Make sure you do not unregister something you did not register !!! + ASSERT(index != _notificationClients.end()); + + if (index != _notificationClients.end()) { + (*index)->Release(); + _notificationClients.erase(index); + TRACE_L1("Unregistered a sink on the browser %p", sink); + } + + _adminLock.Unlock(); + } + + uint32_t TextToSpeechImplementation::Enable(const string &input, string &output) + { + CONVERT_PARAMETERS_TOJSON(); + CHECK_TTS_MANAGER_RETURN_ON_FAIL(); + CHECK_TTS_PARAMETER_RETURN_ON_FAIL("enabletts"); + + _adminLock.Lock(); + + auto status = _ttsManager->enableTTS(parameters["enabletts"].Boolean()); + + _adminLock.Unlock(); + + logResponse(status, response); + returnResponse(status == TTS::TTS_OK); + } + + uint32_t TextToSpeechImplementation::ListVoices(const string &input, string &output) + { + CONVERT_PARAMETERS_TOJSON(); + CHECK_TTS_MANAGER_RETURN_ON_FAIL(); + CHECK_TTS_PARAMETER_RETURN_ON_FAIL("language"); + + _adminLock.Lock(); + + std::vector voice; + auto status = _ttsManager->listVoices(parameters["language"].String(), voice); + + _adminLock.Unlock(); + + if(status == TTS::TTS_OK) + setResponseArray(response, "voices", voice); + + logResponse(status,response); + returnResponse(status == TTS::TTS_OK); + } + + uint32_t TextToSpeechImplementation::SetConfiguration(const string &input, string &output) + { + CONVERT_PARAMETERS_TOJSON(); + CHECK_TTS_MANAGER_RETURN_ON_FAIL(); + + TTS::Configuration config; + config.ttsEndPoint = GET_STR(parameters, "ttsendpoint", ""); + config.ttsEndPointSecured = GET_STR(parameters, "ttsendpointsecured", ""); + config.language = GET_STR(parameters, "language", ""); + config.voice = GET_STR(parameters, "voice", ""); + config.volume = std::stod(GET_STR(parameters, "volume", "0.0")); + config.primVolDuck = std::stoi(GET_STR(parameters,"primvolduckpercent","-1")); + + if(parameters.HasLabel("rate")) { + int rate=0; + getNumberParameter("rate", rate); + config.rate = static_cast(rate); + } + + if(parameters.HasLabel("authinfo")) { + JsonObject auth; + auth = parameters["authinfo"].Object(); + if(((auth["type"].String()).compare("apikey")) == 0) + { + config.apiKey = GET_STR(auth,"value", ""); + } + } + + if(parameters.HasLabel("fallbacktext")) { + JsonObject fallback; + fallback = parameters["fallbacktext"].Object(); + config.data.scenario = fallback["scenario"].String(); + config.data.value = fallback["value"].String(); + config.data.path = GET_STR(fallback,"path", ""); + } + + _adminLock.Lock(); + + auto status = _ttsManager->setConfiguration(config); + + _adminLock.Unlock(); + + logResponse(status, response); + returnResponse(status == TTS::TTS_OK); + } + + uint32_t TextToSpeechImplementation::GetConfiguration(const string &input, string &output) + { + CONVERT_PARAMETERS_TOJSON(); + CHECK_TTS_MANAGER_RETURN_ON_FAIL(); + + _adminLock.Lock(); + + TTS::Configuration ttsConfig; + auto status = _ttsManager->getConfiguration(ttsConfig); + + _adminLock.Unlock(); + + if(status == TTS::TTS_OK) { + response["ttsendpoint"] = ttsConfig.ttsEndPoint; + response["ttsendpointsecured"] = ttsConfig.ttsEndPointSecured; + response["language"] = ttsConfig.language; + response["voice"] = ttsConfig.voice; + response["rate"] = (int) ttsConfig.rate; + response["volume"] = std::to_string(ttsConfig.volume); + } + + logResponse(status, response); + returnResponse(status == TTS::TTS_OK); + } + + uint32_t TextToSpeechImplementation::IsEnabled(const string &input, string &output) + { + CONVERT_PARAMETERS_TOJSON(); + CHECK_TTS_MANAGER_RETURN_ON_FAIL(); + + _adminLock.Lock(); + + response["isenabled"] = JsonValue((bool)_ttsManager->isTTSEnabled()); + + _adminLock.Unlock(); + + logResponse(TTS::TTS_OK,response); + returnResponse(true); + } + + uint32_t nextSpeechId() { + static uint32_t counter = 0; + + if(counter >= 0xFFFFFFFF) + counter = 0; + + return ++counter; + } + + uint32_t TextToSpeechImplementation::Speak(const string &input, string &output) + { + CONVERT_PARAMETERS_TOJSON(); + CHECK_TTS_MANAGER_RETURN_ON_FAIL(); + CHECK_TTS_PARAMETER_RETURN_ON_FAIL("text"); + + _adminLock.Lock(); + + int speechId = 0; + auto status = _ttsManager->speak(speechId, parameters["text"].String()); + + _adminLock.Unlock(); + + if(status == TTS::TTS_OK) + response["speechid"] = (int) speechId; + else + response["speechid"] = (int) -1; + + logResponse(status, response); + returnResponse(status == TTS::TTS_OK); + } + + uint32_t TextToSpeechImplementation::Cancel(const string &input, string &output) + { + CONVERT_PARAMETERS_TOJSON(); + CHECK_TTS_MANAGER_RETURN_ON_FAIL(); + CHECK_TTS_PARAMETER_RETURN_ON_FAIL("speechid"); + + _adminLock.Lock(); + + auto status = _ttsManager->shut(parameters["speechid"].Number()); + + _adminLock.Unlock(); + + logResponse(status, response); + returnResponse(status == TTS::TTS_OK); + } + + + uint32_t TextToSpeechImplementation::Pause(const string &input, string &output) + { + CONVERT_PARAMETERS_TOJSON(); + CHECK_TTS_MANAGER_RETURN_ON_FAIL(); + CHECK_TTS_PARAMETER_RETURN_ON_FAIL("speechid"); + + _adminLock.Lock(); + + auto status = _ttsManager->pause(parameters["speechid"].Number()); + + _adminLock.Unlock(); + + logResponse(status, response); + returnResponse(status == TTS::TTS_OK); + } + + uint32_t TextToSpeechImplementation::Resume(const string &input, string &output) + { + CONVERT_PARAMETERS_TOJSON(); + CHECK_TTS_MANAGER_RETURN_ON_FAIL(); + CHECK_TTS_PARAMETER_RETURN_ON_FAIL("speechid"); + + _adminLock.Lock(); + + auto status = _ttsManager->resume(parameters["speechid"].Number()); + + _adminLock.Unlock(); + + logResponse(status, response); + returnResponse(status == TTS::TTS_OK); + } + + uint32_t TextToSpeechImplementation::IsSpeaking(const string &input, string &output) + { + CONVERT_PARAMETERS_TOJSON(); + CHECK_TTS_MANAGER_RETURN_ON_FAIL(); + CHECK_TTS_PARAMETER_RETURN_ON_FAIL("speechid"); + + _adminLock.Lock(); + + bool speaking = false; + auto status = _ttsManager->isSpeaking(parameters["speechid"].Number(), speaking); + + _adminLock.Unlock(); + + response["speaking"] = speaking; + logResponse(status, response); + returnResponse(status == TTS::TTS_OK); + } + + uint32_t TextToSpeechImplementation::GetSpeechState(const string &input, string &output) + { + CONVERT_PARAMETERS_TOJSON(); + CHECK_TTS_MANAGER_RETURN_ON_FAIL(); + CHECK_TTS_PARAMETER_RETURN_ON_FAIL("speechid"); + + _adminLock.Lock(); + + TTS::SpeechState state; + auto status = _ttsManager->getSpeechState(parameters["speechid"].Number(), state); + + _adminLock.Unlock(); + + if(status == TTS::TTS_OK) + response["speechstate"] = (int) state; + + logResponse(status, response); + returnResponse(status == TTS::TTS_OK); + } + + void TextToSpeechImplementation::setResponseArray(JsonObject& response, const char* key, const std::vector& items) + { + JsonArray arr; + for(auto& i : items) arr.Add(JsonValue(i)); + + response[key] = arr; + } + + void TextToSpeechImplementation::dispatchEvent(Event event, JsonObject ¶ms) + { + string data; + params.ToString(data); + Core::IWorkerPool::Instance().Submit(Job::Create(this, event, data)); + } + + void TextToSpeechImplementation::Dispatch(Event event, string data) + { + _adminLock.Lock(); + + std::list::iterator index(_notificationClients.begin()); + + while (index != _notificationClients.end()) { + switch(event) { + case STATE_CHANGED: (*index)->StateChanged(data); break; + case VOICE_CHANGED: (*index)->VoiceChanged(data); break; + case WILL_SPEAK: (*index)->WillSpeak(data); break; + case SPEECH_START: (*index)->SpeechStart(data); break; + case SPEECH_PAUSE: (*index)->SpeechPause(data); break; + case SPEECH_RESUME: (*index)->SpeechResume(data); break; + case SPEECH_CANCEL: (*index)->SpeechCancelled(data); break; + case SPEECH_INTERRUPT: (*index)->SpeechInterrupted(data); break; + case NETWORK_ERROR: (*index)->NetworkError(data); break; + case PLAYBACK_ERROR: (*index)->PlaybackError(data); break; + case SPEECH_COMPLETE: (*index)->SpeechComplete(data); break; + } + ++index; + } + + _adminLock.Unlock(); + + } + + void TextToSpeechImplementation::onTTSStateChanged(bool state) + { + JsonObject params; + params["state"] = JsonValue((bool)state); + dispatchEvent(STATE_CHANGED, params); + } + + void TextToSpeechImplementation::onVoiceChanged(std::string voice) + { + JsonObject params; + params["voice"] = voice; + dispatchEvent(VOICE_CHANGED, params); + } + + void TextToSpeechImplementation::onWillSpeak(TTS::SpeechData &data) + { + JsonObject params; + params["speechid"] = JsonValue((int)data.id); + params["text"] = data.text; + dispatchEvent(WILL_SPEAK, params); + } + + void TextToSpeechImplementation::onSpeechStart(TTS::SpeechData &data) + { + JsonObject params; + params["speechid"] = JsonValue((int)data.id); + params["text"] = data.text; + dispatchEvent(SPEECH_START, params); + } + + void TextToSpeechImplementation::onSpeechPause(uint32_t speechId) + { + JsonObject params; + params["speechid"] = JsonValue((int)speechId); + dispatchEvent(SPEECH_PAUSE, params); + } + + void TextToSpeechImplementation::onSpeechResume(uint32_t speechId) + { + JsonObject params; + params["speechid"] = JsonValue((int)speechId); + dispatchEvent(SPEECH_RESUME, params); + } + + void TextToSpeechImplementation::onSpeechCancelled(std::vector speechIds) + { + std::stringstream ss; + for(auto it = speechIds.begin(); it != speechIds.end(); ++it) + { + if(it != speechIds.begin()) + ss << ","; + ss << *it; + } + JsonObject params; + params["speechid"] = ss.str(); + dispatchEvent(SPEECH_CANCEL, params); + } + + void TextToSpeechImplementation::onSpeechInterrupted(uint32_t speechId) + { + JsonObject params; + params["speechid"] = JsonValue((int)speechId); + dispatchEvent(SPEECH_INTERRUPT, params); + } + + void TextToSpeechImplementation::onNetworkError(uint32_t speechId) + { + JsonObject params; + params["speechid"] = JsonValue((int)speechId); + dispatchEvent(NETWORK_ERROR, params); + } + + void TextToSpeechImplementation::onPlaybackError(uint32_t speechId) + { + JsonObject params; + params["speechid"] = JsonValue((int)speechId); + dispatchEvent(PLAYBACK_ERROR, params); + } + + void TextToSpeechImplementation::onSpeechComplete(TTS::SpeechData &data) + { + JsonObject params; + params["speechid"] = JsonValue((int)data.id); + params["text"] = data.text; + dispatchEvent(SPEECH_COMPLETE, params); + } + + void logResponse(TTS::TTS_Error X, JsonObject& response) + { + response["TTS_Status"] = static_cast(X); + switch (X){ + case (TTS::TTS_OK): + TTSLOG_VERBOSE("%s api operational success with TTS_OK code = %d", __func__, X); + break; + case (TTS::TTS_FAIL): + TTSLOG_ERROR("%s api failed with TTS_FAIL error code = %d", __func__, X); + break; + case (TTS::TTS_NOT_ENABLED): + TTSLOG_ERROR("%s api failed with TTS_NOT_ENABLED error code = %d", __func__, X); + break; + case (TTS::TTS_INVALID_CONFIGURATION): + TTSLOG_ERROR("%s api failed with TTS_INVALID_CONFIGURATION error code = %d", __func__, X); + break; + default: + TTSLOG_ERROR("%s api failed with unknow error code = %d", __func__, X); + response["TTS_Status"] = static_cast(TTS::TTS_FAIL); + } + } + + bool _readFromFile(std::string filename, TTS::TTSConfiguration &ttsConfig) + { + Core::File file(filename); + if(file.Open()) { + JsonObject config; + if(config.IElement::FromFile(file)) { + Core::JSON::Boolean enabled = config.Get("enabled").Boolean(); + ttsConfig.setEnabled(enabled.Value()); + ttsConfig.setVolume(std::stod(GET_STR(config,"volume","0.0"))); + ttsConfig.setRate(static_cast(std::stoi(GET_STR(config,"rate","0")))); + ttsConfig.setPrimVolDuck(static_cast(std::stoi(GET_STR(config,"primvolduckpercent","25")))); + ttsConfig.setVoice(GET_STR(config,"voice","")); + ttsConfig.setLanguage(GET_STR(config,"language","")); + if(config.HasLabel("fallbacktext")) { + JsonObject fallback; + FallbackData data; + fallback = config["fallbacktext"].Object(); + data.scenario = fallback["scenario"].String(); + data.value = fallback["value"].String(); + data.path = fallback["path"].String(); + ttsConfig.setFallBackText(data); + } + return true; + } + file.Close(); + } + return false; + } + + std::mutex fileMutex; + bool _writeToFile(std::string filename, TTS::TTSConfiguration &ttsConfig) + { + std::lock_guard lock(fileMutex); + Core::File file(filename); + JsonObject config; + file.Create(); + config["enabled"] = JsonValue((bool)ttsConfig.enabled()); + config["volume"] = std::to_string(ttsConfig.volume()); + config["rate"] = std::to_string(ttsConfig.rate()); + config["primvolduckpercent"] = std::to_string(ttsConfig.primVolDuck()); + config["voice"] = ttsConfig.voice(); + config["language"] = ttsConfig.language(); + if(ttsConfig.isFallbackEnabled()) + { + JsonObject fallbackconfig; + fallbackconfig["scenario"] = ttsConfig.getFallbackScenario(); + fallbackconfig["value"] = ttsConfig.getFallbackValue(); + fallbackconfig["path"] =ttsConfig.getFallbackPath(); + config["fallbacktext"] = fallbackconfig; + } + config.IElement::ToFile(file); + fsync((int)file); + file.Close(); + return true; + } + +} // namespace Plugin +} // namespace WPEFramework diff --git a/LgiTextToSpeech/TextToSpeechImplementation.h b/LgiTextToSpeech/TextToSpeechImplementation.h new file mode 100644 index 0000000000..a34655cf14 --- /dev/null +++ b/LgiTextToSpeech/TextToSpeechImplementation.h @@ -0,0 +1,140 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Module.h" +#include +#include "tracing/Logging.h" + +#include "ITextToSpeech.h" +#include "impl/TTSManager.h" +#include "impl/TTSConfiguration.h" +#include + +namespace WPEFramework { +namespace Plugin { + + class TextToSpeechImplementation : public Exchange::ITextToSpeech, public TTS::TTSEventCallback { + public: + enum Event { + STATE_CHANGED, + VOICE_CHANGED, + WILL_SPEAK, + SPEECH_START, + SPEECH_PAUSE, + SPEECH_RESUME, + SPEECH_CANCEL, + SPEECH_INTERRUPT, + NETWORK_ERROR, + PLAYBACK_ERROR, + SPEECH_COMPLETE + }; + + class EXTERNAL Job : public Core::IDispatch { + protected: + Job(TextToSpeechImplementation *tts, Event event, string &data) + : _tts(tts) + , _event(event) + , _data(data) { + if (_tts != nullptr) { + _tts->AddRef(); + } + } + + public: + Job() = delete; + Job(const Job&) = delete; + Job& operator=(const Job&) = delete; + ~Job() { + if (_tts != nullptr) { + _tts->Release(); + } + } + + public: + static Core::ProxyType Create(TextToSpeechImplementation *tts, Event event, string data) { + return (Core::ProxyType(Core::ProxyType::Create(tts, event, data))); + } + + virtual void Dispatch() { + _tts->Dispatch(_event, _data); + } + + private: + TextToSpeechImplementation *_tts; + const Event _event; + const string _data; + }; + + public: + // We do not allow this plugin to be copied !! + TextToSpeechImplementation(const TextToSpeechImplementation&) = delete; + TextToSpeechImplementation& operator=(const TextToSpeechImplementation&) = delete; + + virtual uint32_t Configure(PluginHost::IShell* service); + virtual void Register(INotification* sink) override ; + virtual void Unregister(INotification* sink) override ; + + virtual uint32_t Enable(const string &input, string &output /* @out */) override ; + virtual uint32_t ListVoices(const string &input, string &output /* @out */) override ; + virtual uint32_t SetConfiguration(const string &input, string &output /* @out */) override ; + virtual uint32_t GetConfiguration(const string &input, string &output /* @out */) override ; + virtual uint32_t IsEnabled(const string &input, string &output /* @out */) override ; + virtual uint32_t Speak(const string &input, string &output /* @out */) override ; + virtual uint32_t Cancel(const string &input, string &output /* @out */) override ; + virtual uint32_t Pause(const string &input, string &output /* @out */) override ; + virtual uint32_t Resume(const string &input, string &output /* @out */) override ; + virtual uint32_t IsSpeaking(const string &input, string &output /* @out */) override ; + virtual uint32_t GetSpeechState(const string &input, string &output /* @out */) override ; + + virtual void onTTSStateChanged(bool enabled) override ; + virtual void onVoiceChanged(std::string voice) override ; + virtual void onWillSpeak(TTS::SpeechData &data) override ; + virtual void onSpeechStart(TTS::SpeechData &data) override ; + virtual void onSpeechPause(uint32_t speechId) override ; + virtual void onSpeechResume(uint32_t speechId) override ; + virtual void onSpeechCancelled(std::vector speechIds) override ; + virtual void onSpeechInterrupted(uint32_t speechId) override ; + virtual void onNetworkError(uint32_t speechId) override ; + virtual void onPlaybackError(uint32_t speechId) override ; + virtual void onSpeechComplete(TTS::SpeechData &data) override ; + + BEGIN_INTERFACE_MAP(TextToSpeechImplementation) + INTERFACE_ENTRY(Exchange::ITextToSpeech) + END_INTERFACE_MAP + + private: + static TTS::TTSManager* _ttsManager; + mutable Core::CriticalSection _adminLock; + std::list _notificationClients; + + void dispatchEvent(Event, JsonObject ¶ms); + void Dispatch(Event event, string data); + + public: + TextToSpeechImplementation(); + virtual ~TextToSpeechImplementation(); + void setResponseArray(JsonObject& response, const char* key, const std::vector& items); + + friend class Job; + }; + +} // namespace Plugin +} // namespace WPEFramework diff --git a/LgiTextToSpeech/TextToSpeechJsonRpc.cpp b/LgiTextToSpeech/TextToSpeechJsonRpc.cpp new file mode 100644 index 0000000000..f136fb3ed2 --- /dev/null +++ b/LgiTextToSpeech/TextToSpeechJsonRpc.cpp @@ -0,0 +1,270 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TextToSpeech.h" +#include "UtilsJsonRpc.h" +#include "UtilsUnused.h" + +namespace WPEFramework { +namespace Plugin { + + void TextToSpeech::RegisterAll() + { + Register("enabletts", &TextToSpeech::Enable, this); + Register("listvoices", &TextToSpeech::ListVoices, this); + Register("setttsconfiguration", &TextToSpeech::SetConfiguration, this); + Register("getttsconfiguration", &TextToSpeech::GetConfiguration, this); + Register("isttsenabled", &TextToSpeech::IsEnabled, this); + Register("speak", &TextToSpeech::Speak, this); + Register("cancel", &TextToSpeech::Cancel, this); + Register("pause", &TextToSpeech::Pause, this); + Register("resume", &TextToSpeech::Resume, this); + Register("isspeaking", &TextToSpeech::IsSpeaking, this); + Register("getspeechstate", &TextToSpeech::GetSpeechState, this); + Register("setACL", &TextToSpeech::SetACL, this); + Register("getapiversion", &TextToSpeech::getapiversion, this); + } + + bool TextToSpeech::AddToAccessList(const string &key,const string &value) + { + std::map::iterator itr; + itr = m_AccessList.find(key); + if (itr != m_AccessList.end()) + { + TTSLOG_INFO("method %s found in accesslist...replacing value with %s\n",key.c_str(),value.c_str()); + itr->second= value; + return 1; + } + else + { + TTSLOG_INFO("method %s not found..inserting to accesslist with value %s\n",key.c_str(),value.c_str()); + m_AccessList.insert(std::make_pair(key,value)); + return 0; + } + } + + bool TextToSpeech::HasAccess(const string &method,string &app) + { + std::map::iterator itr; + itr = m_AccessList.find(method); + if (itr != m_AccessList.end()) + { + string value = itr->second; + string app_quote = '\"' + app + '\"';//wrap it with double quote + std::string::size_type pos = value.find(app_quote); + TTSLOG_INFO("method %s found in accesslist and can be accessed by %s\n",method.c_str(),value.c_str()); + if(pos != std::string::npos) + { + TTSLOG_INFO("%s app has access to method %s\n",app.c_str(),method.c_str()); + return true; + } + + TTSLOG_WARNING("%s app does not have access to method %s\n",app.c_str(),method.c_str()); + return false; + } + else + { + TTSLOG_WARNING("method :%s not found in accesslist\n",method.c_str()); + return false; + } + } + + uint32_t TextToSpeech::SetACL(const JsonObject& parameters, JsonObject& response) + { + CHECK_TTS_PARAMETER_RETURN_ON_FAIL("accesslist"); + TTSLOG_INFO("SetACL request:%s\n",parameters["accesslist"].String().c_str()); + JsonArray list = parameters["accesslist"].Array(); + JsonArray::Iterator it = list.Elements(); + while(it.Next()) + { + JsonObject accesslist = it.Current().Object(); + if (accesslist.HasLabel("method") && accesslist.HasLabel("apps")) + { + m_AccessMutex.lock(); + AddToAccessList(accesslist["method"].String(),accesslist["apps"].String()); + m_AccessMutex.unlock(); + } + else + { + TTSLOG_WARNING("SetACL wrong input parameters\n"); + returnResponse(false); + } + } + m_AclCalled = true; + returnResponse(true); + } + + uint32_t TextToSpeech::Enable(const JsonObject& parameters, JsonObject& response) + { + if(_tts) { + string params, result; + parameters.ToString(params); + uint32_t ret= _tts->Enable(params, result); + response.FromString(result); + return ret; + } + return Core::ERROR_NONE; + } + + uint32_t TextToSpeech::ListVoices(const JsonObject& parameters, JsonObject& response) + { + if(_tts) { + string params, result; + parameters.ToString(params); + uint32_t ret = _tts->ListVoices(params, result); + response.FromString(result); + return ret; + } + return Core::ERROR_NONE; + } + + uint32_t TextToSpeech::SetConfiguration(const JsonObject& parameters, JsonObject& response) + { + if(_tts) { + string params, result; + parameters.ToString(params); + uint32_t ret= _tts->SetConfiguration(params, result); + response.FromString(result); + return ret; + } + return Core::ERROR_NONE; + } + + uint32_t TextToSpeech::GetConfiguration(const JsonObject& parameters, JsonObject& response) + { + if(_tts) { + string result; + uint32_t ret = _tts->GetConfiguration(result, result); + response.FromString(result); + return ret; + } + return Core::ERROR_NONE; + } + + uint32_t TextToSpeech::IsEnabled(const JsonObject& parameters ,JsonObject& response) + { + if(_tts) { + string result; + uint32_t ret = _tts->IsEnabled(result, result); + response.FromString(result); + return ret; + } + return Core::ERROR_NONE; + } + + uint32_t TextToSpeech::Speak(const JsonObject& parameters, JsonObject& response) + { + std::string callsign = parameters["callsign"].String(); + // if setACL() not called, we ignore speak's callsign parameter + if(!m_AclCalled || (m_AclCalled && HasAccess("speak",callsign))) + { + if(_tts) { + string params, result; + parameters.ToString(params); + uint32_t ret= _tts->Speak(params, result); + response.FromString(result); + return ret; + } + } + TTSLOG_WARNING("No Speak access for callsign %s\n",callsign.c_str()); + response["speechid"] = (int) -1; + response["TTS_Status"] = static_cast(TTS::TTS_NO_ACCESS); + returnResponse(false); + } + + uint32_t TextToSpeech::Cancel(const JsonObject& parameters, JsonObject& response) + { + if(_tts) { + string params, result; + parameters.ToString(params); + uint32_t ret= _tts->Cancel(params, result); + response.FromString(result); + return ret; + } + return Core::ERROR_NONE; + } + + + uint32_t TextToSpeech::Pause(const JsonObject& parameters, JsonObject& response) + { + if(_tts) { + string params, result; + parameters.ToString(params); + uint32_t ret= _tts->Pause(params, result); + response.FromString(result); + return ret; + } + return Core::ERROR_NONE; + } + + uint32_t TextToSpeech::Resume(const JsonObject& parameters, JsonObject& response) + { + if(_tts) { + string params, result; + parameters.ToString(params); + uint32_t ret= _tts->Resume(params, result); + response.FromString(result); + return ret; + } + return Core::ERROR_NONE; + } + + uint32_t TextToSpeech::IsSpeaking(const JsonObject& parameters, JsonObject& response) + { + if(_tts) { + string params, result; + parameters.ToString(params); + uint32_t ret = _tts->IsSpeaking(params, result); + response.FromString(result); + return ret; + } + return Core::ERROR_NONE; + } + + uint32_t TextToSpeech::GetSpeechState(const JsonObject& parameters, JsonObject& response) + { + if(_tts) { + string params, result; + parameters.ToString(params); + uint32_t ret = _tts->GetSpeechState(params, result); + response.FromString(result); + return ret; + } + return Core::ERROR_NONE; + } + + uint32_t TextToSpeech::getapiversion(const JsonObject& parameters, JsonObject& response) + { + UNUSED(parameters); + + response["version"] = _apiVersionNumber; + + returnResponse(true); + } + + void TextToSpeech::dispatchJsonEvent(const char *event, const string &data) + { + TTSLOG_WARNING("Notify %s %s", event, data.c_str()); + JsonObject params; + params.FromString(data); + Notify(event, params); + } + +} // namespace Plugin +} // namespace WPEFramework diff --git a/LgiTextToSpeech/TextToSpeechPlugin.json b/LgiTextToSpeech/TextToSpeechPlugin.json new file mode 100644 index 0000000000..66bb882b4f --- /dev/null +++ b/LgiTextToSpeech/TextToSpeechPlugin.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/rdkcentral/rdkservices/main/Tools/json_generator/schemas/plugin.schema.json", + "info": { + "title": "TextToSpeech Plugin", + "callsign": "org.rdk.TextToSpeech", + "locator": "libWPEFrameworkTextToSpeech.so", + "status": "production", + "description": "The `TextToSpeech` plugin provides TTS functionality (Voice Guidance & Speech Synthesis) for the client application." + }, + "interface": { + "$ref": "TextToSpeech.json#" + } +} \ No newline at end of file diff --git a/LgiTextToSpeech/impl/TTSCommon.h b/LgiTextToSpeech/impl/TTSCommon.h new file mode 100644 index 0000000000..ceea72ed7f --- /dev/null +++ b/LgiTextToSpeech/impl/TTSCommon.h @@ -0,0 +1,71 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _TTS_ERRORS_H_ +#define _TTS_ERRORS_H_ + +#include "../Module.h" +#include "logger.h" +#include + +namespace TTS { + + using SpeechState = ttsproxy::SpeechState; + + enum ExtendedEvents { + EXT_EVENT_WILL_SPEAK = 1 << 0, + EXT_EVENT_PAUSED = 1 << 1, + EXT_EVENT_RESUMED = 1 << 2, + EXT_EVENT_CANCELLED = 1 << 3, + EXT_EVENT_INTERRUPTED = 1 << 4, + EXT_EVENT_NETWORK_ERROR = 1 << 5, + EXT_EVENT_PLAYBACK_ERROR = 1 << 6, + EXT_EVENT_ALL = 0xFFFF + }; + + enum TTS_Error { + TTS_OK = 0, + TTS_FAIL, + TTS_NOT_ENABLED, + TTS_INVALID_CONFIGURATION, + TTS_NO_ACCESS + }; +} // namespace TTS + +namespace WPEFramework { +namespace Plugin { + void logResponse(TTS::TTS_Error X, JsonObject& response); +} //namespace WPEFramework +} //namespace Plugin + +#define CHECK_TTS_MANAGER_RETURN_ON_FAIL() do {\ + if(!_ttsManager) { \ + LOGERR("Invalid TTSManager instance"); \ + logResponse(TTS::TTS_NOT_ENABLED,response); \ + returnResponse(false); \ + } } while(0) + +#define CHECK_TTS_PARAMETER_RETURN_ON_FAIL(param) do {\ + if(!parameters.HasLabel(param)) { \ + LOGERR("Parameter \"%s\" is not found", param); \ + returnResponse(false); \ + } } while(0) + + +#endif diff --git a/LgiTextToSpeech/impl/TTSConfiguration.h b/LgiTextToSpeech/impl/TTSConfiguration.h new file mode 100644 index 0000000000..3b8d81e9f1 --- /dev/null +++ b/LgiTextToSpeech/impl/TTSConfiguration.h @@ -0,0 +1,71 @@ +#ifndef _TTS_CONFIG_H_ +#define _TTS_CONFIG_H_ +#include +#include +#include + +struct FallbackData +{ + std::string scenario; + std::string value; + std::string path; +}; +namespace TTS { + + +class TTSConfiguration { +public: + bool setEndPoint(const std::string endpoint) {return true;}; + bool setSecureEndPoint(const std::string endpoint) {return true;}; + bool setApiKey(const std::string apikey) {return true;}; + bool setLanguage(const std::string language) {return true;}; + bool setVoice(const std::string voice) {return true;}; + bool setEnabled(const bool dnabled) {return true;}; + bool setVolume(const double volume) {return true;}; + bool setRate(const uint8_t rate) {return true;}; + bool setPrimVolDuck(const int8_t primvolduck) {return true;}; + + bool isFallbackEnabled() {return true;}; + void saveFallbackPath(std::string){}; + const std::string getFallbackScenario(){return "";}; + const std::string getFallbackPath(){return "";}; + const std::string getFallbackValue(){return "";}; + bool setFallBackText(FallbackData &fd){return true;}; + void setPreemptiveSpeak(const bool preemptive){}; + + const std::string &endPoint() { return m_ttsEndPoint; } + const std::string &secureEndPoint() { return m_ttsEndPointSecured; } + const std::string &apiKey() { return m_apiKey; } + const std::string &language() { return m_language; } + const double &volume() { return m_volume; } + const uint8_t &rate() { return m_rate; } + const int8_t &primVolDuck() { return m_primVolDuck; } + const bool enabled() { return m_enabled; } + bool isPreemptive() { return m_preemptiveSpeaking; } + bool loadFromConfigStore(){return true;}; + bool updateConfigStore(){return true;}; + const std::string voice(){return "";}; + + bool updateWith(TTSConfiguration &config){(void) config; return true;}; + bool isValid(){return true;}; + + static std::map m_others; + +private: + std::string m_ttsEndPoint; + std::string m_ttsEndPointSecured; + std::string m_apiKey; + std::string m_language; + std::string m_voice; + double m_volume{}; + uint8_t m_rate{}; + int8_t m_primVolDuck{}; + bool m_preemptiveSpeaking{}; + bool m_enabled{}; + bool m_fallbackenabled{}; + FallbackData m_data{}; + std::mutex m_mutex; +}; + +}//end of TTS namespace +#endif diff --git a/LgiTextToSpeech/impl/TTSManager.cpp b/LgiTextToSpeech/impl/TTSManager.cpp new file mode 100644 index 0000000000..692f3a1d81 --- /dev/null +++ b/LgiTextToSpeech/impl/TTSManager.cpp @@ -0,0 +1,180 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TTSManager.h" + +namespace { + std::string convertDialectToLanguage(const std::string &dialect) { + std::map dialectToLanguage = { + {"eng-GBR", "en-GB"}, + {"nld-NLD", "nl-NL"}, + {"fra-FRA", "fr-FR"}, + {"deu-DEU", "de-DE"}, + {"ita-ITA", "it-IT"}, + }; + auto it = dialectToLanguage.find(dialect); + if(it != dialectToLanguage.end()) { + return it->second; + } + + TTSLOG_ERROR("%s no mapping for %s!!", __func__, dialect.c_str()); + return ""; + } +} + +namespace TTS { + +std::map TTSConfiguration::m_others; + +TTSManager* TTSManager::create(TTSEventCallback *eventCallback) +{ + TTSLOG_TRACE("TTSManager::create"); + return new TTSManager(eventCallback); +} + +TTSManager::TTSManager(TTSEventCallback *callback) : + m_callback(callback), + m_proxy(*this) +{ + TTSLOG_TRACE("TTSManager::TTSManager"); +} + +TTSManager::~TTSManager() { + TTSLOG_TRACE("TTSManager::~TTSManager"); + m_callback = NULL; + +} + +TTS_Error TTSManager::enableTTS(bool enable) { + (void) enable; + TTSLOG_ERROR("%s is not supported", __func__); + return TTS_FAIL; +} + +bool TTSManager::isTTSEnabled() { + return m_proxy.isTtsEnabled(); +} + +TTS_Error TTSManager::listVoices(std::string language, std::vector &voices) { + TTSLOG_ERROR("%s is not supported", __func__); + return TTS_FAIL; +} + +TTS_Error TTSManager::setConfiguration(Configuration &configuration) { + TTSLOG_ERROR("%s is not supported", __func__); + return TTS_FAIL; +} + +TTS_Error TTSManager::getConfiguration(Configuration &configuration) { + std::string dialect; + m_proxy.getLanguageConfiguration(dialect); + configuration.language = convertDialectToLanguage(dialect); + if(configuration.language.empty()) { + configuration.language = dialect; + } + return TTS_OK; +} + +TTS_Error TTSManager::speak(int& speechId, std::string text) { + ttsproxy::SpeechResult res{}; + const bool force = true; + bool ret = m_proxy.speak(text, force, res); + if(!ret || !res.result) + return TTS_FAIL; + speechId = res.speechId; + return TTS_OK; +} + +TTS_Error TTSManager::pause(uint32_t id) { + + if(!m_proxy.pause(id)) + return TTS_FAIL; + return TTS_OK; +} + +TTS_Error TTSManager::resume(uint32_t id) { + if(!m_proxy.resume(id)) + return TTS_FAIL; + return TTS_OK; + +} + +TTS_Error TTSManager::shut(uint32_t id) { + if(!m_proxy.cancel(id)) + return TTS_FAIL; + return TTS_OK; + +} + +TTS_Error TTSManager::isSpeaking(uint32_t id, bool &speaking) { + if(!m_proxy.isSpeaking(id, speaking)) + return TTS_FAIL; + return TTS_OK; + +} + +TTS_Error TTSManager::getSpeechState(uint32_t id, SpeechState &state) { + if(!m_proxy.getSpeechState(id, state)) + return TTS_FAIL; + return TTS_OK; +} + +void TTSManager::onTtsStateChanged(const bool state) +{ + m_callback->onTTSStateChanged(state); +} + +void TTSManager::onSpeechStart(const unsigned speechId, const std::string& text) +{ + SpeechData data{speechId, text}; + m_callback->onSpeechStart(data); +} + +void TTSManager::onSpeechComplete(const unsigned speechId, const std::string& text) +{ + SpeechData data{speechId, text}; + m_callback->onSpeechComplete(data); +} + +void TTSManager::onSpeechInterrupted(const unsigned speechId) +{ + m_callback->onSpeechInterrupted(speechId); +} + +void TTSManager::onSpeechPause(const unsigned speechId) +{ + m_callback->onSpeechPause(speechId); +} + +void TTSManager::onSpeechResume(const unsigned speechId) +{ + m_callback->onSpeechResume(speechId); +} + +void TTSManager::onNetworkError(const unsigned speechId) +{ + m_callback->onNetworkError(speechId); +} + +void TTSManager::onPlaybackError(const unsigned speechId) +{ + m_callback->onPlaybackError(speechId); +} + +} // namespace TTS diff --git a/LgiTextToSpeech/impl/TTSManager.h b/LgiTextToSpeech/impl/TTSManager.h new file mode 100644 index 0000000000..65c68674b8 --- /dev/null +++ b/LgiTextToSpeech/impl/TTSManager.h @@ -0,0 +1,110 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _TTS_ENGINE_H_ +#define _TTS_ENGINE_H_ + +#include "TTSCommon.h" +#include "TTSConfiguration.h" +#include + +#include + +namespace TTS { + +struct Configuration { + Configuration() : volume(0), rate(0), primVolDuck(25) {}; + ~Configuration() {} + + std::string ttsEndPoint; + std::string ttsEndPointSecured; + std::string language; + std::string voice; + std::string apiKey; + FallbackData data{}; + double volume{}; + uint8_t rate{}; + int8_t primVolDuck{}; +}; + +struct SpeechData { + public: + uint32_t id; + std::string text; +}; + +class TTSEventCallback { +public: + TTSEventCallback() = default; + virtual ~TTSEventCallback() = default; + + virtual void onTTSStateChanged(bool enabled) { (void)enabled; } + virtual void onVoiceChanged(std::string voice) { (void)voice; } + virtual void onWillSpeak(SpeechData &data) { (void)data; } + virtual void onSpeechStart(SpeechData &data) { (void)data; } + virtual void onSpeechPause(uint32_t speechId) { (void)speechId; } + virtual void onSpeechResume(uint32_t speechId) { (void)speechId; } + virtual void onSpeechCancelled(std::vector speechIds) { (void)speechIds; } + virtual void onSpeechInterrupted(uint32_t speechId) { (void)speechId; } + virtual void onNetworkError(uint32_t speechId) { (void)speechId; } + virtual void onPlaybackError(uint32_t speechId) { (void)speechId; } + virtual void onSpeechComplete(SpeechData &data) { (void)data; } +}; + +class TTSManager: public ttsproxy::TtsEventListener { +public: + static TTSManager *create(TTSEventCallback *eventCallback); + TTSManager(TTSEventCallback *eventCallback); + virtual ~TTSManager(); + + // TTS Global APIs + TTS_Error enableTTS(bool enable); + bool isTTSEnabled(); + TTS_Error listVoices(std::string language, std::vector &voices); + TTS_Error setConfiguration(Configuration &configuration); + TTS_Error getConfiguration(Configuration &configuration); + + //Speak APIs + TTS_Error speak(int& speechId, std::string text); + TTS_Error pause(uint32_t id); + TTS_Error resume(uint32_t id); + TTS_Error shut(uint32_t id); + TTS_Error isSpeaking(uint32_t id, bool &speaking); + TTS_Error getSpeechState(uint32_t id, SpeechState &state); + TTS_Error clearAudioPipeline() {return TTS_OK;} ; + virtual TTSConfiguration *configuration() {return &m_defaultConfiguration;} + +private: + void onTtsStateChanged(const bool state) override; + void onSpeechStart(const unsigned speechId, const std::string& text) override; + void onSpeechComplete(const unsigned speechId, const std::string& text) override; + void onSpeechInterrupted(const unsigned speechId) override; + void onSpeechPause(const unsigned speechId) override; + void onSpeechResume(const unsigned speechId) override; + void onNetworkError(const unsigned speechId) override; + void onPlaybackError(const unsigned speechId) override; + + TTSEventCallback *m_callback; + TTSConfiguration m_defaultConfiguration; + ttsproxy::TtsProxy m_proxy; +}; + +} // namespace TTS + +#endif diff --git a/LgiTextToSpeech/impl/logger.cpp b/LgiTextToSpeech/impl/logger.cpp new file mode 100644 index 0000000000..9dfac2b63f --- /dev/null +++ b/LgiTextToSpeech/impl/logger.cpp @@ -0,0 +1,170 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "logger.h" +#include +#include +#include +#include +#include + +#ifdef USE_RDK_LOGGER +#include "rdk_debug.h" +#endif + +namespace TTS { + + static inline void sync_stdout() + { + if (getenv("SYNC_STDOUT")) + setvbuf(stdout, NULL, _IOLBF, 0); + } + + const char* methodName(const std::string& prettyFunction) + { + size_t colons = prettyFunction.find("::"); + size_t begin = prettyFunction.substr(0,colons).rfind(" ") + 1; + size_t end = prettyFunction.rfind("(") - begin; + + return prettyFunction.substr(begin,end).c_str(); + } + +#ifdef USE_RDK_LOGGER + + void logger_init() + { + sync_stdout(); + TTS_logger_init("/etc/debug.ini"); + } + + void log(LogLevel level, + const char* func, + const char* file, + int line, + int, // thread id is already handled by TTS_logger + const char* format, ...) + { + const TTS_LogLevel levelMap[] = + {TTS_LOG_FATAL, TTS_LOG_ERROR, TTS_LOG_WARN, TTS_LOG_INFO, TTS_LOG_DEBUG, TTS_LOG_TRACE1}; + + const short kFormatMessageSize = 4096; + // TTSLogger is backed with log4c which has its own default level + // for filtering messages. Therefore, we don't check level here. + char userFormatted[kFormatMessageSize]; + char finalFormatted[kFormatMessageSize]; + + va_list argptr; + va_start(argptr, format); + vsnprintf(userFormatted, kFormatMessageSize, format, argptr); + snprintf(finalFormatted, kFormatMessageSize, "%s:%s:%d %s", func, + basename(file), + line, + userFormatted); + va_end(argptr); + + // Currently, we use customized layout 'comcast_dated_nocr' in log4c. + // This layout doesn't have trailing carriage return, so we need + // to add it explicitly. + // Once the default layout is used, this addition should be deleted. + RDK_LOG(levelMap[static_cast(level)], + "LOG.RDK.TTS", + "%s\n", + finalFormatted); + + if (FATAL_LEVEL == level) + std::abort(); + } + +#else + + static int gDefaultLogLevel = INFO_LEVEL; + // static int gDefaultLogLevel = VERBOSE_LEVEL; + + void logger_init() + { + sync_stdout(); + const char* level = getenv("TTS_DEFAULT_LOG_LEVEL"); + if (level) + gDefaultLogLevel = static_cast(atoi(level)); + } + + void log(LogLevel level, + const char* func, + const char* file, + int line, + int threadID, + const char* format, ...) + { + if (gDefaultLogLevel < level) + return; + + const char* levelMap[] = {"Fatal", "Error", "Warning", "Info", "Verbose", "Trace"}; + const short kFormatMessageSize = 4096; + char formatted[kFormatMessageSize]; + + va_list argptr; + va_start(argptr, format); + vsnprintf(formatted, kFormatMessageSize, format, argptr); + va_end(argptr); + + char timestamp[0xFF] = {0}; + struct timespec spec; + struct tm tm; + + clock_gettime(CLOCK_REALTIME, &spec); + gmtime_r(&spec.tv_sec, &tm); + long ms = spec.tv_nsec / 1.0e6; + + sprintf(timestamp, "%02d%02d%02d-%02d:%02d:%02d.%03ld", + tm.tm_year % 100, + tm.tm_mon + 1, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + ms); + + if (threadID) + { + printf("%s [%s] [pid=%d,tid=%d] %s:%s:%d %s\n", + timestamp, + levelMap[static_cast(level)], + getpid(), + threadID, + func, basename(file), line, + formatted); + } + else + { + printf("%s [%s] %s:%s:%d %s\n", + timestamp, + levelMap[static_cast(level)], + func, basename(file), line, + formatted); + } + + fflush(stdout); + + if (FATAL_LEVEL == level) + std::abort(); + } + +#endif // USE_TTS_LOGGER + +} // namespace TTS diff --git a/LgiTextToSpeech/impl/logger.h b/LgiTextToSpeech/impl/logger.h new file mode 100644 index 0000000000..a2a1579a41 --- /dev/null +++ b/LgiTextToSpeech/impl/logger.h @@ -0,0 +1,92 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TTS_LOGGER_H +#define TTS_LOGGER_H + +#include +#include +#include +#include +#include +#include + +namespace TTS { + +const char* methodName(const std::string& prettyFunction); +#define __METHOD_NAME__ TTS::methodName(__PRETTY_FUNCTION__) + +/** + * Logging level with an increasing order of refinement + * (TRACE_LEVEL = Finest logging) + * It is essental to start with 0 and increase w/o gaps as the value + * can be used for indexing in a mapping table. + */ +enum LogLevel {FATAL_LEVEL = 0, ERROR_LEVEL, WARNING_LEVEL, INFO_LEVEL, VERBOSE_LEVEL, TRACE_LEVEL}; + +/** + * @brief Init logging + * Should be called once per program run before calling log-functions + */ +void logger_init(); + +#define TTS_assert(expr) do { \ + if ( __builtin_expect(expr, true) ) \ + {} \ + else \ + TTSLOG_ERROR("%s", #expr); \ + } while (0) + +/** + * @brief Log a message + * The function is defined by logging backend. + * Currently 2 variants are supported: TTS_logger (USE_TTS_LOGGER), + * stdout(default) + */ +void log(LogLevel level, + const char* func, + const char* file, + int line, + int threadID, + const char* format, ...); + +#ifdef USE_RDK_LOGGER +#define _LOG(LEVEL, FORMAT, ...) \ + TTS::log(LEVEL, \ + __func__, __FILE__, __LINE__, 0, \ + FORMAT, \ + ##__VA_ARGS__) +#else +#define _LOG(LEVEL, FORMAT, ...) \ + TTS::log(LEVEL, \ + __func__, __FILE__, __LINE__, syscall(__NR_gettid), \ + FORMAT, \ + ##__VA_ARGS__) +#endif + +#define TTSLOG_TRACE(FMT, ...) _LOG(TTS::TRACE_LEVEL, FMT, ##__VA_ARGS__) +#define TTSLOG_VERBOSE(FMT, ...) _LOG(TTS::VERBOSE_LEVEL, FMT, ##__VA_ARGS__) +#define TTSLOG_INFO(FMT, ...) _LOG(TTS::INFO_LEVEL, FMT, ##__VA_ARGS__) +#define TTSLOG_WARNING(FMT, ...) _LOG(TTS::WARNING_LEVEL, FMT, ##__VA_ARGS__) +#define TTSLOG_ERROR(FMT, ...) _LOG(TTS::ERROR_LEVEL, FMT, ##__VA_ARGS__) +#define TTSLOG_FATAL(FMT, ...) _LOG(TTS::FATAL_LEVEL, FMT, ##__VA_ARGS__) + +} // namespace TTS + +#endif // TTS_LOGGER_H diff --git a/Monitor/doc/WPE - API - Monitor.docx b/Monitor/doc/WPE-API-Monitor.docx similarity index 100% rename from Monitor/doc/WPE - API - Monitor.docx rename to Monitor/doc/WPE-API-Monitor.docx diff --git a/OCIContainer/CMakeLists.txt b/OCIContainer/CMakeLists.txt index 2aff9fc947..395e3b038a 100644 --- a/OCIContainer/CMakeLists.txt +++ b/OCIContainer/CMakeLists.txt @@ -6,20 +6,17 @@ set(PLUGIN_OCICONTAINER_STARTUPORDER "" CACHE STRING "To configure startup order find_package(PkgConfig) find_package(${NAMESPACE}Plugins REQUIRED) find_package(Dobby CONFIG) +find_package(LibGlib REQUIRED) +find_package(LibGio REQUIRED) # Temporary fix to get defines in Dobby. Will be removed later. add_definitions( -DRDK ) -pkg_search_module(OMI_CLIENT "omiclientlib") - -if(NOT OMI_CLIENT_FOUND) - set(OMI_CLIENT_INCLUDE_DIRS stubs) - message("Using stubs for omiclientlib") -endif() - add_library(${MODULE_NAME} SHARED OCIContainer.cpp Module.cpp + dbus/omi_dbus_api.c + dbus/omi_client.cpp ) set_target_properties(${MODULE_NAME} PROPERTIES @@ -49,7 +46,8 @@ find_package_handle_standard_args( target_include_directories(${MODULE_NAME} PRIVATE ../helpers - ${OMI_CLIENT_INCLUDE_DIRS} + ${LIBGLIB_INCLUDE_DIRS} + ${LIBGIO_INCLUDE_DIRS} ) @@ -59,8 +57,10 @@ target_link_libraries(${MODULE_NAME} ${DOBBY_LIBRARIES} ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${SYSTEMD_LIBRARIES} - ${OMI_CLIENT_LIBRARIES} + ${LIBGIO_LIBRARIES} + ${LIBGLIB_LIBRARIES} ) + # ${NAMESPACE}Protocols::${NAMESPACE}Protocols install(TARGETS ${MODULE_NAME} DESTINATION lib/${STORAGE_DIRECTORY}/plugins) diff --git a/OCIContainer/OCIContainer.cpp b/OCIContainer/OCIContainer.cpp index 5e277bb037..a8259d93b0 100644 --- a/OCIContainer/OCIContainer.cpp +++ b/OCIContainer/OCIContainer.cpp @@ -4,7 +4,6 @@ #include #include -#include #include "UtilsJsonRpc.h" @@ -44,6 +43,7 @@ OCIContainer::OCIContainer() Register("getContainerState", &OCIContainer::getContainerState, this); Register("getContainerInfo", &OCIContainer::getContainerInfo, this); Register("startContainer", &OCIContainer::startContainer, this); + Register("startContainerFromCryptedBundle", &OCIContainer::startContainerFromCryptedBundle, this); Register("startContainerFromDobbySpec", &OCIContainer::startContainerFromDobbySpec, this); Register("stopContainer", &OCIContainer::stopContainer, this); Register("pauseContainer", &OCIContainer::pauseContainer, this); @@ -53,6 +53,7 @@ OCIContainer::OCIContainer() OCIContainer::~OCIContainer() { + } const string OCIContainer::Initialize(PluginHost::IShell *service) @@ -77,9 +78,8 @@ const string OCIContainer::Initialize(PluginHost::IShell *service) // Register a state change event listener mEventListenerId = mDobbyProxy->registerListener(stateListener, static_cast(this)); - mOmiProxy = std::make_shared(); - - mOmiListenerId = mOmiProxy->registerListener(omiErrorListener, static_cast(this)); + mOmiProxy.Run(); + mOmiProxy.registerListener(omiErrorListener, static_cast(this)); return string(); } @@ -87,11 +87,13 @@ const string OCIContainer::Initialize(PluginHost::IShell *service) void OCIContainer::Deinitialize(PluginHost::IShell *service) { mDobbyProxy->unregisterListener(mEventListenerId); - mOmiProxy->unregisterListener(mOmiListenerId); + mOmiProxy.unregisterListener(); + mOmiProxy.Stop(); Unregister("listContainers"); Unregister("getContainerState"); Unregister("getContainerInfo"); Unregister("startContainer"); + Unregister("startContainerFromCryptedBundle"); Unregister("startContainerFromDobbySpec"); Unregister("stopContainer"); Unregister("pauseContainer"); @@ -161,6 +163,7 @@ uint32_t OCIContainer::getContainerState(const JsonObject ¶meters, JsonObjec int cd = GetContainerDescriptorFromId(id); if (cd < 0) { + response["error"] = "container descriptor can not be acquired"; returnResponse(false); } @@ -217,6 +220,7 @@ uint32_t OCIContainer::getContainerInfo(const JsonObject ¶meters, JsonObject int cd = GetContainerDescriptorFromId(id); if (cd < 0) { + response["error"] = "container descriptor can not be acquired"; returnResponse(false); } @@ -225,6 +229,7 @@ uint32_t OCIContainer::getContainerInfo(const JsonObject ¶meters, JsonObject if (containerInfoString.empty()) { LOGERR("Failed to get info for container %s", id.c_str()); + response["error"] = "failed to get container info"; returnResponse(false); } @@ -233,6 +238,7 @@ uint32_t OCIContainer::getContainerInfo(const JsonObject ¶meters, JsonObject WPEC::OptionalType error; if (!WPEJ::IElement::FromString(containerInfoString, containerInfoJson, error)) { LOGERR("Failed to parse Dobby Spec JSON due to: %s", WPEJ::ErrorDisplayMessage(error).c_str()); + response["error"] = "failed to parse dobby spec"; returnResponse(false); } @@ -299,7 +305,7 @@ uint32_t OCIContainer::startContainer(const JsonObject ¶meters, JsonObject & const bool encrypted = is_encrypted(bundlePath); - if (encrypted && !mOmiProxy->mountCryptedBundle(id, + if (encrypted && !mOmiProxy.mountCryptedBundle(id, bundlePath + "rootfs.img", bundlePath + "config.json.jwt", containerPath)) @@ -343,7 +349,7 @@ uint32_t OCIContainer::startContainer(const JsonObject ¶meters, JsonObject & { LOGERR("Failed to start container - internal Dobby error."); - if (encrypted && !mOmiProxy->umountCryptedBundle(id.c_str())) + if (encrypted && !mOmiProxy.umountCryptedBundle(id.c_str())) { LOGERR("Failed to umount container %s - sync unmount request to omi failed.", id.c_str()); response["error"] = "dobby start failed, unmount failed"; @@ -360,6 +366,85 @@ uint32_t OCIContainer::startContainer(const JsonObject ¶meters, JsonObject & returnResponse(true); } +/** + * @brief Starts a container from a crypted OCI bundle + * + * @param[in] parameters - Must include 'containerId', 'rootFSPath' and 'configFilePath'. + * @param[out] response - Dobby descriptor of the started container. + * + * @return A code indicating success. + */ +uint32_t OCIContainer::startContainerFromCryptedBundle(const JsonObject ¶meters, JsonObject &response) +{ + LOGINFO("Start container from crypted OCI bundle"); + + // To start a container, we need a path to an OCI bundle and an ID for the container + returnIfStringParamNotFound(parameters, "containerId"); + returnIfStringParamNotFound(parameters, "rootFSPath"); + returnIfStringParamNotFound(parameters, "configFilePath"); + + std::string id = parameters["containerId"].String(); + std::string rootfsPath = parameters["rootFSPath"].String(); + std::string configPath = parameters["configFilePath"].String(); + std::string command = parameters["command"].String(); + std::string westerosSocket = parameters["westerosSocket"].String(); + const JsonArray eVars = parameters.HasLabel("envVars") ? parameters["envVars"].Array() : JsonArray(); + std::vector envVars = std::vector(); + for (int k = 0; k < eVars.Length(); k++) + { + envVars.push_back(eVars[k].String()); + } + + // Can be used to pass file descriptors to container construction. + // Currently unsupported, see DobbyProxy::startContainerFromBundle(). + std::list emptyList; + + int descriptor; + + std::string containerPath; + + if (!mOmiProxy.mountCryptedBundle(id, + rootfsPath, + configPath, + containerPath)) + { + LOGERR("Failed to start container - sync mount request to omi failed."); + response["error"] = "mount failed"; + returnResponse(false); + } + + LOGINFO("Mount request to omi succeeded, containerPath: %s", containerPath.c_str()); + + // Dobby expects empty strings if values not set + if (command == "null") + { + command = ""; + } + if (westerosSocket == "null") + { + westerosSocket = ""; + } + descriptor = mDobbyProxy->startContainerFromBundle(id, containerPath, emptyList, command, westerosSocket, envVars); + + // startContainer returns -1 on failure + if (descriptor <= 0) + { + LOGERR("Failed to start container - internal Dobby error. Unmounting container."); + + if (!mOmiProxy.umountCryptedBundle(id.c_str())) { + LOGERR("Failed to umount container %s - sync unmount request to omi failed.", id.c_str()); + response["error"] = "dobby start failed, unmount failed"; + } else { + response["error"] = "dobby start failed"; + } + + returnResponse(false); + } + + response["descriptor"] = descriptor; + returnResponse(true); +} + /** * @brief Starts a container using a Dobby spec file * @@ -446,6 +531,7 @@ uint32_t OCIContainer::stopContainer(const JsonObject ¶meters, JsonObject &r int cd = GetContainerDescriptorFromId(id); if (cd < 0) { + response["error"] = "container descriptor can not be acquired"; returnResponse(false); } @@ -457,6 +543,7 @@ uint32_t OCIContainer::stopContainer(const JsonObject ¶meters, JsonObject &r if (!stoppedSuccessfully) { LOGERR("Failed to stop container - internal Dobby error."); + response["error"] = "internal dobby error"; returnResponse(false); } @@ -482,6 +569,7 @@ uint32_t OCIContainer::pauseContainer(const JsonObject ¶meters, JsonObject & int cd = GetContainerDescriptorFromId(id); if (cd < 0) { + response["error"] = "container descriptor can not be acquired"; returnResponse(false); } @@ -490,6 +578,7 @@ uint32_t OCIContainer::pauseContainer(const JsonObject ¶meters, JsonObject & if (!pausedSuccessfully) { LOGERR("Failed to pause container - internal Dobby error."); + response["error"] = "internal dobby error"; returnResponse(false); } @@ -515,6 +604,7 @@ uint32_t OCIContainer::resumeContainer(const JsonObject ¶meters, JsonObject int cd = GetContainerDescriptorFromId(id); if (cd < 0) { + response["error"] = "container descriptor can not be acquired"; returnResponse(false); } @@ -523,6 +613,7 @@ uint32_t OCIContainer::resumeContainer(const JsonObject ¶meters, JsonObject if (!resumedSuccessfully) { LOGERR("Failed to resume container - internal Dobby error."); + response["error"] = "internal dobby error"; returnResponse(false); } @@ -551,6 +642,7 @@ uint32_t OCIContainer::executeCommand(const JsonObject ¶meters, JsonObject & int cd = GetContainerDescriptorFromId(id); if (cd < 0) { + response["error"] = "container descriptor can not be acquired"; returnResponse(false); } @@ -562,6 +654,7 @@ uint32_t OCIContainer::executeCommand(const JsonObject ¶meters, JsonObject & if (!executedSuccessfully) { LOGERR("Failed to execute command in container - internal Dobby error."); + response["error"] = "internal dobby error"; returnResponse(false); } @@ -592,7 +685,7 @@ void OCIContainer::onContainerStarted(int32_t descriptor, const std::string& nam void OCIContainer::onContainerStopped(int32_t descriptor, const std::string& name) { - if (!mOmiProxy->umountCryptedBundle(name)) + if (!mOmiProxy.umountCryptedBundle(name)) { LOGERR("Failed to umount container %s - sync unmount request to omi failed.", name.c_str()); } @@ -679,13 +772,13 @@ const void OCIContainer::stateListener(int32_t descriptor, const std::string& na * @param err Error type * @param _this Callback parameters, or in this case, the pointer to 'this' */ -const void OCIContainer::omiErrorListener(const std::string& id, omi::IOmiProxy::ErrorType err, const void* _this) +const void OCIContainer::omiErrorListener(const std::string& id, OmiDbus::OmiClient::ErrorType err, const void* _this) { // Cast const void* back to OCIContainer* type to get 'this' OCIContainer* __this = const_cast(reinterpret_cast(_this)); - if (__this != nullptr && err == omi::IOmiProxy::ErrorType::verityFailed) + if (__this != nullptr && err == OmiDbus::OmiClient::ErrorType::verityFailed) { __this->onVerityFailed(id); } diff --git a/OCIContainer/OCIContainer.h b/OCIContainer/OCIContainer.h index f0291eea8f..c93b0786fd 100644 --- a/OCIContainer/OCIContainer.h +++ b/OCIContainer/OCIContainer.h @@ -8,7 +8,7 @@ #include #include -#include +#include "dbus/omi_client.hpp" namespace WPEFramework { @@ -40,6 +40,7 @@ class OCIContainer : public PluginHost::IPlugin, public PluginHost::JSONRPC uint32_t getContainerState(const JsonObject ¶meters, JsonObject &response); uint32_t getContainerInfo(const JsonObject ¶meters, JsonObject &response); uint32_t startContainer(const JsonObject ¶meters, JsonObject &response); + uint32_t startContainerFromCryptedBundle(const JsonObject ¶meters, JsonObject &response); uint32_t startContainerFromDobbySpec(const JsonObject ¶meters, JsonObject &response); uint32_t stopContainer(const JsonObject ¶meters, JsonObject &response); uint32_t pauseContainer(const JsonObject ¶meters, JsonObject &response); @@ -68,13 +69,12 @@ class OCIContainer : public PluginHost::IPlugin, public PluginHost::JSONRPC private: int mEventListenerId; // Dobby event listener ID - long unsigned mOmiListenerId; std::shared_ptr mDobbyProxy; // DobbyProxy instance std::shared_ptr mIpcService; // Ipc Service instance const int GetContainerDescriptorFromId(const std::string& containerId); static const void stateListener(int32_t descriptor, const std::string& name, IDobbyProxyEvents::ContainerState state, const void* _this); - static const void omiErrorListener(const std::string& id, omi::IOmiProxy::ErrorType err, const void* _this); - std::shared_ptr mOmiProxy; + static const void omiErrorListener(const std::string& id, OmiDbus::OmiClient::ErrorType err, const void* _this); + OmiDbus::OmiClient mOmiProxy; }; } // namespace Plugin } // namespace WPEFramework diff --git a/OCIContainer/OCIContainer.json b/OCIContainer/OCIContainer.json index 3418a0748a..fd3bbdef23 100644 --- a/OCIContainer/OCIContainer.json +++ b/OCIContainer/OCIContainer.json @@ -41,6 +41,32 @@ "summary":"Name of the Container.", "type":"string", "example":"com.bskyb.epgui" + }, + "result": { + "type":"object", + "properties": { + "success": { + "$ref": "#/common/success" + }, + "error":{ + "summary": "An error message in case of a failure", + "type": "string", + "example": "internal dobby error" + } + }, + "required": [ + "success" + ] + }, + "success": { + "summary": "Whether the request succeeded", + "type": "boolean", + "example": "true" + }, + "error": { + "summary": "Error message", + "type": "string", + "example": "mount failed" } }, "methods": { @@ -194,6 +220,11 @@ }, "success":{ "$ref": "#/common/success" + }, + "error":{ + "summary": "An error message in case of a failure", + "type": "string", + "example": "internal dobby error" } }, "required": [ @@ -235,6 +266,11 @@ }, "success":{ "$ref": "#/common/success" + }, + "error":{ + "summary": "An error message in case of a failure", + "type": "string", + "example": "could not remove symlink" } }, "required": [ @@ -368,6 +404,68 @@ ] } }, + "startContainerFromCryptedBundle":{ + "summary": "Starts a new container from an existing encrypted OCI bundle", + "params": { + "type": "object", + "properties": { + "containerId": { + "$ref": "#/definitions/containerId" + }, + "rootFSPath": { + "summary": "Path to the enrypted OCI bundle containing the rootfs to use to create the container", + "type": "string", + "example": "/containers/rootfs/myBundle" + }, + "configFilePath": { + "summary": "Path to the enrypted OCI bundle containing the config file to use to create the container", + "type": "string", + "example": "/containers/var/myBundle" + }, + "command": { + "$ref": "#/definitions/command" + }, + "westerosSocket":{ + "summary": "Path to a Westeros socket to mount inside the container", + "type": "string", + "example": "/usr/mySocket" + }, + "envvar": { + "summary": "A list of environment variables to add to the container", + "type": "array", + "items": { + "type": "string", + "example": "FOO=BAR" + } + } + }, + "required": [ + "containerId", + "rootFSPath", + "configFilePath" + ] + }, + "result": { + "type": "object", + "properties": { + "descriptor": { + "$ref": "#/definitions/Descriptor" + }, + "success":{ + "$ref": "#/common/success" + }, + "error":{ + "summary": "An error message in case of a failure", + "type": "string", + "example": "internal dobby error" + } + }, + "required": [ + "descriptor", + "success" + ] + } + }, "startContainerFromDobbySpec":{ "summary": "Starts a new container from a legacy Dobby JSON specification.", "events": { @@ -481,4 +579,4 @@ } } -} \ No newline at end of file +} diff --git a/OCIContainer/dbus/omi_client.cpp b/OCIContainer/dbus/omi_client.cpp new file mode 100644 index 0000000000..44bd3be206 --- /dev/null +++ b/OCIContainer/dbus/omi_client.cpp @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2023 LIBERTY GLOBAL all rights reserved. + */ + +#include +#include + +#include +#include + +#include "omi_client.hpp" +#include "UtilsLogging.h" +#include "../Module.h" + +namespace { + const char *OMI_CONFIG_DBUS_INTERFACE_NAME = "com.lgi.onemw.omi1"; + const char *OMI_CONFIG_DBUS_INTERFACE_OBJECT_PATH = "/com/lgi/onemw/omi1"; +} + +namespace OmiDbus +{ + +using namespace WPEFramework; + +OmiClient::OmiClient() +{ + LOGINFO("user_data: %p", this); +} + +OmiClient::~OmiClient() +{ + if (m_interface) + { + Stop(); + } + LOGINFO("user_data: %p", this); +} + +void OmiClient::connectSignal(const char* signalName, + GCallback callback) +{ + auto object = m_interface->proxy; + + auto handle = g_signal_connect(object, signalName, callback, this); + if (!handle) + { + LOGERR("Cannot connect to signal %s", signalName); + } + else + { + m_signalHandles.push_back(handle); + } +} + +void OmiClient::disconnectAllSignals() +{ + auto object = m_interface->proxy; + for (auto handle : m_signalHandles) + { + g_signal_handler_disconnect(object, handle); + } + m_signalHandles.clear(); +} + +void OmiClient::handleDbusEvent(GDBusProxy *proxy, gchar *sender_name, gchar *signal_name_c_str, GVariant *parameters, gpointer user_data) +{ + std::string signal_name = signal_name_c_str; + OmiClient *self = static_cast(user_data); + + if ("VerityFailed" == signal_name) + { + self->handleVerityFailed(parameters); + } +} + +void OmiClient::handleVerityFailed(GVariant *parameters) +{ + gchar *id_c_str; + + g_variant_get(parameters, "(&s)", &id_c_str); + std::string id{id_c_str}; + + std::unique_lock lock(m_listener_mtx); + if(m_listener.first) { + m_listener.first(id, ErrorType::verityFailed, m_listener.second); + } +} + +bool OmiClient::mountCryptedBundle(const std::string& id, + const std::string& rootfs_file_path, + const std::string& config_json_path, + std::string& bundlePath /*out parameter*/) { + gchar *containerPath; + + gboolean res = omi1_call_mount_sync(m_interface, + id.c_str(), + rootfs_file_path.c_str(), + config_json_path.c_str(), + &containerPath, + nullptr, + nullptr); + + if (!res) + { + return false; + } + + bundlePath = std::string(containerPath); + + return true; +} + +bool OmiClient::umountCryptedBundle(const std::string& id) { + return omi1_call_umount_sync(m_interface, + id.c_str(), + nullptr, + nullptr); +} + +void OmiClient::registerListener(const OmiErrorListener &listener, const void* cbParams) { + std::unique_lock lock(m_listener_mtx); + m_listener = std::make_pair(listener, cbParams); +} + +void OmiClient::unregisterListener() { + std::unique_lock lock(m_listener_mtx); + m_listener = std::make_pair(OmiErrorListener(), nullptr); +} + +int OmiClient::Run() +{ + LOGINFO("user_data: %p", this); + + m_loopThread = std::thread(&OmiClient::Worker, this); + bool initialized = m_initialized.get_future().get(); + + if (initialized && m_interface) + { + connectSignal("g-signal", G_CALLBACK (OmiClient::handleDbusEvent)); + LOGINFO("Omi proxy created"); + } + else + { + LOGERR("Failed to create networkconfig proxy."); + } + + return initialized ? 0 : -1; +} + +void OmiClient::Worker() +{ + LOGINFO("OmiClient::Worker() TID: %u", gettid()); + + m_mainContext = g_main_context_new(); + m_mainLoop = g_main_loop_new(m_mainContext, false); + + if(m_mainLoop && m_mainContext) { + g_main_context_push_thread_default(m_mainContext); + + GError* error = nullptr; + m_interface = omi1_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + OMI_CONFIG_DBUS_INTERFACE_NAME, + OMI_CONFIG_DBUS_INTERFACE_OBJECT_PATH, + NULL, /* GCancellable */ + &error); + if (error) + { + LOGERR("Failed to create networkconfig proxy: %s", error->message); + g_error_free(error); + } + m_initialized.set_value(m_interface != nullptr); + + LOGINFO("OmiClient::Worker() start main loop TID: %u", gettid()); + g_main_loop_run(m_mainLoop); // blocks + LOGINFO("OmiClient::Worker() main loop finished TID: %u", gettid()); + g_main_context_pop_thread_default(m_mainContext); + g_main_loop_unref(m_mainLoop); + g_main_context_unref(m_mainContext); + m_mainLoop = nullptr; + m_mainContext = nullptr; + } + else { + LOGERR("Failed to create glib main loop"); + m_initialized.set_value(false); + } +} + +static void release_omi1(Omi1* interface) +{ + if (interface->proxy) g_object_unref(interface->proxy); + g_dbus_connection_flush_sync(interface->connection, NULL, NULL); + g_object_unref(interface->connection); + g_free(interface); +} + +void OmiClient::Stop() +{ + if (m_interface) + { + disconnectAllSignals(); + release_omi1(m_interface); + m_interface = nullptr; + g_main_context_invoke(m_mainContext, +[](gpointer ptr) -> gboolean { + LOGINFO("OmiClient::Stop() quit main loop TID: %u", gettid()); + g_main_loop_quit((GMainLoop*)ptr); + return FALSE; + }, (gpointer)m_mainLoop); + + if(m_loopThread.joinable()) { + m_loopThread.join(); + } + else { + LOGERR("Worker thread should be joinable"); + } + LOGINFO("signals disconnected"); + } +} + +} // namespace lginet + diff --git a/OCIContainer/dbus/omi_client.hpp b/OCIContainer/dbus/omi_client.hpp new file mode 100644 index 0000000000..b776218bf7 --- /dev/null +++ b/OCIContainer/dbus/omi_client.hpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 LIBERTY GLOBAL all rights reserved. + */ + +#ifndef OMI_CLIENT_HPP_ +#define OMI_CLIENT_HPP_ + +#include +#include +#include + +#include "omi_dbus_api.h" + +namespace OmiDbus +{ + +class OmiClient +{ +public: + enum class ErrorType { verityFailed }; + typedef std::function OmiErrorListener; + +public: + OmiClient(); + ~OmiClient(); + OmiClient(const OmiClient&) = delete; + OmiClient& operator=(OmiClient const&) = delete; + + int Run(); + void Stop(); + + bool mountCryptedBundle(const std::string& id, + const std::string& rootfs_file_path, + const std::string& config_json_path, + std::string& bundlePath /*out parameter*/); + bool umountCryptedBundle(const std::string& id); + void registerListener(const OmiErrorListener &listener, const void* cbParams); + void unregisterListener(); + +private: + void handleVerityFailed(GVariant *parameters); + static void handleDbusEvent(GDBusProxy *proxy, gchar *sender_name, gchar *signal_name_c_str, + GVariant *parameters, gpointer user_data); + + void connectSignal(const char* signalName, GCallback callback); + void disconnectAllSignals(); + void Worker(); + + Omi1* m_interface; + std::pair m_listener; + std::mutex m_listener_mtx; + std::vector m_signalHandles; + std::thread m_loopThread; + std::promise m_initialized; + GMainContext *m_mainContext; + GMainLoop *m_mainLoop; +}; + +} // namespace lginet + +#endif // #ifdef OMI_CLIENT_HPP_ diff --git a/OCIContainer/dbus/omi_dbus_api.c b/OCIContainer/dbus/omi_dbus_api.c new file mode 100644 index 0000000000..26d378c1e9 --- /dev/null +++ b/OCIContainer/dbus/omi_dbus_api.c @@ -0,0 +1,104 @@ +#include "omi_dbus_api.h" + +Omi1 *omi1_proxy_new_for_bus_sync( + GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GError **error) +{ + static const gchar *INTERFACE_NAME = "com.lgi.onemw.omi1"; + + GDBusConnection *connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, cancellable, error); + if (!connection) + { + printf("omi1_proxy_new_for_bus_sync: failed g_bus_get_sync; error: '%s'\n", (*error) ? (*error)->message : "?"); + return NULL; + } + else + { + GDBusProxy *proxy = g_dbus_proxy_new_sync( + connection, + flags, + NULL, // A GDBusInterfaceInfo specifying the minimal interface that proxy conforms to or NULL + name, + object_path, + INTERFACE_NAME, + NULL, // cancellable + error); + if (!proxy) + { + printf("omi1_proxy_new_for_bus_sync: failed g_dbus_proxy_new_sync; error: '%s'\n", (*error) ? (*error)->message : "?"); + g_object_unref(connection); + return NULL; + } + else + { + Omi1 *ret = g_malloc(sizeof(Omi1)); + ret->connection = connection; + ret->proxy = proxy; + return ret; + } + } +} + +/* omi1_call_mount_sync method is almost directly copied from gdbus-codegen generated version + the only change is in first arg to g_dbus_proxy_call_sync */ +gboolean +omi1_call_mount_sync ( + Omi1 *proxy, + const gchar *arg_id, + const gchar *arg_rootfs_path, + const gchar *arg_config_json_path, + gchar **out_container_path, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (proxy->proxy, + "Mount", + g_variant_new ("(sss)", + arg_id, + arg_rootfs_path, + arg_config_json_path), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(s)", + out_container_path); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/* omi1_call_umount_sync method is almost directly copied from gdbus-codegen generated version + the only change is in first arg to g_dbus_proxy_call_sync */ +gboolean +omi1_call_umount_sync ( + Omi1 *proxy, + const gchar *arg_id, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (proxy->proxy, + "Umount", + g_variant_new ("(s)", + arg_id), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "()"); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} diff --git a/OCIContainer/dbus/omi_dbus_api.h b/OCIContainer/dbus/omi_dbus_api.h new file mode 100644 index 0000000000..b3b6a02ab9 --- /dev/null +++ b/OCIContainer/dbus/omi_dbus_api.h @@ -0,0 +1,43 @@ +#ifndef __OMI_DBUS_API_H__ +#define __OMI_DBUS_API_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + GDBusConnection* connection; + GDBusProxy* proxy; +} Omi1; + +Omi1 *omi1_proxy_new_for_bus_sync ( + GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GError **error); + +gboolean omi1_call_mount_sync ( + Omi1 *proxy, + const gchar *arg_id, + const gchar *arg_rootfs_path, + const gchar *arg_config_json_path, + gchar **out_container_path, + GCancellable *cancellable, + GError **error); + +gboolean omi1_call_umount_sync ( + Omi1 *proxy, + const gchar *arg_id, + GCancellable *cancellable, + GError **error); + +#ifdef __cplusplus // extern "C" +} +#endif + +#endif diff --git a/OCIContainer/doc/OCIContainerPlugin.md b/OCIContainer/doc/OCIContainerPlugin.md new file mode 100644 index 0000000000..1dcdcc4c3c --- /dev/null +++ b/OCIContainer/doc/OCIContainerPlugin.md @@ -0,0 +1,590 @@ +The RDK Services documentation is hosted as a static web site. + +**Version: 1.0** + +**Status: :black_circle::black_circle::black_circle:** + +org.rdk.OCIContainer plugin for Thunder framework. + +### Table of Contents + +- [Introduction](#head.Introduction) +- [Description](#head.Description) +- [Configuration](#head.Configuration) +- [Methods](#head.Methods) + + +# Introduction + + +## Scope + +This document describes purpose and functionality of the org.rdk.OCIContainer plugin. It includes detailed specification about its configuration and methods provided. + + +## Case Sensitivity + +All identifiers of the interfaces described in this document are case-sensitive. Thus, unless stated otherwise, all keywords, entities, properties, relations and actions should be treated as such. + + +## Acronyms, Abbreviations and Terms + +The table below provides and overview of acronyms used in this document and their definitions. + +| Acronym | Description | +| :-------- | :-------- | +| API | Application Programming Interface | +| HTTP | Hypertext Transfer Protocol | +| JSON | JavaScript Object Notation; a data interchange format | +| JSON-RPC | A remote procedure call protocol encoded in JSON | + +The table below provides and overview of terms and abbreviations used in this document and their definitions. + +| Term | Description | +| :-------- | :-------- | +| callsign | The name given to an instance of a plugin. One plugin can be instantiated multiple times, but each instance the instance name, callsign, must be unique. | + + +## References + +| Ref ID | Description | +| :-------- | :-------- | +| [HTTP](http://www.w3.org/Protocols) | HTTP specification | +| [JSON-RPC](https://www.jsonrpc.org/specification) | JSON-RPC 2.0 specification | +| [JSON](http://www.json.org/) | JSON specification | +| [Thunder](https://github.com/WebPlatformForEmbedded/Thunder/blob/master/doc/WPE%20-%20API%20-%20WPEFramework.docx) | Thunder API Reference | + + +# Description + +The `OCIContainer` plugin allows for control of OCI containers using the Dobby hypervisor. + +The plugin is designed to be loaded and executed within the Thunder framework. For more information about the framework refer to [[Thunder](#ref.Thunder)]. + + +# Configuration + +The table below lists configuration options of the plugin. + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| callsign | string | Plugin instance name (default: *org.rdk.OCIContainer*) | +| classname | string | Class name: *org.rdk.OCIContainer* | +| locator | string | Library name: *libWPEFrameworkOCIContainer.so* | +| autostart | boolean | Determines if the plugin shall be started automatically along with the framework | + + +# Methods + +The following methods are provided by the org.rdk.OCIContainer plugin: + +OCIContainer interface methods: + +| Method | Description | +| :-------- | :-------- | +| [execCommand](#method.execCommand) | Executes a command inside a running container | +| [getContainerInfo](#method.getContainerInfo) | Gets information about a running container such as CPU, memory, and GPU usage (GPU not supported on Xi6) | +| [getContainerState](#method.getContainerState) | Gets the state of a currently running container | +| [listContainers](#method.listContainers) | Lists all running OCI containers Dobby knows about | +| [pauseContainer](#method.pauseContainer) | Pauses a currently running container | +| [resumeContainer](#method.resumeContainer) | Resumes a previously paused container | +| [startContainer](#method.startContainer) | Starts a new container from an existing OCI bundle | +| [startContainerFromDobbySpec](#method.startContainerFromDobbySpec) | Starts a new container from a legacy Dobby JSON specification | +| [stopContainer](#method.stopContainer) | Stops a currently running container | + + + +## *execCommand method* + +Executes a command inside a running container. The path to the executable must resolve within the container's namespace. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.containerId | string | The ID of a container as returned by `listContainers` | +| params?.options | string | *(optional)* Global options for crun `exec` command | +| params.command | string | Command to execute | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | +| result.error | string | Error information | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "org.rdk.OCIContainer.1.execCommand", + "params": { + "containerId": "com.bskyb.epgui", + "options": "--cwd=PATH", + "command": "command" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *getContainerInfo method* + +Gets information about a running container such as CPU, memory, and GPU usage (GPU not supported on Xi6). + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.containerId | string | The ID of a container as returned by `listContainers` | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.info | object | Information about the specified container | +| result.info.cpu | object | CPU information | +| result.info.cpu.usage | object | The amount of CPU usage | +| result.info.cpu.usage.percpu | array | The usage for each CPU | +| result.info.cpu.usage.percpu[#] | integer | | +| result.info.cpu.usage.total | integer | The combined usage for all CPUs | +| result.info.id | string | The ID of a container as returned by `listContainers` | +| result.info.memory | object | The amount of memory being used by the container | +| result.info.memory.user | object | User memory statistics | +| result.info.memory.user.failcnt | integer | The fail count; the number of times that the usage counter hit its limit | +| result.info.memory.user.limit | integer | The memory limit | +| result.info.memory.user.max | integer | The maximum amount of memory used | +| result.info.memory.user.usage | integer | The current memory being used | +| result.info.pids | array | A list of container process IDs | +| result.info.pids[#] | integer | | +| result.info.state | string | The container state (must be one of the following: *Invalid*, *Starting*, *Running*, *Stopped*, *Paused*) | +| result.info.timestamp | integer | The timestamp for container information | +| result.success | boolean | Whether the request succeeded | +| result.error | string | Error information | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "org.rdk.OCIContainer.1.getContainerInfo", + "params": { + "containerId": "com.bskyb.epgui" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "info": { + "cpu": { + "usage": { + "percpu": [ + 83134464184 + ], + "total": 224025108679 + } + }, + "id": "com.bskyb.epgui", + "memory": { + "user": { + "failcnt": 0, + "limit": 230686720, + "max": 38555648, + "usage": 28655616 + } + }, + "pids": [ + 7644 + ], + "state": "Running", + "timestamp": 6849968158125 + }, + "success": true + } +} +``` + + +## *getContainerState method* + +Gets the state of a currently running container. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.containerId | string | The ID of a container as returned by `listContainers` | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.containerId | string | The ID of a container as returned by `listContainers` | +| result.state | string | The container state (must be one of the following: *Invalid*, *Starting*, *Running*, *Stopped*, *Paused*) | +| result.success | boolean | Whether the request succeeded | +| result.error | string | Error information | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "org.rdk.OCIContainer.1.getContainerState", + "params": { + "containerId": "com.bskyb.epgui" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "containerId": "com.bskyb.epgui", + "state": "Running", + "success": true + } +} +``` + + +## *listContainers method* + +Lists all running OCI containers Dobby knows about. + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.containers | array | A list of running containers | +| result.containers[#] | object | | +| result.containers[#].Descriptor | integer | The container descriptor | +| result.containers[#].Id | string | The container Id | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "org.rdk.OCIContainer.1.listContainers" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "containers": [ + { + "Descriptor": 91, + "Id": "com.bskyb.epgui" + } + ], + "success": true + } +} +``` + + +## *pauseContainer method* + +Pauses a currently running container. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.containerId | string | The ID of a container as returned by `listContainers` | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | +| result.error | string | Error information | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "org.rdk.OCIContainer.1.pauseContainer", + "params": { + "containerId": "com.bskyb.epgui" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *resumeContainer method* + +Resumes a previously paused container. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.containerId | string | The ID of a container as returned by `listContainers` | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | +| result.error | string | Error information | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "org.rdk.OCIContainer.1.resumeContainer", + "params": { + "containerId": "com.bskyb.epgui" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + + +## *startContainer method* + +Starts a new container from an existing OCI bundle. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.containerId | string | The ID of a container as returned by `listContainers` | +| params.bundlePath | string | Path to the OCI bundle containing the rootfs and config to use to create the container | +| params?.command | string | *(optional)* Command to execute | +| params?.westerosSocket | string | *(optional)* Path to a Westeros socket to mount inside the container | +| params?.envvar | array | *(optional)* A list of environment variables to add to the container | +| params?.envvar[#] | string | *(optional)* | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.descriptor | integer | The container descriptor | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "org.rdk.OCIContainer.1.startContainer", + "params": { + "containerId": "com.bskyb.epgui", + "bundlePath": "/containers/myBundle", + "command": "command", + "westerosSocket": "/usr/mySocket", + "envvar": [ + "FOO=BAR" + ] + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "descriptor": 91, + "success": true + } +} +``` + + +## *startContainerFromDobbySpec method* + +Starts a new container from a legacy Dobby JSON specification. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.containerId | string | The ID of a container as returned by `listContainers` | +| params.dobbySpec | string | Dobby specification to use for the container | +| params?.command | string | *(optional)* Command to execute | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.descriptor | integer | The container descriptor | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "org.rdk.OCIContainer.1.startContainerFromDobbySpec", + "params": { + "containerId": "com.bskyb.epgui", + "dobbySpec": "/containers/dobbySpec", + "command": "command" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "descriptor": 91, + "success": true + } +} +``` + + +## *stopContainer method* + +Stops a currently running container. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.containerId | string | The ID of a container as returned by `listContainers` | +| params?.force | boolean | *(optional)* If `true`, force stop the container using the `SIGKILL` signal). Otherwise, use the `SIGTERM` signal. The default value if no value is specified is `false` | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | +| result.error | string | Error information | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "method": "org.rdk.OCIContainer.1.stopContainer", + "params": { + "containerId": "com.bskyb.epgui", + "force": true + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 1234567890, + "result": { + "success": true + } +} +``` + +This file will be removed in a future release. The API markdown files for RDK services are located in the [docs/api](https://github.com/rdkcentral/rdkservices/tree/main/docs/api) folder. Please update your bookmarks accordingly. diff --git a/OpenCDMi/CENCParser.h b/OpenCDMi/CENCParser.h index 58b5eb54fc..8d613e9c78 100644 --- a/OpenCDMi/CENCParser.h +++ b/OpenCDMi/CENCParser.h @@ -21,6 +21,8 @@ #define __CENCPARSER_H #include "Module.h" +#include +#include "Protobuf.h" namespace WPEFramework { namespace Plugin { @@ -46,25 +48,25 @@ namespace Plugin { WIDEVINE = 0x0008 }; - class KeyId : public OCDM::KeyId { + class KeyId : public Exchange::KeyId { public: inline KeyId() - : OCDM::KeyId() + : Exchange::KeyId() , _systems(0) { } inline KeyId(const systemType type, const uint8_t kid[], const uint8_t length) - : OCDM::KeyId(kid, length) + : Exchange::KeyId(kid, length) , _systems(type) { } inline KeyId(const systemType type, const uint32_t a, const uint16_t b, const uint16_t c, const uint8_t d[]) - : OCDM::KeyId(a, b, c, d) + : Exchange::KeyId(a, b, c, d) , _systems(type) { } inline KeyId(const KeyId& copy) - : OCDM::KeyId(copy) + : Exchange::KeyId(copy) , _systems(copy._systems) { } @@ -74,23 +76,23 @@ namespace Plugin { inline KeyId& operator=(const KeyId& rhs) { - OCDM::KeyId::operator=(rhs); + Exchange::KeyId::operator=(rhs); _systems = rhs._systems; return (*this); } public: - inline bool operator==(const OCDM::KeyId& rhs) const + inline bool operator==(const Exchange::KeyId& rhs) const { - return (OCDM::KeyId::operator== (rhs)); + return (Exchange::KeyId::operator== (rhs)); } - inline bool operator!=(const OCDM::KeyId& rhs) const + inline bool operator!=(const Exchange::KeyId& rhs) const { return (!operator==(rhs)); } inline bool operator==(const KeyId& rhs) const { - return (OCDM::KeyId::operator== (rhs)); + return (Exchange::KeyId::operator== (rhs)); } inline bool operator!=(const KeyId& rhs) const { @@ -126,13 +128,13 @@ namespace Plugin { } public: - inline ::OCDM::ISession::KeyStatus Status() const + inline Exchange::ISession::KeyStatus Status() const { - return (_keyIds.size() > 0 ? _keyIds.begin()->Status() : ::OCDM::ISession::StatusPending); + return (_keyIds.size() > 0 ? _keyIds.begin()->Status() : Exchange::ISession::StatusPending); } - inline ::OCDM::ISession::KeyStatus Status(const KeyId& key) const + inline Exchange::ISession::KeyStatus Status(const KeyId& key) const { - ::OCDM::ISession::KeyStatus result(::OCDM::ISession::StatusPending); + Exchange::ISession::KeyStatus result(Exchange::ISession::StatusPending); if (key.IsValid() == true) { std::list::const_iterator index(std::find(_keyIds.begin(), _keyIds.end(), key)); if (index != _keyIds.end()) { @@ -145,7 +147,7 @@ namespace Plugin { { return (Iterator(_keyIds)); } - inline bool HasKeyId(const OCDM::KeyId& keyId) const + inline bool HasKeyId(const Exchange::KeyId& keyId) const { return (std::find(_keyIds.begin(), _keyIds.end(), keyId) != _keyIds.end()); } @@ -161,7 +163,7 @@ namespace Plugin { index->Flag(key.Systems()); } } - inline const KeyId* UpdateKeyStatus(::OCDM::ISession::KeyStatus status, const KeyId& key) + inline const KeyId* UpdateKeyStatus(Exchange::ISession::KeyStatus status, const KeyId& key) { KeyId* entry = nullptr; @@ -256,87 +258,117 @@ namespace Plugin { do { // Check if this is a PSSH box... - uint32_t size = (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]; - if (size == 0) { - TRACE(Trace::Information, (_T("While parsing CENC, found chunk of size 0, are you sure the data is valid? %d\n"), __LINE__)); - break; - } - - if ((size <= static_cast(length - offset)) && (memcmp(&(data[offset + 4]), PSSHeader, 4) == 0)) { - ParsePSSHBox(&(data[offset + 4 + 4]), size - 4 - 4); - } else { - uint32_t XMLSize = (data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24)); - - if (XMLSize <= static_cast(length - offset)) { - - uint16_t stringLength = (data[offset + 8] | (data[offset + 9] << 8)); - if (stringLength <= (XMLSize - 10)) { - - // Seems like it is an XMLBlob, without PSSH header, we have seen that on PlayReady only.. - ParseXMLBox(&(data[offset + 10]), stringLength); - } - - offset += XMLSize; - - } else if ((offset == 0) && (data[0] == '<') && (data[2] == 'W') && (data[4] == 'R') && (data[6] == 'M')) { - ParseXMLBox(data, size); + uint32_t sizeBE = ((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]); + if ((sizeBE <= static_cast(length - offset)) && ((length - offset) >= 4) + && (::memcmp(&(data[offset + 4]), PSSHeader, 4) == 0)) { + TRACE(Trace::Information, (_T("Initdata contains a PSSH box"))); + ParsePSSHBox(&(data[offset + 4 + 4]), (sizeBE - 4 - 4)); + offset += sizeBE; + } else if (offset == 0) { + uint32_t sizeLE = (data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24)); + if ((data[0] == '<') && (data[2] == 'W') && (data[4] == 'R') && (data[6] == 'M')) { + // Playready XML data without PSSH header and withouth Playready Rights Managment Header + TRACE(Trace::Information, (_T("Initdata contains Playready XML data"))); + ParseXMLBox(data, length); offset = length; } else if (std::string(reinterpret_cast(data), length).find(JSONKeyIds) != std::string::npos) { - /* keyids initdata type */ - TRACE(Trace::Information, (_T("Initdata contains clearkey's key ids"))); - + // keyids initdata type + TRACE(Trace::Information, (_T("Initdata contains ClearKey key IDs"))); ParseJSONInitData(reinterpret_cast(data), length); - } else { - TRACE(Trace::Information, (_T("Have no clue what this is!!! %d\n"), __LINE__)); + offset = length; + } else if (sizeLE == length) { + // Seems like it is an XMLBlob, without PSSH header, we have seen that on PlayReady only.. + TRACE(Trace::Information, (_T("Initdata contains Playready PSSH payload"))); + if (ParsePlayReadyPSSHData(&data[offset], length) == true) { + offset = length; + } } + break; + } else { + break; } - offset += size; - } while (offset < length); + + if (offset != length) { + TRACE(Trace::Information, (_T("Have no clue what this is!!!"))); + } } void ParsePSSHBox(const uint8_t data[], const uint16_t length) { - systemType system(COMMON); - const uint8_t* psshData(&(data[KeyId::Length() + 4 /* flags */])); - uint32_t count((psshData[0] << 24) | (psshData[1] << 16) | (psshData[2] << 8) | psshData[3]); - uint16_t stringLength = (data[8] | (data[9] << 8)); - - if (::memcmp(&(data[4]), CommonEncryption, KeyId::Length()) == 0) { - psshData += 4; - TRACE(Trace::Information, (_T("Common detected [%d]\n"), __LINE__)); - } else if (::memcmp(&(data[4]), PlayReady, KeyId::Length()) == 0) { - if (stringLength <= (length - 10)) { - ParseXMLBox(&(psshData[10]), count); - TRACE(Trace::Information, (_T("PlayReady XML detected [%d]\n"), __LINE__)); - count = 0; + const uint16_t PSSH_HEADER_SIZE_V0 = 24; + const uint16_t PSSH_HEADER_SIZE_V1 = 28; + + if (length >= PSSH_HEADER_SIZE_V0) { + systemType system(COMMON); + const uint8_t version(data[0]); + const uint8_t* keyIdData(nullptr); + uint32_t keyIdCount(0); + const uint8_t* psshData(nullptr); + uint32_t psshDataSize(0); + + auto Read32BE = [](const uint8_t ptr[]) -> uint32_t { + return ((ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]); + }; + + if (version == 0) { + psshData = &data[PSSH_HEADER_SIZE_V0]; + psshDataSize = Read32BE(psshData - 4); + if ((psshDataSize + PSSH_HEADER_SIZE_V0) > length) { + psshData = nullptr; + } + } else if (version == 1) { + /* Version 1 inserts raw key IDs before DRM system specific payload. */ + keyIdData = &data[PSSH_HEADER_SIZE_V0]; + keyIdCount = Read32BE(keyIdData - 4); + if (((keyIdCount * KeyId::Length()) + PSSH_HEADER_SIZE_V1) > length) { + keyIdData = nullptr; + } else { + psshData = &data[PSSH_HEADER_SIZE_V1 + (keyIdCount * KeyId::Length())]; + psshDataSize = Read32BE(psshData - 4); + if (psshDataSize + PSSH_HEADER_SIZE_V1 + (keyIdCount * KeyId::Length()) > length) { + psshData = nullptr; + } + } } else { - TRACE(Trace::Information, (_T("PlayReady BIN detected [%d]\n"), __LINE__)); - system = PLAYREADY; - psshData += 4; + TRACE(Trace::Error, (_T("Unsupported PSSH version (%hhu)"), version)); } - } else if (::memcmp(&(data[4]), WideVine, KeyId::Length()) == 0) { - TRACE(Trace::Information, (_T("WideVine detected [%d]\n"), __LINE__)); - system = WIDEVINE; - psshData += 4 + 4 /* God knows what this uint32 means, we just skip it. */; - } else if (::memcmp(&(data[4]), ClearKey, KeyId::Length()) == 0) { - TRACE(Trace::Information, (_T("ClearKey detected [%d]\n"), __LINE__)); - system = CLEARKEY; - psshData += 4; - } else { - TRACE(Trace::Information, (_T("Unknown system: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X.\n"), data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11])); - count = 0; - } - - if (data[0] != 1) { - count /= KeyId::Length(); - } - TRACE(Trace::Information, (_T("Adding %d keys from PSSH box\n"), count)); + if ((keyIdData != nullptr) || (psshData != nullptr)) { + if (::memcmp(&(data[4]), CommonEncryption, 16) == 0) { + TRACE(Trace::Information, (_T("Common encryption detected"))); + } else if (::memcmp(&(data[4]), PlayReady, 16) == 0) { + TRACE(Trace::Information, (_T("PlayReady detected"))); + system = PLAYREADY; + if (psshData != nullptr) { + ParsePlayReadyPSSHData(psshData, psshDataSize); + } + } else if (::memcmp(&(data[4]), WideVine, 16) == 0) { + TRACE(Trace::Information, (_T("Widevine detected"))); + system = WIDEVINE; + if (psshData != nullptr) { + ParseWidevinePSSHData(psshData, psshDataSize); + } + } else if (::memcmp(&(data[4]), ClearKey, 16) == 0) { + TRACE(Trace::Information, (_T("ClearKey detected"))); + system = CLEARKEY; + } else { + TRACE(Trace::Information, (_T("Unknown DRM system: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X"), + data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11])); + } - while (count-- != 0) { - AddKeyId(KeyId(system, psshData, KeyId::Length())); - psshData += KeyId::Length(); + if (keyIdData != nullptr) { + TRACE(Trace::Information, (_T("Adding %d keys from PSSHv1 box"), keyIdCount)); + while (keyIdCount-- != 0) { + AddKeyId(KeyId(system, keyIdData, KeyId::Length())); + keyIdData += KeyId::Length(); + } + } + } else { + TRACE(Trace::Error, (_T("Invalid PSSH data"))); + } + } else { + TRACE(Trace::Error, (_T("Invalid PSSH box"))); } } @@ -413,10 +445,10 @@ namespace Plugin { while ((size > 0) && ((begin = FindInXML(slot, size, "", 6); - uint16_t keyValue = FindInXML(&(slot[begin + 10]), end, "VALUE", 5); + uint16_t keyValue = FindInXML(&(slot[begin + 10]), end, "VALUE", 5); uint16_t keyStart = FindInXML(&(slot[begin + 10 + keyValue + 10]), end - keyValue - 10, "\"", 1) + 2; uint16_t keyLength = FindInXML(&(slot[begin + 10 + keyValue + 10 + keyStart]), end - keyValue - 10 - keyStart - 2, "\"", 1) - 2; - + if (end < (size - begin - 10)) { uint8_t byteArray[32]; @@ -476,6 +508,101 @@ namespace Plugin { } } + bool ParsePlayReadyPSSHData(const uint8_t data[], const uint16_t length) + { + auto Read16LE = [](const uint8_t ptr[]) -> uint16_t { + return (ptr[0] | (ptr[1] << 8)); + }; + auto Read32LE = [](const uint8_t ptr[]) -> uint32_t { + return (ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24)); + }; + + bool result = false; + + uint32_t size = Read32LE(data); + data += 4; + if (size == length) { + uint16_t count = Read16LE(data); + if (count > 0) { + const uint8_t* dataEnd = (data + length); + data += 2; + while ((data + 4) < dataEnd) { + const uint16_t recordType = Read16LE(data); + const uint16_t recordLength = Read16LE(data + 2); + data += 4; + if ((data + recordLength) >= dataEnd) { + break; + } + if (recordType == 1 /* rights management */) { + ParseXMLBox(data, recordLength); + } + data += recordLength; + if (--count == 0) { + break; + } + } + } + result = (count == 0); + } + + return (result); + } + + bool ParseWidevinePSSHData(const uint8_t data[], const uint16_t length) + { + class WidevinePsshPB2 : public Protobuf::Message { + public: + enum class algorithm : Protobuf::UInt32::type { + UNENCRYPTED, + AES_CTR = 1 + }; + + public: + WidevinePsshPB2() + { + Add(1, &Algorithm); + Add(2, &KeyIDs); + Add(3, &Provider); + Add(4, &ContentID); + Add(5, &TrackType); + Add(6, &Policy); + Add(7, &CryptoPeriodIndex); + Add(8, &GroupedLicense); + Add(9, &ProtectionScheme); + Add(10,&CryptoPeriodDuration); + } + + public: + Protobuf::EnumType Algorithm; + Protobuf::RepeatedType KeyIDs; + Protobuf::Utf8String Provider; + Protobuf::Utf8String ContentID; + Protobuf::Utf8String TrackType; + Protobuf::Utf8String Policy; + Protobuf::UInt32 CryptoPeriodIndex; + Protobuf::Bytes GroupedLicense; + Protobuf::UInt32 ProtectionScheme; + Protobuf::UInt32 CryptoPeriodDuration; + }; + + bool result = false; + + WidevinePsshPB2 wvpb2; + if ((wvpb2.FromBuffer(data, length) == true) && (wvpb2.IsValid() == true)) { + if (wvpb2.KeyIDs.IsSet() == true) { + for (auto const& keyID : wvpb2.KeyIDs.Elements()) { + AddKeyId(KeyId(WIDEVINE, keyID.Value().data(), keyID.Value().size())); + } + } else { + TRACE(Trace::Information, (_T("No key IDs specified in Widevine PSSH data"))); + } + + result = true; + } + + return (result); + } + private: std::list _keyIds; }; diff --git a/OpenCDMi/CMakeLists.txt b/OpenCDMi/CMakeLists.txt index 17fc8f3ff6..045ed26f33 100644 --- a/OpenCDMi/CMakeLists.txt +++ b/OpenCDMi/CMakeLists.txt @@ -24,8 +24,9 @@ set(PLUGIN_OPENCDMI_MODE "Local" CACHE STRING "Controls if the plugin should run set(PLUGIN_OCDM_STARTUPORDER "" CACHE STRING "To configure startup order of OCDM plugin") # deprecated/legacy flags support -if(PLUGIN_OPENCDMI_OOP) - unset(PLUGIN_OPENCDMI_MODE CACHE) +if(PLUGIN_OPENCDMI_OOP STREQUAL "false") + set(PLUGIN_OPENCDMI_MODE "Off" CACHE STRING "Process mode" FORCE) + unset(PLUGIN_OPENCDMI_OOP CACHE) elseif(PLUGIN_OPENCDMI_OUTOFPROCESS STREQUAL "false") set(PLUGIN_OPENCDMI_MODE "Off" CACHE STRING "Process mode" FORCE) unset(PLUGIN_OPENCDMI_OUTOFPROCESS CACHE) @@ -34,6 +35,7 @@ endif() find_package(ocdm REQUIRED) find_package(${NAMESPACE}Plugins REQUIRED) find_package(CompileSettingsDebug CONFIG REQUIRED) +find_package(${NAMESPACE}Definitions REQUIRED) add_library(${MODULE_NAME} SHARED OCDM.cpp @@ -41,8 +43,10 @@ add_library(${MODULE_NAME} SHARED Module.cpp) add_library(${PLUGIN_OCDM_IMPLEMENTATION} SHARED + LibertyConfig.cpp CENCParser.cpp FrameworkRPC.cpp + CapsParser.cpp Module.cpp) add_dependencies(${MODULE_NAME} ${PLUGIN_OCDM_IMPLEMENTATION}) @@ -59,15 +63,21 @@ set_target_properties(${PLUGIN_OCDM_IMPLEMENTATION} PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED YES) +target_include_directories(${MODULE_NAME} PRIVATE ../helpers) +target_include_directories(${PLUGIN_OCDM_IMPLEMENTATION} PRIVATE ../helpers) target_link_libraries(${MODULE_NAME} PRIVATE CompileSettingsDebug::CompileSettingsDebug - ${NAMESPACE}Plugins::${NAMESPACE}Plugins) + ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${NAMESPACE}Definitions::${NAMESPACE}Definitions) target_link_libraries(${PLUGIN_OCDM_IMPLEMENTATION} PRIVATE CompileSettingsDebug::CompileSettingsDebug ${NAMESPACE}Plugins::${NAMESPACE}Plugins - ocdm::ocdm) + ${NAMESPACE}Definitions::${NAMESPACE}Definitions + ocdm::ocdm + asconnector + rdkloggers) # Library definition section install(TARGETS ${MODULE_NAME} @@ -84,6 +94,14 @@ else() target_link_libraries(${MODULE_NAME} PRIVATE ${PLUGINS_LIBRARIES} ${OCDM_LIBRARIES}) endif() +find_path(LIBODHERR_INCLUDE_DIR "rdk/libodherr/odherr.h") +if (LIBODHERR_INCLUDE_DIR) + message(STATUS "ODH Error reporting support enabled (path: ${LIBODHERR_INCLUDE_DIR})") + target_compile_definitions(${PLUGIN_OCDM_IMPLEMENTATION} PRIVATE -DHAVE_LIBODHERR_ODHERR_H -DODH_SOURCE_NAME="OCDM") + target_include_directories(${PLUGIN_OCDM_IMPLEMENTATION} PRIVATE ${LIBODHERR_INCLUDE_DIR}) + target_link_libraries(${PLUGIN_OCDM_IMPLEMENTATION} PRIVATE odherr jansson) +endif(LIBODHERR_INCLUDE_DIR) + # Library installation section string(TOLOWER ${NAMESPACE} STORAGENAME) install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/${STORAGENAME}/plugins) diff --git a/OpenCDMi/CapsParser.cpp b/OpenCDMi/CapsParser.cpp new file mode 100644 index 0000000000..c0fd5ad7bf --- /dev/null +++ b/OpenCDMi/CapsParser.cpp @@ -0,0 +1,207 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "CapsParser.h" + +#define START_CHAR ")" +#define END_CHAR "," + + +#define LOG(level, ...) DebugLogging(level, __FUNCTION__, __LINE__, __VA_ARGS__) +static bool s_bLogTrace = false; + +typedef enum eLogLevel_ +{ + eTrace, + eWarning, + eError +} eLogLevel; + +void DebugLogging(eLogLevel level, const char* function, int line, const char * format, ...) +{ +#define LOG_MESSAGE_SIZE 4096 + + if(!s_bLogTrace && level < eWarning) { + // Do not log + return; + } + + char logMessage[LOG_MESSAGE_SIZE]; + // Generate the log string + va_list ap; + va_start(ap, format); + vsnprintf(logMessage, LOG_MESSAGE_SIZE, format, ap); + va_end(ap); + + FILE* fpOut = stdout; + if(level == eError) { + fpOut = stderr; + } + + // printf for now. + fprintf(fpOut, "CapParser: %s(%d) : %s", function, line, logMessage); + fflush(fpOut); + return; +} + +//#define ALWAYS_LOG 1 +static void _SetLogLevelTrace(bool bTrace) +{ +#ifndef ALWAYS_LOG + if(s_bLogTrace != bTrace) { + LOG(eError, "Setting s_bLogTrace to %d\n", bTrace); + s_bLogTrace = bTrace; + } +#else + s_bLogTrace = true; +#endif +} +static inline bool _GetLogLevelTrace() +{ + return s_bLogTrace; +} + +namespace WPEFramework { + namespace Plugin { + + CapsParser::CapsParser() + : _lastHash(0) + , _mediaTag("original-media-type") + , _widthTag("width") + , _heightTag("height") + , _mediaType(CDMi::Unknown) + , _width(0) + , _height(0) + { + char* envTraceLog = getenv("GSTCAPS_ENABLE_TRACE_LOGGING"); + if(envTraceLog && strcasecmp(envTraceLog, "true") == 0) { + _SetLogLevelTrace(true); + LOG(eWarning, "using GSTCAPS_ENABLE_TRACE_LOGGING set TRACE Logging to %d\n", _GetLogLevelTrace()); + } + else { + _SetLogLevelTrace(false); + } + } + + CapsParser::~CapsParser() + { + } + + void CapsParser::Parse(const uint8_t* info, uint16_t infoLength) /* override */ + { + LOG(eTrace, "Got a new info string size %d (%p)\n", infoLength, info); + if(infoLength > 0) { + ::string infoStr((char*)info, (size_t)infoLength); + + std::hash<::string> hash_fn; + size_t info_hash = hash_fn(infoStr); + if(_lastHash != info_hash) { + LOG(eTrace, "Got a new info string %s hash = %ld\n", infoStr.c_str(), info_hash); + _lastHash = info_hash; + ::string substring =infoStr.substr(0,5); + + // Parse the data + ::string result = FindMarker(infoStr, _mediaTag); + if(!result.empty()) { + SetMediaType(result); + } + else if(substring.compare("audio")==0||substring.compare("video")== 0){ + SetMediaType(substring); + } + else { + LOG(eError, "No result for media type\n"); + } + if(_mediaType == CDMi::Video) { + result = FindMarker(infoStr, _widthTag); + if(!result.empty()) { + SetWidth(result); + } + else { + LOG(eError, "No result for width\n"); + } + result = FindMarker(infoStr, _heightTag); + if(!result.empty()) { + SetHeight(result); + } + else { + LOG(eError, "No result for height\n"); + } + } + else { + // Audio + _width = 0; + _height = 0; + } + } + } + } + + void CapsParser::SetMediaType(::string& media) + { + if(media.find("video") != ::string::npos) { + _mediaType = CDMi::Video; + } + else if(media.find("audio") != ::string::npos) { + _mediaType = CDMi::Audio; + } + else { + LOG(eError, "Found and unknown media type %s\n", media.c_str()); + _mediaType = CDMi::Unknown; + } + } + + void CapsParser::SetWidth(::string& width) + { + LOG(eTrace, "Setting width to %s\n", width.length() > 0 ? width.c_str() : "NULL"); + if(width.length() > 0) { + _width = (uint16_t)stoi(width, NULL, 10); + } + } + + void CapsParser::SetHeight(::string& height) + { + LOG(eTrace, "Setting height to %s\n", height.length() > 0 ? height.c_str() : "NULL"); + if(height.length() > 0) { + _height = (uint16_t)stoi(height, NULL, 10); + } + } + + ::string CapsParser::FindMarker(::string& data, ::string& tag) const + { + ::string retVal; + + size_t found = data.find(tag); + LOG(eTrace, "Found tag <%s> in <%s> at location %d\n", + tag.c_str(), data.c_str(), (int)found); + if(found != ::string::npos) { + // Found the marker + // Find the end of the gst caps type identifier + size_t start = data.find(START_CHAR, found) + 1; // step over the ")" + size_t end = data.find(END_CHAR, start); + if(end == ::string::npos) { + // Went past the end of the string + end = data.length(); + } + retVal = data.substr(start, end - start); + LOG(eTrace, "Found substr <%s>\n", retVal.c_str()); + } + return retVal; + } + } +} diff --git a/OpenCDMi/CapsParser.h b/OpenCDMi/CapsParser.h new file mode 100644 index 0000000000..9ad148dc86 --- /dev/null +++ b/OpenCDMi/CapsParser.h @@ -0,0 +1,63 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "Module.h" + +#include + +namespace WPEFramework { + namespace Plugin { + + class CapsParser : public CDMi::ICapsParser { + public: + CapsParser(const CapsParser&) = delete; + CapsParser& operator= (const CapsParser&) = delete; + + CapsParser(); + ~CapsParser() override; + + public: + void Parse(const uint8_t* info, uint16_t infoLength) override; + + const uint16_t GetHeight() const { return _height; } + const uint16_t GetWidth() const { return _width; } + + virtual const CDMi::MediaType GetMediaType() const { return _mediaType; } + + private: + virtual ::string FindMarker(::string& data, ::string& tag) const; + virtual void SetMediaType(::string& media); + virtual void SetWidth(::string& width); + virtual void SetHeight(::string& height); + private: + size_t _lastHash; + ::string _mediaTag; + ::string _widthTag; + ::string _heightTag; + CDMi::MediaType _mediaType; + uint16_t _width; + uint16_t _height; + }; + } +} diff --git a/OpenCDMi/FrameworkRPC.cpp b/OpenCDMi/FrameworkRPC.cpp index bd59fd5615..916ed38a57 100644 --- a/OpenCDMi/FrameworkRPC.cpp +++ b/OpenCDMi/FrameworkRPC.cpp @@ -20,14 +20,25 @@ #include #include #include +#include +#include +#include #include "Module.h" #include "CENCParser.h" +#include "CapsParser.h" +#include "LibertyConfig.h" // Get in the definitions required for access to the sepcific // DRM engines. #include #include +#include + +#define INITIALIZATION_RETRY_SLEEP_MS 100 // delay in [ms] after getting CDMi_BUSY_CANNOT_INITIALIZE and retry +#define INITIALIZATION_MAX_RETRY_COUNTER 50 // number of repetitions in retrying procedure + +#include "ReportErrors.h" extern "C" { @@ -40,11 +51,11 @@ namespace Plugin { static void TrimWs(const std::string& str, size_t& start, size_t& end) { - while(std::isspace(str[start]) && start < end) { + while(start < end && std::isspace(str[start])) { ++start; } - while(std::isspace(str[end - 1]) && end - 1 > 0) { + while(end > 1 && std::isspace(str[end - 1])) { --end; } } @@ -109,9 +120,10 @@ namespace Plugin { public: ExternalAccess( const Core::NodeId& source, - ::OCDM::IAccessorOCDM* parentInterface, + Exchange::IAccessorOCDM* parentInterface, + const string& proxyStubPath, const Core::ProxyType & engine) - : RPC::Communicator(source, _T(""), Core::ProxyType(engine)) + : RPC::Communicator(source, proxyStubPath, Core::ProxyType(engine)) , _parentInterface(parentInterface) { engine->Announcements(Announcement()); @@ -132,7 +144,7 @@ namespace Plugin { void* result = nullptr; // Currently we only support version 1 of the IRPCLink :-) - if (((versionId == 1) || (versionId == static_cast(~0))) && ((interfaceId == ::OCDM::IAccessorOCDM::ID) || (interfaceId == Core::IUnknown::ID))) { + if (((versionId == 1) || (versionId == static_cast(~0))) && ((interfaceId == Exchange::IAccessorOCDM::ID) || (interfaceId == Core::IUnknown::ID))) { // Reference count our parent _parentInterface->AddRef(); TRACE(Trace::Information, ("OCDM interface acquired => %p", this)); @@ -143,10 +155,10 @@ namespace Plugin { } private: - ::OCDM::IAccessorOCDM* _parentInterface; + Exchange::IAccessorOCDM* _parentInterface; }; - class AccessorOCDM : public ::OCDM::IAccessorOCDM { + class AccessorOCDM : public Exchange::IAccessorOCDM { private: AccessorOCDM() = delete; AccessorOCDM(const AccessorOCDM&) = delete; @@ -232,34 +244,115 @@ namespace Plugin { uint16_t _occupation; }; + // Purpose of ContextImplementation is initialization and de-initialization of the DRM system. + // Also in case of client crash proper de-initialization is performed. + class ContextImplementation: public Exchange::IOCDMContext { + private: + ContextImplementation() = delete; + ContextImplementation(const ContextImplementation&) = delete; + ContextImplementation& operator=(const ContextImplementation&) = delete; + public: + ContextImplementation(AccessorOCDM* parent, const std::string& keySystem, bool cleanOnDestroy) + : _parent(*parent) + , _keySystem(keySystem) + , _cleanOnDestroy(cleanOnDestroy) + { + ASSERT(parent != nullptr); + + TRACE_L1("Constructed ContextImplementation for keySystem %s", _keySystem.c_str()); + } + virtual ~ContextImplementation() + { + TRACE_L1("Destructing ContextImplementation %p for keySystem %s", this, _keySystem.c_str()); + _parent.Remove(this, _keySystem, _cleanOnDestroy); + TRACE_L1("Destructed ContextImplementation for keySystem %s ", _keySystem.c_str()); + } + void dummy() override + { + //intentionally left empty + } + const std::string& GetKeySystem() + { + return _keySystem; + } + + BEGIN_INTERFACE_MAP(ContextImplementation) + INTERFACE_ENTRY(Core::IUnknown) + END_INTERFACE_MAP + + private: + AccessorOCDM& _parent; + std::string _keySystem; + bool _cleanOnDestroy; + }; + // IMediaKeys defines the MediaKeys interface. - class SessionImplementation : public ::OCDM::ISession, public ::OCDM::ISessionExt { + class SessionImplementation : public Exchange::ISession, public Exchange::ISessionExt { private: SessionImplementation() = delete; SessionImplementation(const SessionImplementation&) = delete; SessionImplementation& operator=(const SessionImplementation&) = delete; - class DataExchange : public ::OCDM::DataExchange, public Core::Thread { + class MediaStreamProperties : public CDMi::IStreamProperties { + public: + MediaStreamProperties() = delete; + MediaStreamProperties(const MediaStreamProperties&) = delete; + MediaStreamProperties& operator=(const MediaStreamProperties&) = delete; + MediaStreamProperties(uint16_t height, uint16_t width, CDMi::MediaType type, uint8_t initLength = 0) + : _height(height) + , _width(width) + , _type(type) + , _initLength(initLength) + { + } + + uint16_t GetHeight() const override + { + return (_height); + } + uint16_t GetWidth() const override + { + return (_width); + } + CDMi::MediaType GetMediaType() const override + { + return (_type); + } + uint8_t InitLength() const override + { + return (_initLength); + } + + private: + uint16_t _height; + uint16_t _width; + CDMi::MediaType _type; + uint8_t _initLength; + }; + + class DataExchange : public Exchange::DataExchange, public Core::Thread { private: DataExchange() = delete; DataExchange(const DataExchange&) = delete; DataExchange& operator=(const DataExchange&) = delete; public: - DataExchange(CDMi::IMediaKeySession* mediaKeys, const string& name, const uint32_t defaultSize) - : ::OCDM::DataExchange(name, defaultSize) + DataExchange(CDMi::IMediaKeySession* mediaKeys, const string& name, const uint32_t defaultSize, CDMi::ICapsParser* parser) + : Exchange::DataExchange(name, defaultSize) , Core::Thread(Core::Thread::DefaultStackSize(), _T("DRMSessionThread")) , _mediaKeys(mediaKeys) , _mediaKeysExt(dynamic_cast(mediaKeys)) , _sessionKey(nullptr) , _sessionKeyLength(0) + , _parser(parser) { + ASSERT(parser != nullptr); Core::Thread::Run(); TRACE(Trace::Information, (_T("Constructing buffer server side: %p - %s"), this, name.c_str())); } ~DataExchange() { - TRACE(Trace::Information, (_T("Destructing buffer server side: %p - %s"), this, ::OCDM::DataExchange::Name().c_str())); + TRACE(Trace::Information, (_T("Destructing buffer server side: %p - %s"), this, Exchange::DataExchange::Name().c_str())); // Make sure the thread reaches a HALT.. We are done. Core::Thread::Stop(); @@ -272,6 +365,8 @@ namespace Plugin { private: virtual uint32_t Worker() override { + size_t clearContentInfoTraceCount = 0; + size_t clearContentInfoTraceTotalCount = 0; while (IsRunning() == true) { @@ -281,31 +376,49 @@ namespace Plugin { RequestConsume(Core::infinite); if (IsRunning() == true) { - uint8_t keyIdLength = 0; - const uint8_t* keyIdData = KeyId(keyIdLength); + uint8_t *payloadBuffer = Buffer(); + + uint16_t sampleCount = SampleLength(); + CDMi::SampleInfo sampleInfo[sampleCount]; + Samples(&sampleInfo[0], sampleCount); + + uint16_t width = 0, height = 0; + uint8_t type = 0; + MediaProperties(height, width, type); + const MediaStreamProperties streamProperties(height, width, static_cast(type)); + + _parser->Parse(StreamInfo(), StreamInfoLength()); + _mediaKeys->SetCapsParser(_parser); int cr = _mediaKeys->Decrypt( - _sessionKey, - _sessionKeyLength, - nullptr, //subsamples - 0, //number of subsamples - IVKey(), - IVKeyLength(), - Buffer(), - BytesWritten(), - &clearContentSize, - &clearContent, - keyIdLength, - keyIdData, - InitWithLast15()); + payloadBuffer, + BytesWritten(), + &clearContent, + &clearContentSize, + sampleInfo, + sampleCount, + dynamic_cast(&streamProperties)); + if ((cr == 0) && (clearContentSize != 0)) { if (clearContentSize != BytesWritten()) { - TRACE(Trace::Information, (_T("Returned clear sample size (%d) differs from encrypted buffer size (%d)"), clearContentSize, BytesWritten())); + if (++clearContentInfoTraceCount < 3) TRACE(Trace::Information, (_T("Returned clear sample size (%d) differs from encrypted buffer size (%d)"), clearContentSize, BytesWritten())); + clearContentInfoTraceTotalCount++; Size(clearContentSize); } - - // Adjust the buffer on our sied (this process) on what we will write back - SetBuffer(0, clearContentSize, clearContent); + else { + // above info trace should be printed 2 times in a row + // reset it in case it was not printed more than 10 times for lifecycle + // this should print 2 times in a row, but no more than 10 times overall + if (clearContentInfoTraceTotalCount < 10) clearContentInfoTraceCount = 0; + } + + if(payloadBuffer != clearContent) { + // This wasn't a case of in-place decryption. So, make sure the decrypted buffer is copied to memory mapped file and released + // Adjust the buffer on our side (this process) on what we will write back + SetBuffer(0, clearContentSize, clearContent); + //Lets release the clear content buffer + _mediaKeys->ReleaseClearContent(nullptr, 0,clearContentSize,clearContent); + } } // Store the status we have for the other side. @@ -324,6 +437,7 @@ namespace Plugin { CDMi::IMediaKeySessionExt* _mediaKeysExt; uint8_t* _sessionKey; uint32_t _sessionKeyLength; + CDMi::ICapsParser* _parser; }; // IMediaKeys defines the MediaKeys interface. @@ -334,7 +448,7 @@ namespace Plugin { Sink& operator=(const Sink&) = delete; public: - Sink(SessionImplementation* parent, ::OCDM::ISession::ICallback* callback) + Sink(SessionImplementation* parent, Exchange::ISession::ICallback* callback) : _parent(*parent) , _callback(callback) { @@ -372,7 +486,7 @@ namespace Plugin { TRACE(Trace::Information, ("OnKeyError(%d,%s)", f_nError, errorMessage)); if (_callback != nullptr) { std::string message(errorMessage, strlen(errorMessage)); - _callback->OnError(f_nError, (::OCDM::OCDM_RESULT)f_crSysError, message); + _callback->OnError(f_nError, (Exchange::OCDM_RESULT)f_crSysError, message); } } @@ -381,27 +495,27 @@ namespace Plugin { { ASSERT (buffer != nullptr); - ::OCDM::ISession::KeyStatus key; + Exchange::ISession::KeyStatus key; CommonEncryptionData::KeyId keyId(CommonEncryptionData::COMMON, buffer, length); TRACE(Trace::Information, ("OnKeyStatusUpdate(%s)", keyMessage)); if (::strcmp(keyMessage, "KeyUsable") == 0) - key = ::OCDM::ISession::Usable; + key = Exchange::ISession::Usable; else if (::strcmp(keyMessage, "KeyReleased") == 0) - key = ::OCDM::ISession::Released; + key = Exchange::ISession::Released; else if (::strcmp(keyMessage, "KeyExpired") == 0) - key = ::OCDM::ISession::Expired; + key = Exchange::ISession::Expired; else if (::strcmp(keyMessage, "KeyOutputRestricted") == 0) - key = ::OCDM::ISession::OutputRestricted; + key = Exchange::ISession::OutputRestricted; else if (::strcmp(keyMessage, "KeyOutputDownscaled") == 0) - key = ::OCDM::ISession::OutputDownscaled; + key = Exchange::ISession::OutputDownscaled; else if (::strcmp(keyMessage, "SEC_RESULT_HW_FAILURE") == 0) - key = ::OCDM::ISession::HWError; + key = Exchange::ISession::HWError; else if (::strcmp(keyMessage, "KeyOutputRestrictedHDCP22") == 0) - key = ::OCDM::ISession::OutputRestrictedHDCP22; + key = Exchange::ISession::OutputRestrictedHDCP22; else - key = ::OCDM::ISession::InternalError; + key = Exchange::ISession::InternalError; const CommonEncryptionData::KeyId* updated = _parent._cencData.UpdateKeyStatus(key, keyId); @@ -411,7 +525,7 @@ namespace Plugin { _callback->OnKeyStatusUpdate(updated->Id(), updated->Length(), key); } } - void Revoke(::OCDM::ISession::ICallback* callback) + void Revoke(Exchange::ISession::ICallback* callback) { if ((_callback != nullptr) && (_callback == callback)) { _callback->Release(); @@ -426,9 +540,14 @@ namespace Plugin { _callback->OnKeyStatusesUpdated(); } + void OnBindLicense(uint32_t callbackType, const uint8_t *licenseMessage, const uint8_t licenseMessageLength) override + { + _callback->OnBindLicense(callbackType, licenseMessage, licenseMessageLength); + } + private: SessionImplementation& _parent; - ::OCDM::ISession::ICallback* _callback; + Exchange::ISession::ICallback* _callback; }; public: @@ -439,7 +558,7 @@ namespace Plugin { AccessorOCDM* parent, const std::string keySystem, CDMi::IMediaKeySession* mediaKeySession, - ::OCDM::ISession::ICallback* callback, + Exchange::ISession::ICallback* callback, const CommonEncryptionData* sessionData) : _parent(*parent) , _refCount(1) @@ -450,6 +569,7 @@ namespace Plugin { , _sink(this, callback) , _buffer(nullptr) , _cencData(*sessionData) + , _parser() { ASSERT(parent != nullptr); ASSERT(sessionData != nullptr); @@ -464,7 +584,7 @@ namespace Plugin { AccessorOCDM* parent, const std::string keySystem, CDMi::IMediaKeySessionExt* mediaKeySession, - ::OCDM::ISession::ICallback* callback, + Exchange::ISession::ICallback* callback, const CommonEncryptionData* sessionData) : _parent(*parent) , _refCount(1) @@ -486,6 +606,8 @@ namespace Plugin { TRACE(Trace::Information, (_T("Constructed the Session Server side: %p"), this)); _mediaKeySession->Run(&_sink); TRACE(Trace::Information, (_T("Constructed the Session Server side: %p"), this)); + + _mediaKeySession->SetCapsParser(&_parser); } #ifdef __WINDOWS__ #pragma warning(default : 4355) @@ -493,6 +615,7 @@ namespace Plugin { virtual ~SessionImplementation() { + _mediaKeySession->SetCapsParser(nullptr); TRACE(Trace::Information, (_T("Destructing the Session Server side: %p"), this)); // this needs to be done in a thread safe way. Leave it up to @@ -510,44 +633,64 @@ namespace Plugin { { return ((keySystem == _keySystem) && (_cencData.IsSupported(keyIds) == true)); } - inline bool HasKeyId(const OCDM::KeyId& keyId) const + inline bool HasKeyId(const Exchange::KeyId& keyId) const { return (_cencData.HasKeyId(keyId)); } - virtual std::string SessionId() const override + std::string SessionId() const override { return (_sessionId); } - virtual std::string Metadata() const override + std::string Metadata() const override { return _mediaKeySession->GetMetadata(); } - virtual ::OCDM::ISession::KeyStatus Status() const override + virtual Exchange::ISession::KeyStatus Status() const override { return (_cencData.Status()); } - ::OCDM::ISession::KeyStatus Status(const uint8_t keyId[], const uint8_t length) const override + Exchange::OCDM_RESULT Metricdata(uint32_t& bufferSize, uint8_t buffer[]) const override { + Exchange::OCDM_RESULT result = Exchange::OCDM_INTERFACE_NOT_IMPLEMENTED; + + CDMi::IMediaSessionMetrics* extension = dynamic_cast(_mediaKeySession); + + if (extension != nullptr) { + result = static_cast(extension->Metrics(bufferSize, buffer)); + } + + return(result); + } + + Exchange::ISession::KeyStatus Status(const uint8_t keyId[], const uint8_t length) const override { return (_cencData.Status(CommonEncryptionData::KeyId(static_cast(0), keyId, length))); } - ::OCDM::OCDM_RESULT CreateSessionBuffer(std::string& bufferID) override { + Exchange::OCDM_RESULT CreateSessionBuffer(std::string& bufferID) override { - ::OCDM::OCDM_RESULT result = ::OCDM::OCDM_SUCCESS; + Exchange::OCDM_RESULT result = Exchange::OCDM_SUCCESS; _adminLock.Lock(); if( _buffer == nullptr ) { if (_parent._administrator.AquireBuffer(bufferID) == true) { - _buffer = new DataExchange(_mediaKeySession, bufferID, _parent.DefaultSize()); + _buffer = new DataExchange(_mediaKeySession, bufferID, _parent.DefaultSize(), &_parser); _adminLock.Unlock(); + + ASSERT(_buffer != nullptr); + + if(_buffer->IsValid() == false){ + TRACE(Trace::Fatal, ("Could not open session buffer %s", BufferId().c_str())); + } + + TRACE(Trace::Information, ("Server::Session::CreateSessionBuffer(%s,%s,%s) => %p", _keySystem.c_str(), _sessionId.c_str(), BufferId().c_str(), this)); } else { _adminLock.Unlock(); - result = ::OCDM::OCDM_INVALID_DECRYPT_BUFFER; + result = Exchange::OCDM_INVALID_DECRYPT_BUFFER; bufferID.clear(); TRACE(Trace::Error, ("Failed to create buffer for Server::Session::CreateSessionBuffer(%s,%s) => %p", _keySystem.c_str(), _sessionId.c_str(), this)); } @@ -555,108 +698,124 @@ namespace Plugin { _adminLock.Unlock(); TRACE(Trace::Information, ("Buffer already created Server::Session::CreateSessionBuffer(%s,%s,%s) => %p", _keySystem.c_str(), _sessionId.c_str(), BufferId().c_str(), this)); bufferID = _buffer->Name(); - result = ::OCDM::OCDM_S_FALSE; + result = Exchange::OCDM_S_FALSE; } return result; } - virtual std::string BufferId() const override + std::string BufferId() const override { std::string bufferid; _adminLock.Lock(); if( _buffer != nullptr ) { _adminLock.Unlock(); bufferid = _buffer->Name(); + } else { + _adminLock.Unlock(); } return bufferid; } - virtual std::string BufferIdExt() const override + std::string BufferIdExt() const override { return BufferId(); } // Loads the data stored for the specified session into the cdm object - virtual ::OCDM::OCDM_RESULT Load() override + virtual Exchange::OCDM_RESULT Load() override { TRACE(Trace::Information, ("Load()")); - return (::OCDM::OCDM_RESULT)(_mediaKeySession->Load()); + return (Exchange::OCDM_RESULT)(_mediaKeySession->Load()); } // Process a key message response. - virtual void Update(const uint8_t* keyMessage, const uint16_t keyLength) override + void Update(const uint8_t* keyMessage, const uint16_t keyLength) override { TRACE(Trace::Information, ("Update(%d)", keyLength)); return (_mediaKeySession->Update(keyMessage, keyLength)); } //Removes all license(s) and key(s) associated with the session - virtual ::OCDM::OCDM_RESULT Remove() override + virtual Exchange::OCDM_RESULT Remove() override { TRACE(Trace::Information, ("Remove()")); - return (::OCDM::OCDM_RESULT)(_mediaKeySession->Remove()); + return (Exchange::OCDM_RESULT)(_mediaKeySession->Remove()); } //We are done with the Session, close what we can.. - virtual void Close() override + void Close() override { TRACE(Trace::Information, ("Close()")); _mediaKeySession->Close(); } - virtual void ResetOutputProtection() override { + void ResetOutputProtection() override { TRACE(Trace::Information, (_T("ResetOutputProtection! %p"), this)); _mediaKeySession->ResetOutputProtection(); } - virtual void Revoke(OCDM::ISession::ICallback* callback) override + virtual void SetParameter(const std::string& name, const std::string& value) override { + TRACE(Trace::Information, (_T("SetParameter! %p"), this)); + _mediaKeySession->SetParameter(name, value); + } + + virtual void Revoke(Exchange::ISession::ICallback* callback) override { _sink.Revoke(callback); } - virtual uint32_t SessionIdExt() const override + uint32_t SessionIdExt() const override { return _mediaKeySessionExt->GetSessionIdExt(); } - virtual OCDM::OCDM_RESULT SetDrmHeader(const uint8_t drmHeader[], uint32_t drmHeaderLength) override + Exchange::OCDM_RESULT SetDrmHeader(const uint8_t drmHeader[], uint16_t drmHeaderLength) override + { + return (Exchange::OCDM_RESULT)_mediaKeySessionExt->SetDrmHeader(drmHeader, drmHeaderLength); + } + + Exchange::OCDM_RESULT GetChallengeDataExt(uint8_t* challenge, uint16_t& challengeSize, uint32_t isLDL, uint8_t* url, uint16_t& urlSize) override { - return (OCDM::OCDM_RESULT)_mediaKeySessionExt->SetDrmHeader(drmHeader, drmHeaderLength); + uint32_t resultSize = challengeSize; + uint32_t urlBufferSize = urlSize; + Exchange::OCDM_RESULT outcome = static_cast(_mediaKeySessionExt->GetChallengeDataExt(challenge, resultSize, isLDL, url, urlBufferSize)); + challengeSize = (resultSize & 0xFFFF); + urlSize = (urlBufferSize & 0xFFFF); + return (outcome); } - virtual OCDM::OCDM_RESULT GetChallengeDataExt(uint8_t* challenge, uint32_t& challengeSize, uint32_t isLDL) override + Exchange::OCDM_RESULT CancelChallengeDataExt() override { - return (OCDM::OCDM_RESULT)_mediaKeySessionExt->GetChallengeDataExt(challenge, challengeSize, isLDL); + return (Exchange::OCDM_RESULT)_mediaKeySessionExt->CancelChallengeDataExt(); } - virtual OCDM::OCDM_RESULT CancelChallengeDataExt() override + Exchange::OCDM_RESULT StoreLicenseData(const uint8_t licenseData[], uint16_t licenseDataSize, unsigned char* secureStopId) override { - return (OCDM::OCDM_RESULT)_mediaKeySessionExt->CancelChallengeDataExt(); + return (Exchange::OCDM_RESULT)_mediaKeySessionExt->StoreLicenseData(licenseData, licenseDataSize, secureStopId); } - virtual OCDM::OCDM_RESULT StoreLicenseData(const uint8_t licenseData[], uint32_t licenseDataSize, unsigned char* secureStopId) override + Exchange::OCDM_RESULT SelectKeyId(const uint8_t keyLength, const uint8_t keyId[]) override { - return (OCDM::OCDM_RESULT)_mediaKeySessionExt->StoreLicenseData(licenseData, licenseDataSize, secureStopId); + return (Exchange::OCDM_RESULT)_mediaKeySessionExt->SelectKeyId(keyLength, keyId); } - virtual OCDM::OCDM_RESULT SelectKeyId(const uint8_t keyLength, const uint8_t keyId[]) override + Exchange::OCDM_RESULT CleanDecryptContext() override { - return (OCDM::OCDM_RESULT)_mediaKeySessionExt->SelectKeyId(keyLength, keyId); + return (Exchange::OCDM_RESULT)_mediaKeySessionExt->CleanDecryptContext(); } - virtual OCDM::OCDM_RESULT CleanDecryptContext() override + std::string KeySystem() const { - return (OCDM::OCDM_RESULT)_mediaKeySessionExt->CleanDecryptContext(); + return _keySystem; } BEGIN_INTERFACE_MAP(Session) - INTERFACE_ENTRY(::OCDM::ISession) - INTERFACE_RELAY(::OCDM::ISessionExt, _mediaKeySessionExt) + INTERFACE_ENTRY(Exchange::ISession) + INTERFACE_RELAY(Exchange::ISessionExt, _mediaKeySessionExt) END_INTERFACE_MAP - private: private: AccessorOCDM& _parent; mutable Core::CriticalSection _adminLock; @@ -668,6 +827,7 @@ namespace Plugin { Core::Sink _sink; DataExchange* _buffer; CommonEncryptionData _cencData; + CapsParser _parser; }; public: @@ -680,42 +840,133 @@ namespace Plugin { { ASSERT(parent != nullptr); } - virtual ~AccessorOCDM() + ~AccessorOCDM() override { TRACE(Trace::Information, (_T("Released the AccessorOCDM server side [%d]"), __LINE__)); } public: - virtual bool IsTypeSupported( + bool IsTypeSupported( const std::string& keySystem, const std::string& mimeType) const override { - return (_parent.IsTypeSupported(keySystem, mimeType) ? 0 : 1); + return (_parent.IsTypeSupported(keySystem, mimeType) ? true : false); } - virtual OCDM::OCDM_RESULT Metadata( + Exchange::OCDM_RESULT Metadata( const std::string& keySystem, std::string& metadata) const override { - OCDM::OCDM_RESULT result = OCDM::OCDM_KEYSYSTEM_NOT_SUPPORTED; + Exchange::OCDM_RESULT result = Exchange::OCDM_KEYSYSTEM_NOT_SUPPORTED; metadata.clear(); CDMi::IMediaKeys* system = _parent.KeySystem(keySystem); if (system != nullptr) { metadata = system->GetMetadata(); - result = OCDM::OCDM_SUCCESS; + result = Exchange::OCDM_SUCCESS; } return result; } + Exchange::OCDM_RESULT Metricdata(const string& keySystem, uint32_t& bufferSize, uint8_t buffer[]) const override { + Exchange::OCDM_RESULT result = Exchange::OCDM_KEYSYSTEM_NOT_SUPPORTED; + + CDMi::IMediaKeys* system = _parent.KeySystem(keySystem); + if (system != nullptr) { + CDMi::IMediaSystemMetrics* extension = dynamic_cast(system); + if (extension != nullptr) { + result = static_cast(extension->Metrics(bufferSize, buffer)); + } + else { + result = Exchange::OCDM_INTERFACE_NOT_IMPLEMENTED; + } + } + + return(result); + } + uint32_t DefaultSize() const { return _defaultSize; } + Exchange::OCDM_RESULT CreateContext(const string& keySystem, bool cleanOnDestroy, Exchange::IOCDMContext*& context) override + { + Exchange::OCDM_RESULT result = Exchange::OCDM_RESULT::OCDM_S_FALSE; + CDMi::IMediaKeys *system = _parent.KeySystem(keySystem); + context = nullptr; + TRACE_L1("creating context for: %s", keySystem.c_str()); + + if(_parent.widevineAwaitingConfig) { + LockGuard lock(_parent.widevineInitMutex); + if (_parent.widevineDesignators.count(keySystem) != 0) { + SYSLOG(Logging::Startup, (_T("WideVine initialization: apply the config"))); + auto factory = _parent._systemToFactory.find(keySystem); + _parent.widevineInitialConfig = Lgi::updateWidevineConfig(_parent.widevineInitialConfig, _parent._asConfig); + factory->second.Factory->Initialize(_parent._shell, _parent.widevineInitialConfig); + _parent.widevineAwaitingConfig = false; + } + } + + bool hadInitializationError = false; + if (system != nullptr) { + result = Exchange::OCDM_RESULT::OCDM_SUCCESS; + CDMi::IMediaKeysExt* systemExt = dynamic_cast(_parent.KeySystem(keySystem)); + if (systemExt != nullptr) { + + for (int i = 0; i < INITIALIZATION_MAX_RETRY_COUNTER; i++) { + + result = static_cast(systemExt->InitializeCtx(keySystem)); + + if (result == Exchange::OCDM_RESULT::OCDM_SUCCESS) { + if (hadInitializationError) { + TRACE(Trace::Warning, (_T("DRM initialization: success after re-trying, send SUCCESS notification"))); + _parent.initializationStatusNotify(keySystem, Exchange::IContentDecryption::Status::SUCCESS); + ODH_ERROR_REPORT_CTX_ERROR(0, "DRM initialization: success after retrying", i); + } + + ContextImplementation *ctxImplementation = Core::Service::Create(this, keySystem, cleanOnDestroy); + context = ctxImplementation; + + _adminLock.Lock(); + _contextList.push_front(ctxImplementation); + TRACE_L1("context created: %p, number of context instances %d", ctxImplementation, _contextList.size()); + _adminLock.Unlock(); + break; + } else { + TRACE(Trace::Error, (_T("DRM initialization: failed, result=%08x counter=%d"), result, i)); + if (result == Exchange::OCDM_RESULT::OCDM_BUSY_CANNOT_INITIALIZE) { + if (!hadInitializationError) { + hadInitializationError = true; + TRACE(Trace::Error, (_T("DRM initialization: failed, send BUSY notification"))); + _parent.initializationStatusNotify(keySystem, Exchange::IContentDecryption::Status::BUSY); + } + usleep(INITIALIZATION_RETRY_SLEEP_MS * 1000); + } else { + break; + } + } + } + + if (context == nullptr) { + TRACE(Trace::Error, (_T("Could not create DRM context! [%d]"), __LINE__)); + if (hadInitializationError) { + TRACE(Trace::Error, (_T("DRM initialization: failed, send FAILED notification"))); + _parent.initializationStatusNotify(keySystem, Exchange::IContentDecryption::Status::FAILED); + ODH_ERROR_REPORT_CTX_ERROR(0, "DRM initialization: failed after retrying", INITIALIZATION_MAX_RETRY_COUNTER); + } + } + } + } else { + TRACE(Trace::Error, (_T("System is null"))); + } + + return result; + } + // Create a MediaKeySession using the supplied init data and CDM data. - virtual OCDM::OCDM_RESULT CreateSession( + Exchange::OCDM_RESULT CreateSession( const std::string& keySystem, const int32_t licenseType, const std::string& initDataType, @@ -723,9 +974,9 @@ namespace Plugin { const uint16_t initDataLength, const uint8_t* CDMData, const uint16_t CDMDataLength, - ::OCDM::ISession::ICallback* callback, + Exchange::ISession::ICallback* callback, std::string& sessionId, - ::OCDM::ISession*& session) override + Exchange::ISession*& session) override { CDMi::IMediaKeys *system = _parent.KeySystem(keySystem); @@ -737,33 +988,34 @@ namespace Plugin { // OKe we got a buffer machanism to transfer the raw data, now create // the session. - if (system->CreateMediaKeySession(keySystem, licenseType, - initDataType.c_str(), initData, initDataLength, + if (system->CreateMediaKeySession(keySystem, licenseType, + initDataType.c_str(), initData, initDataLength, CDMData, CDMDataLength, &sessionInterface) == 0) { if (sessionInterface != nullptr) { - SessionImplementation *newEntry = - Core::Service::Create(this, - keySystem, sessionInterface, - callback, &keyIds); - - session = newEntry; - sessionId = newEntry->SessionId(); - - _adminLock.Lock(); - - _sessionList.push_front(newEntry); - - if(false == keyIds.IsEmpty()) - { - CommonEncryptionData::Iterator index(keyIds.Keys()); - while (index.Next() == true) { - const CommonEncryptionData::KeyId& entry(index.Current()); - callback->OnKeyStatusUpdate( entry.Id(), entry.Length(), ::OCDM::ISession::StatusPending); - } - } - _adminLock.Unlock(); + SessionImplementation *newEntry = + Core::Service::Create(this, + keySystem, sessionInterface, + callback, &keyIds); + + session = newEntry; + sessionId = newEntry->SessionId(); + + _adminLock.Lock(); + + _sessionList.push_front(newEntry); + + if(false == keyIds.IsEmpty()) + { + CommonEncryptionData::Iterator index(keyIds.Keys()); + while (index.Next() == true) { + const CommonEncryptionData::KeyId& entry(index.Current()); + callback->OnKeyStatusUpdate( entry.Id(), entry.Length(), Exchange::ISession::StatusPending); + } + } + RefContext(keySystem); + _adminLock.Unlock(); } } } @@ -772,35 +1024,35 @@ namespace Plugin { TRACE(Trace::Error, (_T("Could not create a DRM session! [%d]"), __LINE__)); } - return (session != nullptr ? ::OCDM::OCDM_RESULT::OCDM_SUCCESS : ::OCDM::OCDM_RESULT::OCDM_S_FALSE); + return (session != nullptr ? Exchange::OCDM_RESULT::OCDM_SUCCESS : Exchange::OCDM_RESULT::OCDM_S_FALSE); } // Set Server Certificate - virtual ::OCDM::OCDM_RESULT SetServerCertificate( + Exchange::OCDM_RESULT SetServerCertificate( const std::string& keySystem, const uint8_t* serverCertificate, const uint16_t serverCertificateLength) override { CDMi::IMediaKeys* system = _parent.KeySystem(keySystem); - ::OCDM::OCDM_RESULT result = ::OCDM::OCDM_RESULT::OCDM_S_FALSE; + Exchange::OCDM_RESULT result = Exchange::OCDM_RESULT::OCDM_S_FALSE; if (system != nullptr) { TRACE(Trace::Information, ("Set ServerCertificate()")); - result = static_cast<::OCDM::OCDM_RESULT>(system->SetServerCertificate(serverCertificate, serverCertificateLength)); + result = static_cast(system->SetServerCertificate(serverCertificate, serverCertificateLength)); } else { TRACE(Trace::Error, (_T("Could not set the Server Certificates for system: %s"), keySystem.c_str())); } return result; } - virtual uint64_t GetDrmSystemTime(const std::string& keySystem) const override + uint64_t GetDrmSystemTime(const std::string& keySystem) const override { CDMi::IMediaKeysExt* systemExt = dynamic_cast(_parent.KeySystem(keySystem)); if (systemExt) { - return (OCDM::OCDM_RESULT)systemExt->GetDrmSystemTime(); + return (Exchange::OCDM_RESULT)systemExt->GetDrmSystemTime(); } - return ::OCDM::OCDM_RESULT::OCDM_S_FALSE; + return Exchange::OCDM_RESULT::OCDM_S_FALSE; } std::string GetVersionExt(const std::string& keySystem) const override @@ -830,13 +1082,13 @@ namespace Plugin { return false; } - OCDM::OCDM_RESULT EnableSecureStop(const std::string& keySystem, bool enable) override + Exchange::OCDM_RESULT EnableSecureStop(const std::string& keySystem, bool enable) override { CDMi::IMediaKeysExt* systemExt = dynamic_cast(_parent.KeySystem(keySystem)); if (systemExt) { - return (OCDM::OCDM_RESULT)systemExt->EnableSecureStop(enable); + return (Exchange::OCDM_RESULT)systemExt->EnableSecureStop(enable); } - return ::OCDM::OCDM_RESULT::OCDM_S_FALSE; + return Exchange::OCDM_RESULT::OCDM_S_FALSE; } uint32_t ResetSecureStops(const std::string& keySystem) override @@ -848,7 +1100,7 @@ namespace Plugin { return 0; } - OCDM::OCDM_RESULT GetSecureStopIds( + Exchange::OCDM_RESULT GetSecureStopIds( const std::string& keySystem, unsigned char Ids[], uint16_t idsLength, @@ -856,89 +1108,110 @@ namespace Plugin { { CDMi::IMediaKeysExt* systemExt = dynamic_cast(_parent.KeySystem(keySystem)); if (systemExt) { - return (OCDM::OCDM_RESULT)systemExt->GetSecureStopIds(Ids, idsLength, count); + return (Exchange::OCDM_RESULT)systemExt->GetSecureStopIds(Ids, idsLength, count); } - return ::OCDM::OCDM_RESULT::OCDM_S_FALSE; + return Exchange::OCDM_RESULT::OCDM_S_FALSE; } - OCDM::OCDM_RESULT GetSecureStop( + Exchange::OCDM_RESULT GetSecureStop( const std::string& keySystem, const unsigned char sessionID[], - uint32_t sessionIDLength, + uint16_t sessionIDLength, unsigned char* rawData, uint16_t& rawSize) { CDMi::IMediaKeysExt* systemExt = dynamic_cast(_parent.KeySystem(keySystem)); if (systemExt) { - return (OCDM::OCDM_RESULT)systemExt->GetSecureStop(sessionID, sessionIDLength, rawData, rawSize); + return (Exchange::OCDM_RESULT)systemExt->GetSecureStop(sessionID, sessionIDLength, rawData, rawSize); } - return ::OCDM::OCDM_RESULT::OCDM_S_FALSE; + return Exchange::OCDM_RESULT::OCDM_S_FALSE; } - OCDM::OCDM_RESULT CommitSecureStop( + Exchange::OCDM_RESULT CommitSecureStop( const std::string& keySystem, const unsigned char sessionID[], - uint32_t sessionIDLength, + uint16_t sessionIDLength, const unsigned char serverResponse[], - uint32_t serverResponseLength) + uint16_t serverResponseLength) { CDMi::IMediaKeysExt* systemExt = dynamic_cast(_parent.KeySystem(keySystem)); if (systemExt) { - return (OCDM::OCDM_RESULT)systemExt->CommitSecureStop(sessionID, sessionIDLength, serverResponse, serverResponseLength); + return (Exchange::OCDM_RESULT)systemExt->CommitSecureStop(sessionID, sessionIDLength, serverResponse, serverResponseLength); } - return ::OCDM::OCDM_RESULT::OCDM_S_FALSE; + return Exchange::OCDM_RESULT::OCDM_S_FALSE; } - OCDM::OCDM_RESULT DeleteKeyStore(const std::string& keySystem) override + Exchange::OCDM_RESULT DeleteKeyStore(const std::string& keySystem) override { CDMi::IMediaKeysExt* systemExt = dynamic_cast(_parent.KeySystem(keySystem)); if (systemExt) { - return (OCDM::OCDM_RESULT)systemExt->DeleteKeyStore(); + return (Exchange::OCDM_RESULT)systemExt->DeleteKeyStore(); } - return ::OCDM::OCDM_RESULT::OCDM_S_FALSE; + return Exchange::OCDM_RESULT::OCDM_S_FALSE; } - OCDM::OCDM_RESULT DeleteSecureStore(const std::string& keySystem) override + Exchange::OCDM_RESULT DeleteSecureStore(const std::string& keySystem) override { CDMi::IMediaKeysExt* systemExt = dynamic_cast(_parent.KeySystem(keySystem)); if (systemExt) { - return (OCDM::OCDM_RESULT)systemExt->DeleteSecureStore(); + return (Exchange::OCDM_RESULT)systemExt->DeleteSecureStore(keySystem); } - return ::OCDM::OCDM_RESULT::OCDM_S_FALSE; + return Exchange::OCDM_RESULT::OCDM_S_FALSE; } - OCDM::OCDM_RESULT GetKeyStoreHash( + Exchange::OCDM_RESULT GetKeyStoreHash( const std::string& keySystem, uint8_t keyStoreHash[], - uint32_t keyStoreHashLength) override + uint16_t keyStoreHashLength) override { CDMi::IMediaKeysExt* systemExt = dynamic_cast(_parent.KeySystem(keySystem)); if (systemExt) { - return (OCDM::OCDM_RESULT)systemExt->GetSecureStoreHash(keyStoreHash, keyStoreHashLength); + return (Exchange::OCDM_RESULT)systemExt->GetSecureStoreHash(keySystem, keyStoreHash, keyStoreHashLength); } - return ::OCDM::OCDM_RESULT::OCDM_S_FALSE; + return Exchange::OCDM_RESULT::OCDM_S_FALSE; } - OCDM::OCDM_RESULT GetSecureStoreHash( + Exchange::OCDM_RESULT GetSecureStoreHash( const std::string& keySystem, uint8_t secureStoreHash[], - uint32_t secureStoreHashLength) override + uint16_t secureStoreHashLength) override { CDMi::IMediaKeysExt* systemExt = dynamic_cast(_parent.KeySystem(keySystem)); if (systemExt) { - return (OCDM::OCDM_RESULT)systemExt->GetSecureStoreHash(secureStoreHash, secureStoreHashLength); + return (Exchange::OCDM_RESULT)systemExt->GetSecureStoreHash(keySystem, secureStoreHash, secureStoreHashLength); } - return ::OCDM::OCDM_RESULT::OCDM_S_FALSE; + return Exchange::OCDM_RESULT::OCDM_S_FALSE; + } + + Exchange::OCDM_RESULT CleanSecureStore(const std::string& keySystem) override + { + Exchange::OCDM_RESULT result = Exchange::OCDM_RESULT::OCDM_S_FALSE; + CDMi::IMediaKeysExt* systemExt = dynamic_cast(_parent.KeySystem(keySystem)); + if (systemExt) { + result = (Exchange::OCDM_RESULT)systemExt->CleanSecureStore(keySystem); + } + return result; + } + + void GetSessionsDesignators(std::list & list) { + _adminLock.Lock(); + list.clear(); + std::list::const_iterator index(_sessionList.begin()); + while (index != _sessionList.end()) { + list.emplace_back((*index)->KeySystem()); + index++; + } + _adminLock.Unlock(); } BEGIN_INTERFACE_MAP(AccessorOCDM) - INTERFACE_ENTRY(::OCDM::IAccessorOCDM) + INTERFACE_ENTRY(Exchange::IAccessorOCDM) END_INTERFACE_MAP private: - ::OCDM::ISession* FindSession(const CommonEncryptionData& keyIds, const string& keySystem) const + Exchange::ISession* FindSession(const CommonEncryptionData& keyIds, const string& keySystem) const { - ::OCDM::ISession* result = nullptr; + Exchange::ISession* result = nullptr; std::list::const_iterator index(_sessionList.begin()); @@ -953,6 +1226,68 @@ namespace Plugin { } return (result); } + + void Remove(ContextImplementation* context, const string& keySystem, bool cleanOnDestroy) + { + _adminLock.Lock(); + ASSERT(context != nullptr); + + if (context != nullptr) { + std::list::iterator index(_contextList.begin()); + + while ((index != _contextList.end()) && (context != (*index))) { + index++; + } + + if (index != _contextList.end()) { + CDMi::IMediaKeysExt* systemExt = dynamic_cast(_parent.KeySystem(keySystem)); + + if (systemExt != nullptr) { + Exchange::OCDM_RESULT result = (Exchange::OCDM_RESULT)systemExt->DeinitializeCtx(keySystem, cleanOnDestroy); + if (result != Exchange::OCDM_RESULT::OCDM_SUCCESS) { + TRACE(Trace::Error, (_T("Context deinitialization failure"))); + } + } + _contextList.erase(index); + } else { + TRACE(Trace::Error, (_T("Context not found %p"), context)); + } + } + _adminLock.Unlock(); + } + + void RefContext(const string& keySystem) + { + std::list::iterator index(_contextList.begin()); + + while ((index != _contextList.end()) && ((*index)->GetKeySystem() != keySystem)) { + index++; + } + + if (index != _contextList.end()) { + (*index)->AddRef(); + TRACE(Trace::Information, (_T("Context found for: %s - adding ref"), keySystem.c_str())); + } else { + TRACE(Trace::Warning, (_T("Context not found for: %s"), keySystem.c_str())); + } + } + + void ReleaseContext(const string& keySystem) + { + std::list::iterator index(_contextList.begin()); + + while ((index != _contextList.end()) && ((*index)->GetKeySystem() != keySystem)) { + index++; + } + + if (index != _contextList.end()) { + (*index)->Release(); + TRACE(Trace::Information, (_T("Context found for: %s - releasing"), keySystem.c_str())); + } else { + TRACE(Trace::Warning, (_T("Context not found for: %s"), keySystem.c_str())); + } + } + void Remove(SessionImplementation* session, const string& keySystem, CDMi::IMediaKeySession* mediaKeySession) { @@ -995,6 +1330,8 @@ namespace Plugin { } } + //This is to make sure that ContextImplementation is released after session is destroyed + ReleaseContext(keySystem); _adminLock.Unlock(); } @@ -1004,6 +1341,7 @@ namespace Plugin { BufferAdministrator _administrator; uint32_t _defaultSize; std::list _sessionList; + std::list _contextList; }; class Config : public Core::JSON::Container { @@ -1055,7 +1393,7 @@ namespace Plugin { : Core::JSON::Container() , Location() , Connector(_T("/tmp/ocdm")) - , SharePath(_T("/tmp")) + , SharePath(_T("/tmp/OCDM")) , ShareSize(8 * 1024) , KeySystems() { @@ -1077,46 +1415,86 @@ namespace Plugin { Core::JSON::ArrayType KeySystems; }; + class AsyncInitThread { + public: + explicit AsyncInitThread(OCDMImplementation& parent) + : _parent(parent) + , _worker(*this) + { + } + ~AsyncInitThread() = default; + + void Start() + { + _worker.Submit(); + } + + void Stop() + { + _worker.Revoke(); + } + AsyncInitThread(const AsyncInitThread&) = delete; + AsyncInitThread& operator=(const AsyncInitThread&) = delete; + + private: + void Dispatch() + { + if (_parent.InitializeAsync() != Core::ERROR_NONE) { + TRACE(Trace::Error, (_T("OCDM Async Intialization Failed"))); + } + } + + private: + OCDMImplementation& _parent; + friend Core::ThreadPool::JobType; + Core::WorkerPool::JobType _worker; + }; + public: OCDMImplementation() : _entryPoint(nullptr) + , _engine() , _service(nullptr) + , _shell(nullptr) , _compliant(false) , _systemToFactory() , _systemLibraries() + , _thread(*this) { TRACE(Trace::Information, (_T("Constructing OCDMImplementation Service: %p"), this)); } + virtual ~OCDMImplementation() { - if (_service != nullptr) { - delete _service; - } - - if (_entryPoint != nullptr) { - _entryPoint->Release(); - } - - _systemLibraries.clear(); - TRACE(Trace::Information, (_T("Destructed OCDMImplementation Service: %p"), this)); } public: uint32_t Initialize(PluginHost::IShell* service) override { - uint32_t result = Core::ERROR_OPENING_FAILED; + uint32_t result = Core::ERROR_NONE; - // On activation subscribe, on deactivation un-subscribe - PluginHost::ISubSystem* subSystem = service->SubSystems(); + _shell = service; + + _shell->AddRef(); + _thread.Start(); + return (result); + } + + uint32_t InitializeAsync() + { + uint32_t result = Core::ERROR_NONE; + + // On activation subscribe, on deactivation un-subscribe + PluginHost::ISubSystem* subSystem = _shell->SubSystems(); ASSERT(subSystem != nullptr); // Start loading the configured factories Config config; - config.FromString(service->ConfigLine()); + config.FromString(_shell->ConfigLine()); - const string locator(service->DataPath() + config.Location.Value()); + const string locator(_shell->DataPath() + config.Location.Value()); // Before we start loading the mapping of the Keys to the factories, load the factories :-) Core::Directory entry(locator.c_str(), _T("*.drm")); @@ -1131,7 +1509,7 @@ namespace Plugin { if (handle != nullptr) { CDMi::ISystemFactory* entry = handle(); - if (handle != nullptr) { + if (entry != nullptr) { SystemFactory element; element.Name = Core::ClassNameOnly(entry->KeySystem()).Text(); element.Factory = entry; @@ -1142,6 +1520,7 @@ namespace Plugin { } } else { SYSLOG(Logging::Startup, (_T("Could not load factory [%s], error [%s]"), Core::File::FileNameExtended(entry.Current()).c_str(), library.Error().c_str())); + result = Core::ERROR_OPENING_FAILED; } } @@ -1165,14 +1544,27 @@ namespace Plugin { } else { SYSLOG(Logging::Startup, (_T("Required factory [%s], not found for [%s]"), system.c_str(), designator.c_str())); + result = Core::ERROR_OPENING_FAILED; } } } //now handle the configuration if (factory != factories.end()) { - const string configuration(index.Current().Configuration.Value()); - factory->second.Factory->Initialize(service, configuration); + string configuration(index.Current().Configuration.Value()); + if(system == "WideVine") { + // WideVine will be configured later, at CreateSession + SYSLOG(Logging::Startup, (_T("Initialization for WideVine defered"))); + LockGuard lock(widevineInitMutex); + widevineInitialConfig = configuration; + Core::JSON::ArrayType::ConstIterator designators(static_cast&>(index.Current().Designators).Elements()); + while (designators.Next() == true) { + widevineDesignators.insert(designators.Current().Value()); + } + widevineAwaitingConfig = true; + } else { + factory->second.Factory->Initialize(_shell, configuration); + } } } @@ -1189,34 +1581,36 @@ namespace Plugin { SYSLOG(Logging::Startup, (_T("No DRM factories specified. OCDM can not service any DRM requests."))); } - _entryPoint = Core::Service::Create<::OCDM::IAccessorOCDM>(this, config.SharePath.Value(), config.ShareSize.Value()); - Core::ProxyType server = Core::ProxyType::Create(&Core::IWorkerPool::Instance()); - _service = new ExternalAccess(Core::NodeId(config.Connector.Value().c_str()), _entryPoint, server); - - if (_service != nullptr) { + _entryPoint = Core::Service::Create(this, config.SharePath.Value(), config.ShareSize.Value()); + _engine = Core::ProxyType::Create(&Core::IWorkerPool::Instance()); + _service = new ExternalAccess(Core::NodeId(config.Connector.Value().c_str()), _entryPoint, _shell->ProxyStubPath(), _engine); - if (_service->IsListening() == false) { - delete _service; - _entryPoint->Release(); - _service = nullptr; - _entryPoint = nullptr; - } else { - if (subSystem != nullptr) { + if (_service->IsListening() == false) { + delete _service; + _entryPoint->Release(); + _engine.Release(); + _service = nullptr; + _entryPoint = nullptr; + } else { + if (subSystem != nullptr) { - // Announce the port on which we are listening - Core::SystemInfo::SetEnvironment(_T("OPEN_CDM_SERVER"), config.Connector.Value(), true); + // Announce the port on which we are listening + Core::SystemInfo::SetEnvironment(_T("OPEN_CDM_SERVER"), config.Connector.Value(), true); - ASSERT(subSystem->IsActive(PluginHost::ISubSystem::DECRYPTION) == false); - subSystem->Set(PluginHost::ISubSystem::DECRYPTION, this); - } - if (_systemToFactory.size() == 0) { - SYSLOG(Logging::Startup, (string(_T("OCDM server has NO key systems registered!!!")))); - } + ASSERT(subSystem->IsActive(PluginHost::ISubSystem::DECRYPTION) == false); + subSystem->Set(PluginHost::ISubSystem::DECRYPTION, this); + } + if (_systemToFactory.size() == 0) { + SYSLOG(Logging::Startup, (string(_T("OCDM server has NO key systems registered!!!")))); } } + return (result); } + void Deinitialize(PluginHost::IShell* service) override { + _thread.Stop(); + std::map::iterator factory(_systemToFactory.begin()); std::list deinitialized; @@ -1224,15 +1618,32 @@ namespace Plugin { while (factory != _systemToFactory.end()) { std::list::iterator index(std::find(deinitialized.begin(), deinitialized.end(), factory->second.Factory)); - if(index == deinitialized.end()){ + if(index == deinitialized.end()){ TRACE(Trace::Information, (_T("Deinitializing factory(%p) for key system %s"), factory->second.Factory, factory->second.Factory->KeySystem())); factory->second.Factory->Deinitialize(service); deinitialized.push_back(factory->second.Factory); } - + factory++; } + + if (_service != nullptr) { + delete _service; + } + + if (_entryPoint != nullptr) { + _entryPoint->Release(); + } + + if (_engine.IsValid()) { + _engine.Release(); + } + _systemLibraries.clear(); + + _shell->Release(); + _shell = nullptr; } + virtual uint32_t Reset() { return (Core::ERROR_NONE); @@ -1254,6 +1665,27 @@ namespace Plugin { return (Core::Service::Create(sessions)); } + uint32_t Register(IContentDecryption::INotification* notification) override + { + LockGuard lock(notificationMutex); + ASSERT(std::find(_notificationCallbacks.begin(), _notificationCallbacks.end(), notification) == _notificationCallbacks.end()); + _notificationCallbacks.push_back(notification); + notification->AddRef(); + return (Core::ERROR_NONE); + } + + uint32_t Unregister(IContentDecryption::INotification* notification) override + { + LockGuard lock(notificationMutex); + auto index(std::find(_notificationCallbacks.begin(), _notificationCallbacks.end(), notification)); + ASSERT(index != _notificationCallbacks.end()); + if (index != _notificationCallbacks.end()) { + (*index)->Release(); + _notificationCallbacks.erase(index); + } + return (Core::ERROR_NONE); + } + public: bool IsTypeSupported(const std::string& keySystem, const std::string& contentType) { @@ -1335,6 +1767,24 @@ namespace Plugin { return (result); } + void initializationStatusNotify(const string& designator, const Exchange::IContentDecryption::Status status) + { + string keySystem; + // translate "designator" (com.microsoft.playready, ...) into "keySystem" (PlayReady/Widevine) + for (auto system : _keySystems) { + std::list designators; + LoadDesignators(system, designators); + if ((std::find(designators.begin(), designators.end(), designator) != designators.end())) { + keySystem = system; + break; + } + } + LockGuard lock(notificationMutex); + for(const auto callback: _notificationCallbacks) { + callback->initializationStatus(keySystem, status); + } + } + private: void LoadDesignators(const string& keySystem, std::list& designators) const { @@ -1348,15 +1798,22 @@ namespace Plugin { } void LoadSessions(const string& keySystem, std::list& designators) const { - std::map::const_iterator index(_systemToFactory.begin()); - while (index != _systemToFactory.end()) { - if (keySystem == index->second.Name) { - designators.push_back(index->first); + if (_entryPoint) { + std::list systemDesignators; + LoadDesignators(keySystem, systemDesignators); + std::list sessionDesignators; + AccessorOCDM * acc = static_cast(_entryPoint); + acc->GetSessionsDesignators(sessionDesignators); + for (auto sessionDesignator: sessionDesignators) { + if ((std::find(systemDesignators.begin(), systemDesignators.end(), sessionDesignator) != systemDesignators.end())) { + designators.emplace_back(sessionDesignator); + } } - index++; + TRACE(Trace::Information, (_T("Number of: %s sessions: %d"), keySystem.c_str(), designators.size())); + } else { + TRACE(Trace::Error, (_T("null _entryPoint"))); } } - public: // ------------------------------------------------------------------------------------------------------------- // IDecryption methods @@ -1382,14 +1839,25 @@ namespace Plugin { blacklist.insert(std::pair>(system, elements)); } - ::OCDM::IAccessorOCDM* _entryPoint; + Exchange::IAccessorOCDM* _entryPoint; + Core::ProxyType _engine; ExternalAccess* _service; + PluginHost::IShell* _shell; bool _compliant; std::map _systemToFactory; Blacklist _systemBlacklistedCodecRegexps; Blacklist _systemBlacklistedMediaTypeRegexps; std::list _systemLibraries; std::list _keySystems; + AsyncInitThread _thread; + using LockGuard = std::lock_guard; + std::list _notificationCallbacks{}; + std::mutex notificationMutex{}; + Lgi::ASConfig _asConfig; + std::string widevineInitialConfig; + std::set widevineDesignators; + std::atomic_bool widevineAwaitingConfig {false}; + std::mutex widevineInitMutex; }; SERVICE_REGISTRATION(OCDMImplementation, 1, 0); diff --git a/OpenCDMi/LibertyConfig.cpp b/OpenCDMi/LibertyConfig.cpp new file mode 100644 index 0000000000..c981555cca --- /dev/null +++ b/OpenCDMi/LibertyConfig.cpp @@ -0,0 +1,160 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LibertyConfig.h" +#include + +namespace Lgi { +WidevineConfig::WidevineConfig() + : WPEFramework::Core::JSON::Container() + , Certificate() + , Product() + , Company() + , Model() + , Device() + , BuildInfo() + , CertScope() + , StorageLocation() +{ + Add(_T("certificate"), &Certificate); + Add(_T("product"), &Product); + Add(_T("company"), &Company); + Add(_T("model"), &Model); + Add(_T("device"), &Device); + Add(_T("buildinfo"), &BuildInfo); + Add(_T("certscope"), &CertScope); + Add(_T("storagelocation"), &StorageLocation); +} + +ASConfig::ASConfig() { + rdk_logger_init("/etc/debug.ini"); + const std::string asURI = "ws://127.0.0.1:10415"; + _ASConnector = std::make_shared(asURI, *this); + _ASConnector->WatchAPIServices("ready", ASAPI::APIServices::ready); + _ASConnector->WatchAPIConfig("Company", "apps.youtube.brandName", ""); + _ASConnector->WatchAPIConfig("Model", "cpe.modelName", ""); + _ASConnector->WatchAPIConfig("BuildInfo", "cpe.firmwareVersion", ""); + _ASConnector->Connect(); +} + +bool ASConfig::waitForReady(std::unique_lock& lck) +{ + // must be called with _ConfigMtx held! + if (!_AsReady) + { + TRACE(WPEFramework::Trace::Information, (_T("ASConfig: start waiting for 'ready' from lgias"))); + _ASConnCv.wait_for(lck, std::chrono::seconds(30), [this](){return _AsReady;}); + TRACE(WPEFramework::Trace::Information, (_T("ASConfig: ended waiting for 'ready' from lgias; ready: %d"), _AsReady)); + } + if (!_AsReady) + { + TRACE(WPEFramework::Trace::Warning, (_T("ASConfig: timedout waiting for 'ready' from lgias"))); + } + return _AsReady; +} + +std::string ASConfig::getCompany() +{ + std::string ret; + { + std::unique_lock lck(_ConfigMtx); + if (!waitForReady(lck)) return ret; + if(_Company.empty()) { + _ASConnCv.wait_for(lck, std::chrono::seconds(5), [this](){return !_Company.empty();}); + } + ret = _Company; + } + return ret; +} + +std::string ASConfig::getModel() +{ + std::string ret; + { + std::unique_lock lck(_ConfigMtx); + if (!waitForReady(lck)) return ret; + if(_Model.empty()) { + _ASConnCv.wait_for(lck, std::chrono::seconds(5), [this](){return !_Model.empty();}); + } + ret = _Model; + } + return ret; +} + +std::string ASConfig::getBuildInfo() +{ + std::string ret; + { + std::unique_lock lck(_ConfigMtx); + if (!waitForReady(lck)) return ret; + if(_BuildInfo.empty()) { + _ASConnCv.wait_for(lck, std::chrono::seconds(5), [this](){return !_BuildInfo.empty();}); + } + ret = _BuildInfo; + } + return ret; +} + +void ASConfig::OnWatchData(ASConnector::DataSource src, const std::string& alias, json_t* data) +{ + TRACE(WPEFramework::Trace::Information, (_T("ASConfig: received '%s' from lgias; data: '%s'"), alias.c_str(), json_is_string(data) ? json_string_value(data) : "")); + if(alias == "ready") { + std::lock_guard lck(_ConfigMtx); + TRACE(WPEFramework::Trace::Information, (_T("ASConfig: received 'ready' from lgias"))); + _AsReady = true; + } else if(alias == "Company") { + if (!json_is_string(data)) { + return; + } + std::lock_guard lck(_ConfigMtx); + _Company = json_string_value(data); + } else if(alias == "Model") { + if (!json_is_string(data)) { + return; + } + std::lock_guard lck(_ConfigMtx); + _Model = json_string_value(data); + } else if(alias == "BuildInfo") { + if (!json_is_string(data)) { + return; + } + std::lock_guard lck(_ConfigMtx); + _BuildInfo = json_string_value(data); + } else { + return; // do not notify _ASConnCv on uninteresting data + } + _ASConnCv.notify_all(); +} + +std::string updateWidevineConfig(const std::string& widevineConfigStr, ASConfig& asConfig) +{ + std::string ret; + WidevineConfig widevineConfig; + widevineConfig.FromString(widevineConfigStr); + widevineConfig.BuildInfo = asConfig.getBuildInfo(); + widevineConfig.Model = asConfig.getModel(); + widevineConfig.Company = asConfig.getCompany(); + auto certScope = getenv("COBALT_CERT_SCOPE"); + if(certScope) { + widevineConfig.CertScope = certScope; + } + widevineConfig.ToString(ret); + return ret; +} +} diff --git a/OpenCDMi/LibertyConfig.h b/OpenCDMi/LibertyConfig.h new file mode 100644 index 0000000000..99ac36ea83 --- /dev/null +++ b/OpenCDMi/LibertyConfig.h @@ -0,0 +1,73 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "Module.h" + +#include +#include + + +namespace Lgi { +using JSONString = WPEFramework::Core::JSON::String; + +class WidevineConfig : public WPEFramework::Core::JSON::Container { +public: + WidevineConfig(const WidevineConfig&) = delete; + WidevineConfig& operator=(const WidevineConfig&) = delete; + WidevineConfig(); + ~WidevineConfig() = default; + +public: + JSONString Certificate; + JSONString Product; + JSONString Company; + JSONString Model; + JSONString Device; + JSONString BuildInfo; + JSONString CertScope; + JSONString StorageLocation; +}; + +class ASConfig: public ASConnector::events { + public: + ASConfig(); + std::string getCompany(); + std::string getModel(); + std::string getBuildInfo(); + + private: + void OnWatchData(ASConnector::DataSource src, const std::string& alias, json_t* data) override final; + bool waitForReady(std::unique_lock& lck); + + std::shared_ptr _ASConnector; + std::mutex _ConfigMtx; + std::condition_variable _ASConnCv; + std::string _Company; + std::string _Model; + std::string _BuildInfo; + bool _AsReady {false}; +}; + +std::string updateWidevineConfig(const std::string& widevineConfigStr, ASConfig& asConfig); +} diff --git a/OpenCDMi/OCDM.config b/OpenCDMi/OCDM.config index 4e286ee329..0214ff6a17 100644 --- a/OpenCDMi/OCDM.config +++ b/OpenCDMi/OCDM.config @@ -11,17 +11,33 @@ endif() map() kv(locator lib${PLUGIN_OCDM_IMPLEMENTATION}.so) - kv(mode ${PLUGIN_OPENCDMI_MODE}) + if(PLUGIN_OPENCDMI_MODE) + kv(mode ${PLUGIN_OPENCDMI_MODE}) + else() + if (PLUGIN_OPENCDMI_OOP) + kv(mode "Local") + else() + kv(mode "Off") + endif() + endif() if(PLUGIN_OPENCDMI_USER) kv(user ${PLUGIN_OPENCDMI_USER}) endif() if(PLUGIN_OPENCDMI_GROUP) kv(group ${PLUGIN_OPENCDMI_GROUP}) endif() + key(configuration) + map() + kv(awc_container "proxy") + kv(start_timeout 6000) + kv(stop_timeout 6000) + end() end() ans(rootobject) map() + kv(sharepath "/tmp/OCDM") + kv(connector "/tmp/OCDM/ocdm") kv(systems ___array___) if (NOT PLUGIN_OPENCDMI_MODE) kv(outofprocess ${PLUGIN_OPENCDMI_OOP}) @@ -36,28 +52,31 @@ map() kv(designators "___array___;org.chromium.externalclearkey;org.w3.clearkey") end() ans(keysystem) -map_append(${configuration} systems ${keysystem}) +map_append(${configuration} systems ___array___ ${keysystem}) endif() if(PLUGIN_OPENCDMI_PLAYREADY OR PLUGIN_OPENCDMI_PLAYREADY_NEXUS OR PLUGIN_OPENCDMI_PLAYREADY_NEXUS_SVP OR PLUGIN_OPENCDMI_PLAYREADY_VGDRM) map() kv(name "PlayReady") - kv(designators "com.youtube.playready;com.microsoft.playready;com.netflix.playready") -if(PLUGIN_OPENCDMI_PLAYREADY_METERING_CERTIFICATE) - key(configuration) - map() - kv(metering ${PLUGIN_OPENCDMI_PLAYREADY_METERING_CERTIFICATE}) - end() -else() + kv(designators "com.youtube.playready;com.microsoft.playready;com.netflix.playready;com.microsoft.playready.recommendation;com.ipplayer.playready;com.amazon.playready;com.disney.playready") key(configuration) map() - kv(read-dir "${PLUGIN_OCDM_PLAYREADY_READ_DIR}") - kv(store-location "${PLUGIN_OCDM_PLAYREADY_STORE_LOCATION}") + if(PLUGIN_OPENCDMI_PLAYREADY_METERING_CERTIFICATE) + kv(metering ${PLUGIN_OPENCDMI_PLAYREADY_METERING_CERTIFICATE}) + endif() + if(PLUGIN_OCDM_PLAYREADY_READ_DIR) + kv(read-dir "${PLUGIN_OCDM_PLAYREADY_READ_DIR}") + endif() + if(PLUGIN_OCDM_PLAYREADY_STORE_LOCATION) + kv(store-location "${PLUGIN_OCDM_PLAYREADY_STORE_LOCATION}") + endif() + if(PLUGIN_OPENCDMI_PLAYREADY_CERTIFICATE_LABEL) + kv(certificatelabel "${PLUGIN_OPENCDMI_PLAYREADY_CERTIFICATE_LABEL}") + endif() end() -endif() end() ans(keysystem) -map_append(${configuration} systems ${keysystem}) +map_append(${configuration} systems ___array___ ${keysystem}) endif() if(PLUGIN_OPENCDMI_WIDEVINE OR OPENCDMI_WIDEVINE_NEXUS_SVP) @@ -71,11 +90,13 @@ map() if(PLUGIN_OPENCDMI_WIDEVINE_KEYBOX) kv(keybox ${PLUGIN_OPENCDMI_WIDEVINE_KEYBOX}) endif() - + if(PLUGIN_OPENCDMI_WIDEVINE_STORAGE_LOCATION) + kv(storagelocation ${PLUGIN_OPENCDMI_WIDEVINE_STORAGE_LOCATION}) + endif() end() end() ans(keysystem) -map_append(${configuration} systems ${keysystem}) +map_append(${configuration} systems ___array___ ${keysystem}) endif() if(PLUGIN_OPENCDMI_NAGRA) @@ -89,13 +110,13 @@ map() end() end() ans(keysystem) -map_append(${configuration} systems ${keysystem}) +map_append(${configuration} systems ___array___ ${keysystem}) map() kv(name "NagraConnect") kv(designators "___array___;com.nagra.connect") end() ans(keysystem) -map_append(${configuration} systems ${keysystem}) +map_append(${configuration} systems ___array___ ${keysystem}) endif() @@ -105,7 +126,7 @@ map() kv(designators "___array___;com.ncas.alpha") end() ans(keysystem) -map_append(${configuration} systems ${keysystem}) +map_append(${configuration} systems ___array___ ${keysystem}) endif() map_append(${configuration} root ${rootobject}) diff --git a/OpenCDMi/OCDM.cpp b/OpenCDMi/OCDM.cpp index 1791970c9a..a867b3ea43 100644 --- a/OpenCDMi/OCDM.cpp +++ b/OpenCDMi/OCDM.cpp @@ -45,41 +45,38 @@ namespace OCDM { Exchange::IMemory* MemoryObserver(const RPC::IRemoteConnection* connection) { class MemoryObserverImpl : public Exchange::IMemory { - private: - MemoryObserverImpl(); - MemoryObserverImpl(const MemoryObserverImpl&); - MemoryObserverImpl& operator=(const MemoryObserverImpl&); - public: + MemoryObserverImpl() = delete; + MemoryObserverImpl(const MemoryObserverImpl&) = delete; + MemoryObserverImpl& operator=(const MemoryObserverImpl&) = delete; + MemoryObserverImpl(const RPC::IRemoteConnection* connection) : _main(connection == 0 ? Core::ProcessInfo().Id() : connection->RemoteId()) { } - ~MemoryObserverImpl() - { - } + ~MemoryObserverImpl() = default; public: - virtual uint64_t Resident() const + uint64_t Resident() const override { return _main.Resident(); } - virtual uint64_t Allocated() const + uint64_t Allocated() const override { return _main.Allocated(); } - virtual uint64_t Shared() const + uint64_t Shared() const override { return _main.Shared(); } - virtual uint8_t Processes() const + uint8_t Processes() const override { return (IsOperational() ? 1 : 0); } #ifndef USE_THUNDER_R4 - virtual bool IsOperational() const + virtual bool IsOperational() const override // TODO is it 'const bool' or 'bool' in TH2? #else - virtual const bool IsOperational() const + virtual bool IsOperational() const override #endif /* USE_THUNDER_R4 */ { return _main.IsActive(); @@ -114,12 +111,14 @@ namespace Plugin { string message; + ASSERT(service != nullptr); ASSERT(_service == nullptr); ASSERT(_memory == nullptr); ASSERT(_opencdmi == nullptr); + ASSERT(_connectionId == 0); - _connectionId = 0; _service = service; + _service->AddRef(); _skipURL = static_cast(_service->WebPrefix().length()); // Register the Process::Notification stuff. The Remote process might die before we get a @@ -130,9 +129,8 @@ namespace Plugin { if (_opencdmi == nullptr) { message = _T("OCDM could not be instantiated."); - _service->Unregister(&_notification); - _service = nullptr; } else { + RegisterAll(); _opencdmi->Initialize(_service); ASSERT(_connectionId != 0); @@ -142,61 +140,80 @@ namespace Plugin { _memory = WPEFramework::OCDM::MemoryObserver(connection); ASSERT(_memory != nullptr); - + _opencdmi->Register(&_notification); connection->Release(); } else { message = _T("OCDM crashed at initialize!"); - _opencdmi = nullptr; - _service->Unregister(&_notification); - _service = nullptr; } } + if(message.length() != 0) { + Deinitialize(service); + } + return message; } /*virtual*/ void OCDM::Deinitialize(PluginHost::IShell* service) { ASSERT(_service == service); - ASSERT(_memory != nullptr); - ASSERT(_opencdmi != nullptr); _service->Unregister(&_notification); - _memory->Release(); - _opencdmi->Deinitialize(service); - RPC::IRemoteConnection* connection(_service->RemoteConnection(_connectionId)); - uint32_t result = _opencdmi->Release(); - ASSERT(result == Core::ERROR_DESTRUCTION_SUCCEEDED); + if(_opencdmi != nullptr) { - PluginHost::ISubSystem* subSystem = service->SubSystems(); + if(_memory != nullptr) { + _memory->Release(); + _memory = nullptr; + } - ASSERT(subSystem != nullptr); + _opencdmi->Unregister(&_notification); + _opencdmi->Deinitialize(service); + RPC::IRemoteConnection* connection(_service->RemoteConnection(_connectionId)); - if (subSystem != nullptr) { - ASSERT(subSystem->IsActive(PluginHost::ISubSystem::DECRYPTION) == true); - subSystem->Set(PluginHost::ISubSystem::NOT_DECRYPTION, nullptr); - subSystem->Release(); + UnregisterAll(); + + VARIABLE_IS_NOT_USED uint32_t result = _opencdmi->Release(); + _opencdmi = nullptr; + // It should have been the last reference we are releasing, + // so it should end up in a DESCRUCTION_SUCCEEDED, if not we + // are leaking... + ASSERT(result == Core::ERROR_DESTRUCTION_SUCCEEDED); + + // If this was running in a (container) proccess... + if (connection != nullptr) { + + // Lets trigger the cleanup sequence for + // out-of-process code. Which will guard + // that unwilling processes, get shot if + // not stopped friendly :~) + connection->Terminate(); + connection->Release(); + } } - if (connection != nullptr) { - connection->Terminate(); - connection->Release(); + + PluginHost::ISubSystem* subSystem = service->SubSystems(); + + if (subSystem != nullptr) { + if(subSystem->IsActive(PluginHost::ISubSystem::DECRYPTION) == true) { + subSystem->Set(PluginHost::ISubSystem::NOT_DECRYPTION, nullptr); + subSystem->Release(); + } } - // Deinitialize what we initialized.. - _memory = nullptr; - _opencdmi = nullptr; + _service->Release(); _service = nullptr; + _connectionId = 0; } /* virtual */ string OCDM::Information() const { // No additional info to report. - return (nullptr); + return string(); } - /* virtual */ void OCDM::Inbound(Web::Request& request) + /* virtual */ void OCDM::Inbound(Web::Request&) { } diff --git a/OpenCDMi/OCDM.h b/OpenCDMi/OCDM.h index cbefe3538c..a78969b2b0 100644 --- a/OpenCDMi/OCDM.h +++ b/OpenCDMi/OCDM.h @@ -24,31 +24,27 @@ #include #include #include +#include "UtilsJsonRpc.h" namespace WPEFramework { namespace Plugin { class OCDM : public PluginHost::IPlugin, public PluginHost::IWeb, public PluginHost::JSONRPC { private: - OCDM(const OCDM&) = delete; - OCDM& operator=(const OCDM&) = delete; - class Notification : public RPC::IRemoteConnection::INotification { - - private: + class Notification : public RPC::IRemoteConnection::INotification, + public Exchange::IContentDecryption::INotification { + public: Notification() = delete; Notification(const Notification&) = delete; Notification& operator=(const Notification&) = delete; - public: explicit Notification(OCDM* parent) : _parent(*parent) { ASSERT(parent != nullptr); } - ~Notification() - { - } + ~Notification() override = default; public: virtual void Activated(RPC::IRemoteConnection*) @@ -58,9 +54,15 @@ namespace Plugin { { _parent.Deactivated(connection); } + void initializationStatus(const std::string& keySystem, + Exchange::IContentDecryption::Status status) override + { + _parent.OnEvent(keySystem, static_cast(status)); + } BEGIN_INTERFACE_MAP(Notification) INTERFACE_ENTRY(RPC::IRemoteConnection::INotification) + INTERFACE_ENTRY(Exchange::IContentDecryption::INotification) END_INTERFACE_MAP private: @@ -69,10 +71,6 @@ namespace Plugin { public: class Data : public Core::JSON::Container { - private: - Data(const Data&) = delete; - Data& operator=(const Data&) = delete; - public: class System : public Core::JSON::Container { private: @@ -80,14 +78,16 @@ namespace Plugin { public: System() - : Name() + : Core::JSON::Container() + , Name() , Designators() { Add(_T("name"), &Name); Add(_T("designators"), &Designators); } System(const string& name, RPC::IStringIterator* entries) - : Name() + : Core::JSON::Container() + , Name() , Designators() { Add(_T("name"), &Name); @@ -99,15 +99,14 @@ namespace Plugin { Load(entries); } System(const System& copy) - : Name(copy.Name) + : Core::JSON::Container() + , Name(copy.Name) , Designators(copy.Designators) { Add(_T("name"), &Name); Add(_T("designators"), &Designators); } - virtual ~System() - { - } + ~System() override = default; public: Core::JSON::String Name; @@ -126,37 +125,49 @@ namespace Plugin { }; public: + Data(const Data&) = delete; + Data& operator=(const Data&) = delete; Data() : Core::JSON::Container() { Add(_T("systems"), &Systems); } - ~Data() - { - } + ~Data() = default; public: Core::JSON::ArrayType Systems; }; public: - #ifdef __WINDOWS__ - #pragma warning(disable : 4355) - #endif + OCDM(const OCDM&) = delete; + OCDM& operator=(const OCDM&) = delete; + OCDM() - : _service(nullptr) + : _skipURL(0) + , _connectionId(0) + , _service(nullptr) , _opencdmi(nullptr) , _memory(nullptr) , _notification(this) { - RegisterAll(); } - #ifdef __WINDOWS__ - #pragma warning(default : 4355) - #endif - virtual ~OCDM() + + ~OCDM() override = default; + + void OnEvent(const std::string& keySystem, + const JsonData::OCDM::DrminitializationstatusParamsData::StatusType status) { - UnregisterAll(); + if (status == JsonData::OCDM::DrminitializationstatusParamsData::StatusType::BUSY) + { + JsonData::OCDM::SessionInfo data; + data.Drm = keySystem; + sendNotify(_T("drmalreadyinitialized"), data); + } + + JsonData::OCDM::DrminitializationstatusParamsData data; + data.Status = status; + data.Drm = keySystem; + sendNotify(_T("drminitializationstatus"), data); } public: @@ -178,22 +189,22 @@ namespace Plugin { // If there is an error, return a string describing the issue why the initialisation failed. // The Service object is *NOT* reference counted, lifetime ends if the plugin is deactivated. // The lifetime of the Service object is guaranteed till the deinitialize method is called. - virtual const string Initialize(PluginHost::IShell* service); + const string Initialize(PluginHost::IShell* service) override; // The plugin is unloaded from the webbridge. This is call allows the module to notify clients // or to persist information if needed. After this call the plugin will unlink from the service path // and be deactivated. The Service object is the same as passed in during the Initialize. // After theis call, the lifetime of the Service object ends. - virtual void Deinitialize(PluginHost::IShell* service); + void Deinitialize(PluginHost::IShell* service) override; // Returns an interface to a JSON struct that can be used to return specific metadata information with respect // to this plugin. This Metadata can be used by the MetData plugin to publish this information to the ouside world. - virtual string Information() const; + string Information() const override; // IWeb methods // ------------------------------------------------------------------------------------------------------- - virtual void Inbound(Web::Request& request); - virtual Core::ProxyType Process(const Web::Request& request); + void Inbound(Web::Request& request) override; + Core::ProxyType Process(const Web::Request& request) override; private: void Deactivated(RPC::IRemoteConnection* process); @@ -205,10 +216,11 @@ namespace Plugin { void UnregisterAll(); uint32_t get_drms(Core::JSON::ArrayType& response) const; uint32_t get_keysystems(const string& index, Core::JSON::ArrayType& response) const; + uint32_t get_sessions(Core::JSON::ArrayType& response) const; private: - uint8_t _skipURL{}; - uint32_t _connectionId{}; + uint8_t _skipURL; + uint32_t _connectionId; PluginHost::IShell* _service; Exchange::IContentDecryption* _opencdmi; Exchange::IMemory* _memory; diff --git a/OpenCDMi/OCDMJsonRpc.cpp b/OpenCDMi/OCDMJsonRpc.cpp index 7dab5286ff..4ee24c64e3 100644 --- a/OpenCDMi/OCDMJsonRpc.cpp +++ b/OpenCDMi/OCDMJsonRpc.cpp @@ -34,12 +34,14 @@ namespace Plugin { { Property>(_T("drms"), &OCDM::get_drms, nullptr, this); Property>(_T("keysystems"), &OCDM::get_keysystems, nullptr, this); + Property>(_T("sessions"), &OCDM::get_sessions, nullptr, this); } void OCDM::UnregisterAll() { Unregister(_T("keysystems")); Unregister(_T("drms")); + Unregister(_T("sessions")); } bool OCDM::KeySystems(const string& name, Core::JSON::ArrayType& response) const @@ -101,6 +103,27 @@ namespace Plugin { return result; } + uint32_t OCDM::get_sessions(Core::JSON::ArrayType& response) const + { + RPC::IStringIterator* drmsIter(_opencdmi->Systems()); + if (drmsIter != nullptr) { + string system; + while (drmsIter->Next(system)) { + RPC::IStringIterator * sessionsIter(_opencdmi->Sessions(system)); + string sessionItem; + if (sessionsIter != nullptr) { + while (sessionsIter->Next(sessionItem)) { + SessionInfo session; + session.Drm = system; + response.Add(session); + } + sessionsIter->Release(); + } + } + drmsIter->Release(); + } + return Core::ERROR_NONE; + } } // namespace Plugin } diff --git a/OpenCDMi/Protobuf.h b/OpenCDMi/Protobuf.h new file mode 100644 index 0000000000..936de9e0e1 --- /dev/null +++ b/OpenCDMi/Protobuf.h @@ -0,0 +1,530 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2022 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "Module.h" + +namespace WPEFramework { + +namespace Protobuf { + + struct IElement { + enum class WireType : uint8_t { + VARINT = 0, + FIXED64 = 1, + LENGTH_DELIMITED = 2, + GROUP_START = 3, + GROUP_END = 4, + FIXED32 = 5 + }; + + virtual ~IElement() = default; + virtual uint32_t Deserialize(const uint8_t data[], const uint32_t length) = 0; + virtual bool IsSet() const = 0; + virtual WireType Type() const = 0; + }; // struct IElement + + template + class ValueElementType : public IElement { + public: + using type = T; + using IElement::WireType; + ValueElementType(const ValueElementType&) = default; + ValueElementType& operator=(const ValueElementType&) = default; + ValueElementType() + : _value() + , _set(false) + { } + ~ValueElementType() = default; + + public: + bool IsSet() const override { + return (_set); + } + const T& Value() const { + return (_value); + } + T& Value() { + return (_value); + } + + protected: + void Set(const bool set) { + _set = set; + } + + private: + T _value; + bool _set; + }; // class ValueElementType + + template + class VarintType : public ValueElementType { + static_assert(std::is_integral::value || std::is_enum::value, "Varint requires int type"); + + public: + VarintType() + : ValueElementType() + { } + VarintType(const VarintType&) = default; + VarintType& operator=(const VarintType&) = default; + ~VarintType() = default; + + public: + using typename ValueElementType::WireType; + uint32_t Deserialize(const uint8_t data[], const uint32_t length) override + { + ASSERT(data != nullptr); + uint32_t result = 0; + result = ReadVarint(data, length, ValueElementType::Value()); + ValueElementType::Set(result != 0); + return (result); + } + WireType Type() const override { + return (WireType::VARINT); + } + + private: + static uint8_t ReadVarint(const uint8_t data[], const uint32_t length, T& out) + { + ASSERT(data != nullptr); + uint8_t result = 0; + const uint8_t* ptr = data; + uint8_t shift = 0; + uint64_t value = 0; + uint8_t size = sizeof(value); + while (size-- && (ptr < (data + length))) { + value |= (static_cast(*ptr & 0x7F) << shift); + if (((*ptr++) & 0x80) == 0) { + out = static_cast(value); + result = static_cast(ptr - data); + break; + } + shift += 7; + } + return (result); + } + }; // class VarintType + + template + class ZigzagVarintType : public VarintType { + static_assert(std::is_signed::value, "Zigzaged varint is meant for signed integer type"); + + public: + ZigzagVarintType() + : ValueElementType() + { } + ZigzagVarintType(const ZigzagVarintType&) = default; + ZigzagVarintType& operator=(const ZigzagVarintType&) = default; + ~ZigzagVarintType() = default; + + public: + uint32_t Deserialize(const uint8_t data[], const uint32_t length) override + { + ASSERT(data != nullptr); + uint32_t result = 0; + result = VarintType::ReadVarint(data, length, ValueElementType::Value()); + if (result != 0) { + T& value = ValueElementType::Value(); + // unzigzag the value... + if (value & 1) { + value ^= static_cast(-1); + } + value >>= 1; + } + ValueElementType::Set(result != 0); + return (result); + } + }; // class ZigzagVarintType + + template + class FixedType : public ValueElementType { + protected: + FixedType() + : ValueElementType() + { } + FixedType(const FixedType&) = default; + FixedType& operator=(const FixedType&) = default; + ~FixedType() = default; + + public: + uint32_t Deserialize(const uint8_t data[], const uint32_t length) override + { + ASSERT(data != nullptr); + uint32_t result = 0; + result = ReadFixed(data, length, ValueElementType::Value()); + ValueElementType::Set(result != 0); + return (result); + } + + private: + static uint8_t ReadFixed(const uint8_t data[], const uint32_t length, T& out) + { + ASSERT(data != nullptr); + uint8_t result = 0; + if (length >= sizeof(out)) { + result = static_cast(sizeof(out)); +#ifdef LITTLE_ENDIAN_PLATFORM + /* Can be int, float or double here... */ + ::memcpy(&out, data, sizeof(out)); +#else +#error ReadFixed not implemented for big endian +#endif + } + return (result); + } + }; // class FixedType + + template + class Fixed32Type : public FixedType { + static_assert(std::is_arithmetic::value && (sizeof(T) == sizeof(uint32_t)), "Fixed32 requires int32 or float type"); + + public: + Fixed32Type() + : FixedType() + { } + Fixed32Type(const Fixed32Type&) = default; + Fixed32Type& operator=(const Fixed32Type&) = default; + ~Fixed32Type() = default; + + public: + using typename ValueElementType::WireType; + WireType Type() const override { + return (WireType::FIXED32); + } + }; // class Fixed32Type + + template + class Fixed64Type : public FixedType { + static_assert(std::is_arithmetic::value && (sizeof(T) == sizeof(uint64_t)), "Fixed64 requires int64 or double type"); + + public: + Fixed64Type() + : FixedType() + { } + Fixed64Type(const Fixed64Type&) = default; + Fixed64Type& operator=(const Fixed64Type&) = default; + ~Fixed64Type() = default; + + public: + using typename ValueElementType::WireType; + WireType Type() const override { + return (WireType::FIXED64); + } + }; // class Fixed64Type + + template + class BytesType : public ValueElementType> { + public: + using type = std::basic_string; + BytesType() + : ValueElementType() + { } + BytesType(const BytesType&) = default; + BytesType& operator=(const BytesType&) = default; + ~BytesType() = default; + + public: + using typename ValueElementType::WireType; + uint32_t Deserialize(const uint8_t data[], const uint32_t length) override + { + ASSERT(data != nullptr); + uint32_t result = 0; + const uint8_t* ptr = data; + VarintType size{}; + result = size.Deserialize(ptr, length); + if ((result != 0) && (size.IsSet() == true) && (size.Value() != 0) && (size.Value() <= length)) { + ptr += result; + if ((ptr + size.Value()) <= (data + length)) { + ValueElementType::Value().append(reinterpret_cast(ptr), size.Value()); + result += size.Value(); + } + } + return (result); + } + WireType Type() const override { + return (WireType::LENGTH_DELIMITED); + } + }; // class BytesType + + template + class RepeatedType : public IElement{ + public: + using type = typename ELEMENT::type; + RepeatedType() + : _elements() + { } + RepeatedType(const RepeatedType&) = default; + RepeatedType& operator=(const RepeatedType&) = default; + ~RepeatedType() = default; + + public: + bool IsSet() const override { + return (_elements.empty() == false); + } + const std::list& Elements() const { + return (_elements); + } + void Clear() { + _elements.clear(); + } + uint32_t Deserialize(const uint8_t data[], const uint32_t length) override + { + ASSERT(data != nullptr); + ELEMENT element{}; + uint32_t result = element.Deserialize(data, length); + if (result != 0) { + _elements.push_back(std::move(element)); + } + return (result); + } + WireType Type() const override { + ELEMENT element{}; + return (element.Type()); + } + private: + std::list _elements; + }; // class RepeatedType + + template + class PackedRepeatedType : public IElement { + static_assert(std::is_arithmetic::value, "PackedRepeated requires elements of numerical type"); + + public: + using type = typename ELEMENT::type; + PackedRepeatedType() + : _elements() + { } + PackedRepeatedType(const PackedRepeatedType&) = default; + PackedRepeatedType& operator=(const PackedRepeatedType&) = default; + ~PackedRepeatedType() = default; + + public: + bool IsSet() const override { + return (_elements.empty() == false); + } + const std::list& Elements() const { + return (_elements); + } + void Clear() { + _elements.clear(); + } + uint32_t Deserialize(const uint8_t data[], const uint32_t length) override + { + ASSERT(data != nullptr); + uint32_t result = 0; + const uint8_t* ptr = data; + VarintType size{}; + result = size.Deserialize(ptr, length); + ptr += result; + if ((result != 0) && ((size.IsSet() == true) && (size.Value() != 0) && (size.Value() <= (length - result)))) { + const uint8_t* end = (ptr + size.Value()); + while (ptr < end) { + ELEMENT element{}; + uint32_t consumed = element.Deserialize(ptr, static_cast(end - ptr)); + if (consumed != 0) { + ptr += consumed; + result += consumed; + _elements.push_back(element); + } else { + // TRACE_L1("Failed to deserialize packed element"); + result = 0; + break; + } + } + } + return (result); + } + + private: + std::list _elements; + }; // class PackedRepeatedType + + class Message : public IElement { + private: + struct Entry { + IElement* element; + bool required; + }; + + public: + using type = void; + Message() + : _elements() + { } + Message(const Message&) = default; + Message& operator=(const Message&) = default; + ~Message() = default; + + public: + bool IsSet() const override { + return (_elements.empty() == false); + } + void Clear() { + _elements.clear(); + } + void Add(const uint8_t index, IElement* element, bool required = false) + { + ASSERT(index != 0); // 0 is not allowed as key + ASSERT(element != nullptr); + _elements.emplace(index, Entry{ element, required }); + } + bool IsValid() const + { + bool valid = true; + for (auto const& entry : _elements) { + VARIABLE_IS_NOT_USED const IElement* const& element = entry.second.element; + ASSERT(element != nullptr); + if ((entry.second.required == true) && (entry.second.element->IsSet() == false)) { + valid = false; + break; + } + } + return (valid); + } + bool FromBuffer(const uint8_t data[], const uint32_t length) + { + ASSERT(data != nullptr); + bool result = true; + const uint8_t* ptr = data; + const uint8_t* end = (ptr + length); + while (ptr < end) { + VarintType tag{}; + uint32_t tagSize = tag.Deserialize(ptr, static_cast(end - ptr)); + if (tagSize > 0) { + const IElement::WireType type = static_cast(tag.Value() & 0x7); + const uint32_t key = static_cast(tag.Value() >> 3); + const uint32_t available = static_cast(end - ptr - tagSize); + uint32_t consumed = 0; + ptr += tagSize; + + if (key != 0) { + auto it = _elements.find(key); + if (it != _elements.end()) { + IElement* const& element = (it->second).element; + ASSERT(element != nullptr); + if (type == element->Type()) { + consumed = element->Deserialize(ptr, available); + } else { + // TRACE_L1("Wire type mismatch, check proto definition"); + } + } else { + consumed = Skip(ptr, available, type); + } + } + + if (consumed == 0) { + // TRACE_L1("Failed to parse element"); + result = false; + break; + } + + ptr += consumed; + } else { + // TRACE_L1("Failed to parse key/value pair"); + result = false; + break; + } + } + return (result); + } + + public: + uint32_t Deserialize(const uint8_t data[], const uint32_t length) override + { + ASSERT(data != nullptr); + uint32_t result = 0; + const uint8_t* ptr = data; + VarintType size{}; + result = size.Deserialize(ptr, length); + if ((result != 0) && (size.IsSet() == true) && (size.Value() > 0) && (size.Value() <= (length - result))) { + ptr += result; + if (FromBuffer(ptr, size.Value()) == true) { + result += size.Value(); + } else { + // TRACE_L1("Failed to parse message"); + } + } + return (result); + } + WireType Type() const { + return (WireType::LENGTH_DELIMITED); + } + + private: + static uint32_t Skip(const uint8_t data[], const uint32_t length, const WireType type) + { + ASSERT(data != nullptr); + uint32_t result = 0; + switch (type) { + case WireType::VARINT: + result = Skip>(data, length); + break; + case WireType::FIXED64: + result = Skip>(data, length); + break; + case WireType::LENGTH_DELIMITED: + result = Skip>(data, length); + break; + case WireType::FIXED32: + result = Skip>(data, length); + break; + case WireType::GROUP_START: + case WireType::GROUP_END: + /* deprecated and not supported here */ + // TRACE_L1("Groups are not supported!"); + break; + default: + // TRACE_L1("Unknown wire type!"); + break; + } + return (result); + } + template + static uint32_t Skip(const uint8_t data[], const uint32_t length) + { + ASSERT(data != nullptr); + ELEMENT element{}; + return (element.Deserialize(data, length)); + } + + private: + std::map _elements; + }; // class Message + + using Bytes = BytesType; + using Utf8String = BytesType; + using Bool = VarintType; + using Int32 = VarintType; + using Int64 = VarintType; + using UInt32 = VarintType; + using UInt64 = VarintType; + using SInt32 = ZigzagVarintType; + using SInt64 = ZigzagVarintType; + using Fixed32 = Fixed32Type; + using Fixed64 = Fixed64Type; + using SFixed32 = Fixed32Type; + using SFixed64 = Fixed64Type; + using Float = FixedType; + using Double = FixedType; + template using EnumType = VarintType; + +}; // namespace Protobuf + +} // namespace diff --git a/PlayerInfo/CMakeLists.txt b/PlayerInfo/CMakeLists.txt index e2c0263ba8..45fdd15e18 100644 --- a/PlayerInfo/CMakeLists.txt +++ b/PlayerInfo/CMakeLists.txt @@ -37,6 +37,7 @@ set(PLUGIN_PLAYERINFO_MODE "Off" CACHE STRING "Controls if the plugin should run find_package(${NAMESPACE}Plugins REQUIRED) find_package(${NAMESPACE}Definitions REQUIRED) +find_package(${NAMESPACE}DSManagerPlugin REQUIRED) find_package(CompileSettingsDebug CONFIG REQUIRED) add_library(${MODULE_NAME} SHARED @@ -51,6 +52,7 @@ target_link_libraries(${MODULE_NAME} PRIVATE CompileSettingsDebug::CompileSettingsDebug ${NAMESPACE}Definitions::${NAMESPACE}Definitions + ${NAMESPACE}DSManagerPlugin::${NAMESPACE}DSManagerPlugin ${NAMESPACE}Plugins::${NAMESPACE}Plugins) find_package(GStreamer REQUIRED) diff --git a/PlayerInfo/DeviceSettings/PlatformImplementation.cpp b/PlayerInfo/DeviceSettings/PlatformImplementation.cpp index 282af9914c..c11d45cb04 100644 --- a/PlayerInfo/DeviceSettings/PlatformImplementation.cpp +++ b/PlayerInfo/DeviceSettings/PlatformImplementation.cpp @@ -34,11 +34,12 @@ #include "dsMgr.h" #include "manager.hpp" +#include "dsmanagerplugin/DSManagerPlugin.h" namespace WPEFramework { namespace Plugin { -class PlayerInfoImplementation : public Exchange::IPlayerProperties, public Exchange::Dolby::IOutput +class PlayerInfoImplementation : public Exchange::IPlayerProperties, public Exchange::Dolby::IOutput, public DSManagerPlugin { private: @@ -71,10 +72,10 @@ class PlayerInfoImplementation : public Exchange::IPlayerProperties, public Exch for (auto index: caps) { MediaTypes mediaType{gst_caps_from_string(index.first.c_str())}; - if (elements = std::move(GstUtils::GstRegistryGetElementForMediaType(decoderFactories.get(), std::move(mediaType)))) { + if (elements = GstUtils::GstRegistryGetElementForMediaType(decoderFactories.get(), mediaType)) { codecIteratorList.push_back(index.second); - } else if (elements = std::move(GstUtils::GstRegistryGetElementForMediaType(parserFactories.get(), std::move(mediaType)))) { + } else if (elements = GstUtils::GstRegistryGetElementForMediaType(parserFactories.get(), mediaType)) { for (GList* iterator = elements.get(); iterator; iterator = iterator->next) { @@ -86,7 +87,7 @@ class PlayerInfoImplementation : public Exchange::IPlayerProperties, public Exch if (padTemplate->direction == GST_PAD_SRC) { MediaTypes mediaTypes{gst_static_pad_template_get_caps(padTemplate)}; - if (GstUtils::GstRegistryGetElementForMediaType(decoderFactories.get(), std::move(mediaTypes))) { + if (GstUtils::GstRegistryGetElementForMediaType(decoderFactories.get(), mediaTypes)) { codecIteratorList.push_back(index.second); } } @@ -99,7 +100,7 @@ class PlayerInfoImplementation : public Exchange::IPlayerProperties, public Exch } private: - static inline FeatureList GstRegistryGetElementForMediaType(GList* elementsFactories, MediaTypes&& mediaTypes) { + static inline FeatureList GstRegistryGetElementForMediaType(GList* elementsFactories, MediaTypes& mediaTypes) { FeatureList candidates{gst_element_factory_list_filter(elementsFactories, mediaTypes.get(), GST_PAD_SINK, false)}; return (candidates); @@ -117,11 +118,11 @@ class PlayerInfoImplementation : public Exchange::IPlayerProperties, public Exch public: PlayerInfoImplementation() { - gst_init(0, nullptr); + //currently gstreamer is not used. This init causes crash during reactivation of PlayerInfo plugin + //gst_init(0, nullptr); UpdateAudioCodecInfo(); UpdateVideoCodecInfo(); Utils::IARM::init(); - device::Manager::Initialize(); IARM_Result_t res; IARM_CHECK( IARM_Bus_RegisterEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_AUDIO_MODE, AudioModeHandler) ); PlayerInfoImplementation::_instance = this; @@ -269,6 +270,7 @@ class PlayerInfoImplementation : public Exchange::IPlayerProperties, public Exch else if(amode == device::AudioStereoMode::kStereo) mode = STEREO; else if(amode == device::AudioStereoMode::kMono) mode = MONO; else if(amode == device::AudioStereoMode::kPassThru) mode = PASSTHRU; + else if(amode == device::AudioStereoMode::kSurroundMatFollow) mode = STEREO_SURROUND_MAT_FOLLOW; else mode = UNKNOWN; PlayerInfoImplementation::_instance->audiomodeChanged(mode, true); } @@ -386,6 +388,7 @@ class PlayerInfoImplementation : public Exchange::IPlayerProperties, public Exch else if(soundmode == device::AudioStereoMode::kStereo) mode = STEREO; else if(soundmode == device::AudioStereoMode::kMono) mode = MONO; else if(soundmode == device::AudioStereoMode::kPassThru) mode = PASSTHRU; + else if(soundmode == device::AudioStereoMode::kSurroundMatFollow) mode = STEREO_SURROUND_MAT_FOLLOW; else mode = UNKNOWN; /* Auto mode applicable for HDMI Arc and SPDIF */ @@ -452,9 +455,20 @@ class PlayerInfoImplementation : public Exchange::IPlayerProperties, public Exch {"audio/x-vorbis", Exchange::IPlayerProperties::AUDIO_VORBIS_OGG}, {"audio/x-wav", Exchange::IPlayerProperties::AUDIO_WAV}, }; - if (GstUtils::GstRegistryCheckElementsForMediaTypes(audioCaps, _audioCodecs) != true) { - TRACE(Trace::Warning, (_T("There is no Audio Codec support available"))); - } + //hardcoded values - correct for ApolloV1+ + _audioCodecs.push_back(Exchange::IPlayerProperties::AudioCodec::AUDIO_MPEG1); + _audioCodecs.push_back(Exchange::IPlayerProperties::AudioCodec::AUDIO_MPEG3); + _audioCodecs.push_back(Exchange::IPlayerProperties::AudioCodec::AUDIO_MPEG2); + _audioCodecs.push_back(Exchange::IPlayerProperties::AudioCodec::AUDIO_MPEG4); + _audioCodecs.push_back(Exchange::IPlayerProperties::AudioCodec::AUDIO_AAC); + _audioCodecs.push_back(Exchange::IPlayerProperties::AUDIO_AC3); + _audioCodecs.push_back(Exchange::IPlayerProperties::AUDIO_AC3_PLUS); + _audioCodecs.push_back(Exchange::IPlayerProperties::AUDIO_OPUS); + _audioCodecs.push_back(Exchange::IPlayerProperties::AUDIO_VORBIS_OGG); + + //if (GstUtils::GstRegistryCheckElementsForMediaTypes(audioCaps, _audioCodecs) != true) { + // TRACE(Trace::Warning, (_T("There is no Audio Codec support available"))); + //} } void UpdateVideoCodecInfo() @@ -470,9 +484,16 @@ class PlayerInfoImplementation : public Exchange::IPlayerProperties, public Exch {"video/x-vp9", Exchange::IPlayerProperties::VideoCodec::VIDEO_VP9}, {"video/x-vp10", Exchange::IPlayerProperties::VideoCodec::VIDEO_VP10} }; - if (GstUtils::GstRegistryCheckElementsForMediaTypes(videoCaps, _videoCodecs) != true) { - TRACE(Trace::Warning, (_T("There is no Video Codec support available"))); - } + //hardcoded values - correct for ApolloV1+ + _videoCodecs.push_back(Exchange::IPlayerProperties::VideoCodec::VIDEO_MPEG); + _videoCodecs.push_back(Exchange::IPlayerProperties::VideoCodec::VIDEO_H264); + _videoCodecs.push_back(Exchange::IPlayerProperties::VideoCodec::VIDEO_H265); + _videoCodecs.push_back(Exchange::IPlayerProperties::VideoCodec::VIDEO_H265_10); + _videoCodecs.push_back(Exchange::IPlayerProperties::VideoCodec::VIDEO_VP9); + + //if (GstUtils::GstRegistryCheckElementsForMediaTypes(videoCaps, _videoCodecs) != true) { + // TRACE(Trace::Warning, (_T("There is no Video Codec support available"))); + //} } private: diff --git a/PlayerInfo/GStreamer/PlatformImplementation.cpp b/PlayerInfo/GStreamer/PlatformImplementation.cpp index 314ca4cf44..2230fa8784 100644 --- a/PlayerInfo/GStreamer/PlatformImplementation.cpp +++ b/PlayerInfo/GStreamer/PlatformImplementation.cpp @@ -58,10 +58,10 @@ class PlayerInfoImplementation : public Exchange::IPlayerProperties { for (auto index: caps) { MediaTypes mediaType{gst_caps_from_string(index.first.c_str())}; - if (elements = std::move(GstUtils::GstRegistryGetElementForMediaType(decoderFactories.get(), std::move(mediaType)))) { + if (elements = GstUtils::GstRegistryGetElementForMediaType(decoderFactories.get(), mediaType)) { codecIteratorList.push_back(index.second); - } else if (elements = std::move(GstUtils::GstRegistryGetElementForMediaType(parserFactories.get(), std::move(mediaType)))) { + } else if (elements = GstUtils::GstRegistryGetElementForMediaType(parserFactories.get(), mediaType)) { for (GList* iterator = elements.get(); iterator; iterator = iterator->next) { @@ -73,7 +73,7 @@ class PlayerInfoImplementation : public Exchange::IPlayerProperties { if (padTemplate->direction == GST_PAD_SRC) { MediaTypes mediaTypes{gst_static_pad_template_get_caps(padTemplate)}; - if (GstUtils::GstRegistryGetElementForMediaType(decoderFactories.get(), std::move(mediaTypes))) { + if (GstUtils::GstRegistryGetElementForMediaType(decoderFactories.get(), mediaTypes)) { codecIteratorList.push_back(index.second); } } @@ -86,7 +86,7 @@ class PlayerInfoImplementation : public Exchange::IPlayerProperties { } private: - static inline FeatureList GstRegistryGetElementForMediaType(GList* elementsFactories, MediaTypes&& mediaTypes) { + static inline FeatureList GstRegistryGetElementForMediaType(GList* elementsFactories, const MediaTypes& mediaTypes) { FeatureList candidates{gst_element_factory_list_filter(elementsFactories, mediaTypes.get(), GST_PAD_SINK, false)}; return (candidates); diff --git a/PlayerInfo/PlayerInfo.cpp b/PlayerInfo/PlayerInfo.cpp index aa2c4036d4..5e1a291ce0 100644 --- a/PlayerInfo/PlayerInfo.cpp +++ b/PlayerInfo/PlayerInfo.cpp @@ -139,7 +139,6 @@ namespace Plugin { _service->Release(); _service = nullptr; - _player = nullptr; _connectionId = 0; diff --git a/PlayerInfo/PlayerInfo.h b/PlayerInfo/PlayerInfo.h index 5d18787337..fb92962a4d 100644 --- a/PlayerInfo/PlayerInfo.h +++ b/PlayerInfo/PlayerInfo.h @@ -65,7 +65,7 @@ namespace Plugin { DolbyNotification& operator=(const DolbyNotification&) = delete; explicit DolbyNotification(PlayerInfo* parent) - : _parent(*parent) + : _parent(*parent), _client(nullptr) { ASSERT(parent != nullptr); } @@ -110,6 +110,7 @@ namespace Plugin { , _player(nullptr) , _audioCodecs(nullptr) , _videoCodecs(nullptr) + , _dolbyOut(nullptr) , _dolbyNotification(this) , _notification(this) , _service(nullptr) diff --git a/PlayerInfo/PlayerInfo.json b/PlayerInfo/PlayerInfo.json index 1b88f6c113..05fdc0463f 100644 --- a/PlayerInfo/PlayerInfo.json +++ b/PlayerInfo/PlayerInfo.json @@ -198,7 +198,8 @@ "Mono", "Stereo", "Surround", - "Passthru" + "Passthru", + "StereoSurroundMatFollow" ], "example": "Unknown" } @@ -240,7 +241,8 @@ "Mono", "Stereo", "Surround", - "Passthru" + "Passthru", + "StereoSurroundMatFollow" ], "example": "Unknown" }, diff --git a/SecurityAgent/AccessControlList.h b/SecurityAgent/AccessControlList.h index 9176a309df..0fcdb88764 100644 --- a/SecurityAgent/AccessControlList.h +++ b/SecurityAgent/AccessControlList.h @@ -24,9 +24,9 @@ #include // helper functions -namespace { +//namespace { - void ReplaceString(string& subject, const string& search,const string& replace) + void inline ReplaceString(string& subject, const string& search,const string& replace) { size_t pos = 0; while ((pos = subject.find(search, pos)) != string::npos) { @@ -35,7 +35,7 @@ namespace { } } - string CreateRegex(const string& input) + string inline CreateRegex(const string& input) { string regex = input; @@ -46,7 +46,7 @@ namespace { return regex; } - string CreateUrlRegex(const string& input) + string inline CreateUrlRegex(const string& input) { string regex = input; @@ -66,7 +66,7 @@ namespace { return regex; } - string GetUrlOrigin(const string& input) + string inline GetUrlOrigin(const string& input) { // see https://tools.ietf.org/html/rfc3986 auto path = input.find('/', input.find("//") + 2); @@ -78,7 +78,7 @@ namespace { return input.substr(0, end); } -} +//} namespace WPEFramework { namespace Plugin { diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index d310159a3f..3df6f1bce0 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -49,6 +49,7 @@ set_source_files_properties( tests/test_HdmiCecSource.cpp tests/test_FrontPanel.cpp tests/test_HdmiCecSink.cpp + tests/test_HdcpProfile.cpp PROPERTIES COMPILE_FLAGS "-fexceptions") include_directories(../LocationSync diff --git a/TraceControl/TraceControl.config b/TraceControl/TraceControl.config index 7745dc3f8c..13dfc0c2c0 100644 --- a/TraceControl/TraceControl.config +++ b/TraceControl/TraceControl.config @@ -1,4 +1,4 @@ -set(PLUGIN_TRACECONTROL_AUTOSTART true CACHE BOOL "Automatically start TraceControl plugin") +set(PLUGIN_TRACECONTROL_AUTOSTART ${PLUGIN_TRACECONTROL_AUTOSTART} CACHE BOOL "Automatically start TraceControl plugin") set(PLUGIN_TRACECONTROL_REMOTE false CACHE BOOL "Remote binding details enabled") set(PLUGIN_TRACECONTROL_PORT 0 CACHE STRING "PORT address") set(PLUGIN_TRACECONTROL_BINDING "0.0.0.0" CACHE STRING "Binding IP Address") diff --git a/TraceControl/TraceControl.cpp b/TraceControl/TraceControl.cpp index c7746b2abd..d0e927885c 100644 --- a/TraceControl/TraceControl.cpp +++ b/TraceControl/TraceControl.cpp @@ -171,7 +171,7 @@ namespace Plugin { } } - result->Body(Core::proxy_cast(response)); + result->Body(Core::ProxyType(response)); result->ContentType = Web::MIME_JSON; } else if ((request.Verb == Web::Request::HTTP_PUT) && (index.Next() == true)) { if ((index.Current() == _T("on")) || (index.Current() == _T("off"))) { diff --git a/TraceControl/TraceControl.h b/TraceControl/TraceControl.h index e36d12cc12..26b88b7883 100644 --- a/TraceControl/TraceControl.h +++ b/TraceControl/TraceControl.h @@ -29,7 +29,7 @@ namespace Plugin { class TraceControl : public PluginHost::IPlugin, public PluginHost::IWeb, public PluginHost::JSONRPC { public: - enum state { + enum state : int { ENABLED, DISABLED, TRISTATED @@ -696,10 +696,7 @@ namespace Plugin { if (state == Source::LOADED) { if (index->second->Timestamp() < timeStamp) { timeStamp = index->second->Timestamp(); - - if (state == Source::LOADED) { - selected = index->second; - } + selected = index->second; } } else if (state == Source::FAILURE) { // Oops this requires recovery, so let's flush diff --git a/TraceControl/doc/WPE - API - TraceControl.docx b/TraceControl/doc/WPE-API-TraceControl.docx similarity index 100% rename from TraceControl/doc/WPE - API - TraceControl.docx rename to TraceControl/doc/WPE-API-TraceControl.docx diff --git a/WebKitBrowser/BrowserConsoleLog.h b/WebKitBrowser/BrowserConsoleLog.h index 7017253d0c..3dceb736ec 100644 --- a/WebKitBrowser/BrowserConsoleLog.h +++ b/WebKitBrowser/BrowserConsoleLog.h @@ -20,7 +20,10 @@ #ifndef __BROWSERCONSOLELOG_H #define __BROWSERCONSOLELOG_H +#ifndef __CORE_MESSAGING__ #include +#endif + #ifndef WEBKIT_GLIB_API #include "InjectedBundle/Utils.h" #endif @@ -37,7 +40,7 @@ class BrowserConsoleLog { BrowserConsoleLog(const string& prefix, const string& message, const uint64_t line, const uint64_t column) { _text = '[' + prefix + "][" + Core::NumberType(line).Text() + ',' + Core::NumberType(column).Text() + ']' + message; - const uint16_t maxStringLength = Trace::TRACINGBUFFERSIZE - 1; + const uint16_t maxStringLength = Messaging::MessageUnit::DataSize - 1; if (_text.length() > maxStringLength) { _text = _text.substr(0, maxStringLength); } @@ -46,7 +49,7 @@ class BrowserConsoleLog { BrowserConsoleLog(const string& prefix, const WKStringRef message, const uint64_t line, const uint64_t column) { _text = '[' + prefix + "][" + Core::NumberType(line).Text() + ',' + Core::NumberType(column).Text() + ']' + WebKit::Utils::WKStringToString(message); - const uint16_t maxStringLength = Trace::TRACINGBUFFERSIZE - 1; + const uint16_t maxStringLength = Messaging::MessageUnit::DataSize - 1; if (_text.length() > maxStringLength) { _text = _text.substr(0, maxStringLength); } diff --git a/WebKitBrowser/CHANGELOG.md b/WebKitBrowser/CHANGELOG.md index e66ea52e7c..1eb2d8dfff 100644 --- a/WebKitBrowser/CHANGELOG.md +++ b/WebKitBrowser/CHANGELOG.md @@ -16,6 +16,10 @@ All notable changes to this RDK Service will be documented in this file. * For more details, refer to [versioning](https://github.com/rdkcentral/rdkservices#versioning) section under Main README. +## [1.1.17] - 2024-09-11 +### Added +- Config entry to enable/disable WebRTC ICE candidate filtering + ## [1.1.16] - 2023-07-25 ### Added - Initial support for WPEWebKit 2.38 diff --git a/WebKitBrowser/CMakeLists.txt b/WebKitBrowser/CMakeLists.txt index f8df78ffe7..81694b8ac5 100644 --- a/WebKitBrowser/CMakeLists.txt +++ b/WebKitBrowser/CMakeLists.txt @@ -51,6 +51,8 @@ option(PLUGIN_APPS_ENABLE_DFG "Enable the use of DFG javascript optimalization" option(PLUGIN_WEBKITBROWSER_CLOUD_COOKIEJAR "Enable support for exporting/importing cookie jar" OFF) option(PLUGIN_WEBKITBROWSER_LOGGING_UTILS "Enable possibility to redirect stdout/err to specific systemd service" OFF) +option(PLUGIN_WEBKITBROWSER_ODH_TELEMETRY "Enable ODH telemetry" ON) + set(PLUGIN_WEBKITBROWSER_IMPLEMENTATION "${MODULE_NAME}Impl" CACHE STRING "Specify a library with a webkit implementation." ) set(PLUGIN_WEBKITBROWSER_AUTOSTART "false" CACHE STRING "Automatically start WebKitBrowser plugin") @@ -62,8 +64,9 @@ set(PLUGIN_WEBKITBROWSER_STARTURL "about:blank" CACHE STRING "Initial URL for We set(PLUGIN_WEBKITBROWSER_USERAGENT "" CACHE STRING "User agent string") set(PLUGIN_WEBKITBROWSER_MEMORYPROFILE "512m" CACHE STRING "Memory Profile") set(PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_WEBPROCESSLIMIT "300" CACHE STRING "Memory Pressure Webprocess Limit") +set(PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_WEBPROCESSKILLTHRESHOLD "1.3" CACHE STRING "Memory Pressure Webprocess Kill Threshold") set(PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_NETWORKPROCESSLIMIT "100" CACHE STRING "Memory Pressure Networkprocess Limit") -set(PLUGIN_WEBKITBROWSER_MEDIA_CONTENT_TYPES_REQUIRING_HARDWARE_SUPPORT "video/*" CACHE STRING "Media content types requiring hardware support") +set(PLUGIN_WEBKITBROWSER_MEDIA_CONTENT_TYPES_REQUIRING_HARDWARE_SUPPORT "" CACHE STRING "Media content types requiring hardware support") set(PLUGIN_WEBKITBROWSER_MEDIADISKCACHE "false" CACHE STRING "Media Disk Cache") set(PLUGIN_WEBKITBROWSER_MSEBUFFERS "audio:2m,video:15m,text:1m" CACHE STRING "MSE Buffers for WebKit") set(PLUGIN_WEBKITBROWSER_DISKCACHE "0" CACHE STRING "Disk Cache") @@ -72,8 +75,9 @@ set(PLUGIN_WEBKITBROWSER_EXTENSION_DIRECTORY "Extension" CACHE STRING "Directory set(PLUGIN_WEBKITBROWSER_LOCALSTORAGE "" CACHE STRING "HTML5 local storage path") set(PLUGIN_WEBKITBROWSER_COOKIESTORAGE "" CACHE STRING "Browser cookie storage path") set(PLUGIN_WEBKITBROWSER_WINDOWCLOSE "false" CACHE STRING "Allow window close") +set(PLUGIN_WEBKITBROWSER_SUSPEND_ON_WINDOW_CLOSE "false" CACHE STRING "Allow move to suspend on window close") set(PLUGIN_WEBKITBROWSER_WEBGL "true" CACHE STRING "Enable WebGL") -set(PLUGIN_WEBKITBROWSER_RESOLUTION "720p" CACHE STRING "Browser resolution") +set(PLUGIN_WEBKITBROWSER_RESOLUTION "1080p" CACHE STRING "Browser resolution") set(PLUGIN_WEBKITBROWSER_THREADEDPAINTING "1" CACHE STRING "Threads for the Threaded Painting") set(PLUGIN_WEBKITBROWSER_WEBINSPECTOR_ADDRESS "0.0.0.0:9224" CACHE STRING "IP:Port for WebInspector of WebKitBrowser") set(PLUGIN_WEBKITBROWSER_LOCALSTORAGE_ENABLE "true" CACHE STRING "Enable LocalStorage of WebKitBrowser") @@ -82,6 +86,8 @@ set(PLUGIN_WEBKITBROWSER_PERSISTENTPATHPOSTFIX "" CACHE STRING "Specify callsign set(PLUGIN_WEBKITBROWSER_PTSOFFSET "0" CACHE STRING "Set ptsoffset for webkit") set(PLUGIN_WEBKITBROWSER_USE_EXACT_PATHS "false" CACHE STRING "Use paths specified in configuration options without further modifying them") +set(RDK6_SUPPORT "false" CACHE STRING "Support RDK6 API") + set(PLUGIN_YOUTUBE_AUTOSTART "false" CACHE STRING "Automatically start Youtube plugin") set(PLUGIN_YOUTUBE_STARTUPORDER "" CACHE STRING "To configure startup order of YouTube plugin") set(PLUGIN_YOUTUBE_MODE "Local" CACHE STRING "Controls if the plugin should run in its own process, in process or remote") @@ -119,9 +125,9 @@ set(PLUGIN_RESIDENT_APP_WEBINSPECTOR_ADDRESS ":::10000" CACHE STRING "IP:Port fo set(PLUGIN_RESIDENT_APP_STARTURL "about:blank" CACHE STRING "Initial URL for Resident App plugin") set(PLUGIN_RESIDENT_APP_LOCALSTORAGE_ENABLE "true" CACHE STRING "Enable LocalStorage of Resident App") set(PLUGIN_RESIDENT_APP_PERSISTENTPATHPOSTFIX "" CACHE STRING "Specify callsign persistent path postfix") +set(PLUGIN_RESIDENT_APP_COMPOSITOR "msaa" CACHE STRING "cairo compositor mode for Resident App") set(PLUGIN_RESIDENT_APP_MEMORYPRESSURE_WEBPROCESSLIMIT ${PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_WEBPROCESSLIMIT} CACHE STRING "Resident App Memory Pressure Webprocess Limit") set(PLUGIN_RESIDENT_APP_MEMORYPRESSURE_NETWORKPROCESSLIMIT ${PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_NETWORKPROCESSLIMIT} CACHE STRING "Resident App Memory Pressure Networkprocess Limit") -set(PLUGIN_RESIDENT_APP_COMPOSITOR "msaa" CACHE STRING "cairo compositor mode for Resident App") set(PLUGIN_SEARCH_AND_DISCOVERY_APP_AUTOSTART "false" CACHE STRING "Automatically start Search&Discovery App plugin") set(PLUGIN_SEARCH_AND_DISCOVERY_APP_STARTUPORDER "" CACHE STRING "To configure startup order of Search&Discovery App plugin") @@ -141,9 +147,9 @@ set(PLUGIN_HTML_APP_USERAGENT "${PLUGIN_WEBKITBROWSER_USERAGENT}" CACHE STRING " set(PLUGIN_HTML_APP_WEBINSPECTOR_ADDRESS ":::10001" CACHE STRING "IP:Port for WebInspector of Html App") set(PLUGIN_HTML_APP_LOCALSTORAGE_ENABLE "false" CACHE STRING "Enable LocalStorage of Html App") set(PLUGIN_HTML_APP_PERSISTENTPATHPOSTFIX "" CACHE STRING "Specify callsign persistent path postfix") +set(PLUGIN_HTML_APP_COMPOSITOR "noaa" CACHE STRING "cairo compositor mode for Html App") set(PLUGIN_HTML_APP_MEMORYPRESSURE_WEBPROCESSLIMIT ${PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_WEBPROCESSLIMIT} CACHE STRING "Html App Memory Pressure Webprocess Limit") set(PLUGIN_HTML_APP_MEMORYPRESSURE_NETWORKPROCESSLIMIT ${PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_NETWORKPROCESSLIMIT} CACHE STRING "Html App Memory Pressure Networkprocess Limit") -set(PLUGIN_HTML_APP_COMPOSITOR "noaa" CACHE STRING "cairo compositor mode for Html App") set(PLUGIN_LIGHTNING_APP_AUTOSTART "false" CACHE STRING "Automatically start Lightning App plugin") set(PLUGIN_LIGHTNING_APP_STARTUPORDER "" CACHE STRING "To configure startup order of Lightning App plugin") @@ -152,9 +158,9 @@ set(PLUGIN_LIGHTNING_APP_USERAGENT "${PLUGIN_WEBKITBROWSER_USERAGENT}" CACHE STR set(PLUGIN_LIGHTNING_APP_WEBINSPECTOR_ADDRESS ":::10002" CACHE STRING "IP:Port for WebInspector of Lightning App") set(PLUGIN_LIGHTNING_APP_LOCALSTORAGE_ENABLE "false" CACHE STRING "Enable LocalStorage of Lightning App") set(PLUGIN_LIGHTNING_APP_PERSISTENTPATHPOSTFIX "" CACHE STRING "Specify callsign persistent path postfix") +set(PLUGIN_LIGHTNING_APP_COMPOSITOR "noaa" CACHE STRING "cairo compositor mode for Lightning App") set(PLUGIN_LIGHTNING_APP_MEMORYPRESSURE_WEBPROCESSLIMIT ${PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_WEBPROCESSLIMIT} CACHE STRING "Lightning App Memory Pressure Webprocess Limit") set(PLUGIN_LIGHTNING_APP_MEMORYPRESSURE_NETWORKPROCESSLIMIT ${PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_NETWORKPROCESSLIMIT} CACHE STRING "Lightning App Memory Pressure Networkprocess Limit") -set(PLUGIN_LIGHTNING_APP_COMPOSITOR "noaa" CACHE STRING "cairo compositor mode for Lightning App") set(PLUGIN_JSPP_AUTOSTART "false" CACHE STRING "Automatically start JSPP plugin") set(PLUGIN_JSPP_STARTUPORDER "" CACHE STRING "To configure startup order of JSPP plugin") @@ -162,9 +168,9 @@ set(PLUGIN_JSPP_MODE "Local" CACHE STRING "Controls if the plugin should run in set(PLUGIN_JSPP_USERAGENT "${PLUGIN_WEBKITBROWSER_USERAGENT}" CACHE STRING "User agent string for JSPP") set(PLUGIN_JSPP_LOCALSTORAGE_ENABLE "false" CACHE STRING "Enable LocalStorage of JSPP") set(PLUGIN_JSPP_PERSISTENTPATHPOSTFIX "" CACHE STRING "Specify callsign persistent path postfix") +set(PLUGIN_JSPP_STARTURL "about:blank" CACHE STRING "JSPP default URL to use") set(PLUGIN_JSPP_MEMORYPRESSURE_WEBPROCESSLIMIT ${PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_WEBPROCESSLIMIT} CACHE STRING "JSPP Memory Pressure Webprocess Limit") set(PLUGIN_JSPP_MEMORYPRESSURE_NETWORKPROCESSLIMIT ${PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_NETWORKPROCESSLIMIT} CACHE STRING "JSPP Memory Pressure Networkprocess Limit") -set(PLUGIN_JSPP_STARTURL "about:blank" CACHE STRING "JSPP default URL to use") set(PLUGIN_AMAZON_AUTOSTART "false" CACHE STRING "Automatically start Amazon plugin") set(PLUGIN_AMAZON_STARTUPORDER "" CACHE STRING "To configure startup order of Amazon plugin") @@ -180,6 +186,8 @@ find_package(${NAMESPACE}Definitions REQUIRED) find_package(CompileSettingsDebug CONFIG REQUIRED) find_package(WPEWebKit REQUIRED) find_package(WPEBackend REQUIRED) +find_package(OpenSSL REQUIRED) +find_package(CURL REQUIRED) add_library(${MODULE_NAME} SHARED Module.cpp @@ -207,12 +215,30 @@ if(NOT WEBKIT_GLIB_API) target_sources(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE InjectedBundle/Utils.cpp) endif() +# This is a temporary solution. Normally we should leave this to the framework. +# Do not replicate for other plugins. +target_link_options(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE "-Wl,-z,nodelete") + target_link_libraries(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${NAMESPACE}Definitions::${NAMESPACE}Definitions WPEBackend::WPEBackend - WPEWebKit::WPEWebKit) + WPEWebKit::WPEWebKit + OpenSSL::Crypto + ${CURL_LIBRARIES}) + +target_include_directories(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE ${CURL_INCLUDE_DIRS}) + +if (PLUGIN_WEBKITBROWSER_ODH_TELEMETRY) + find_package(OdhErrTelemetry REQUIRED) + target_link_libraries(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} + PRIVATE + OdhErrTelemetry::OdhErrTelemetry) + target_compile_definitions(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} + PRIVATE + USE_ODH_TELEMETRY=1) +endif() if (PLUGIN_WEBKITBROWSER_CLOUD_COOKIEJAR) find_package(ZLIB REQUIRED) @@ -243,6 +269,15 @@ if(PLUGIN_WEBKITBROWSER_USE_EXACT_PATHS) target_compile_definitions(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE USE_EXACT_PATHS) endif() +if(RDK6_SUPPORT) + target_compile_definitions(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE RDK6_SUPPORT) + target_compile_definitions(${MODULE_NAME} PRIVATE RDK6_SUPPORT) +endif() + +if(PLUGIN_WEBKITBROWSER_CLIENT_CERT_KEY_PASSWD) + target_compile_definitions(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE CLIENT_CERTS_PRIV_KEY_PASSWD="${PLUGIN_WEBKITBROWSER_CLIENT_CERT_KEY_PASSWD}") +endif() + set_target_properties(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED YES diff --git a/WebKitBrowser/Extension/AAMPJSBindings.cpp b/WebKitBrowser/Extension/AAMPJSBindings.cpp index ff9e77aeac..d1bfe8e2db 100644 --- a/WebKitBrowser/Extension/AAMPJSBindings.cpp +++ b/WebKitBrowser/Extension/AAMPJSBindings.cpp @@ -106,7 +106,9 @@ void UnloadJSBindings(WebKitScriptWorld* world, WebKitFrame* frame) { // Just pass headers json to aamp plugin. SetHttpHeaders Called from RequestHeaders.cpp void SetHttpHeaders(const char * headerJson) { +#ifdef RDK6_SUPPORT aamp_SetPageHttpHeaders(headerJson); +#endif } } // namespace AAMP diff --git a/WebKitBrowser/Extension/CMakeLists.txt b/WebKitBrowser/Extension/CMakeLists.txt index 3af94589ca..b2e49403b2 100644 --- a/WebKitBrowser/Extension/CMakeLists.txt +++ b/WebKitBrowser/Extension/CMakeLists.txt @@ -30,7 +30,8 @@ option(PLUGIN_WEBKITBROWSER_CUSTOM_PROCESS_INFO "Enable set of custom process in add_library(${MODULE_NAME} SHARED main.cpp - WhiteListedOriginDomainsList.cpp + CORSWhiteListedOriginDomainsList.cpp + MixedContentWhiteListedOriginDomainsList.cpp Milestone.cpp NotifyWPEFramework.cpp RequestHeaders.cpp diff --git a/WebKitBrowser/Extension/WhiteListedOriginDomainsList.cpp b/WebKitBrowser/Extension/CORSWhiteListedOriginDomainsList.cpp similarity index 62% rename from WebKitBrowser/Extension/WhiteListedOriginDomainsList.cpp rename to WebKitBrowser/Extension/CORSWhiteListedOriginDomainsList.cpp index 337d11ab68..3148cf86d7 100644 --- a/WebKitBrowser/Extension/WhiteListedOriginDomainsList.cpp +++ b/WebKitBrowser/Extension/CORSWhiteListedOriginDomainsList.cpp @@ -17,7 +17,7 @@ * limitations under the License. */ -#include "WhiteListedOriginDomainsList.h" +#include "CORSWhiteListedOriginDomainsList.h" using std::unique_ptr; using std::vector; @@ -25,51 +25,39 @@ using std::vector; namespace WPEFramework { namespace WebKit { - // Parses JSON containing white listed CORS origin-domain pairs. - static void ParseWhiteList(const string& jsonString, WhiteListedOriginDomainsList::WhiteMap& info) + CORSWhiteListedOriginDomainsList::CORSWhiteListedOriginDomainsList(const char* jsonString) { // Origin/Domain pair stored in JSON string. - class JSONEntry : public Core::JSON::Container { + class CORSJSONEntry : public JSONEntry { private: - JSONEntry& operator=(const JSONEntry&) = delete; + CORSJSONEntry& operator=(const CORSJSONEntry&) = delete; public: - JSONEntry() - : Core::JSON::Container() - , Origin() - , Domain() + CORSJSONEntry() + : JSONEntry() , SubDomain(true) { - Add(_T("origin"), &Origin); - Add(_T("domain"), &Domain); Add(_T("subdomain"), &SubDomain); } - JSONEntry(const JSONEntry& rhs) - : Core::JSON::Container() - , Origin(rhs.Origin) - , Domain(rhs.Domain) + CORSJSONEntry(const CORSJSONEntry& rhs) + : JSONEntry(rhs) , SubDomain(rhs.SubDomain) { - Add(_T("origin"), &Origin); - Add(_T("domain"), &Domain); Add(_T("subdomain"), &SubDomain); } - public: - Core::JSON::String Origin; - Core::JSON::ArrayType Domain; Core::JSON::Boolean SubDomain; }; - Core::JSON::ArrayType entries; + Core::JSON::ArrayType entries; entries.FromString(jsonString); - Core::JSON::ArrayType::Iterator originIndex(entries.Elements()); + Core::JSON::ArrayType::Iterator originIndex(entries.Elements()); while (originIndex.Next() == true) { if ((originIndex.Current().Origin.IsSet() == true) && (originIndex.Current().Domain.IsSet() == true)) { - WhiteListedOriginDomainsList::Domains& domains(info[originIndex.Current().Origin.Value()]); + CORSWhiteListedOriginDomainsList::Domains& domains(_whiteMap[originIndex.Current().Origin.Value()]); Core::JSON::ArrayType::Iterator domainIndex(originIndex.Current().Domain.Elements()); bool subDomain(originIndex.Current().SubDomain.Value()); @@ -81,15 +69,8 @@ namespace WebKit { } } - /* static */unique_ptr WhiteListedOriginDomainsList::Parse(const char* whitelist) - { - unique_ptr whiteList(new WhiteListedOriginDomainsList()); - ParseWhiteList(whitelist, whiteList->_whiteMap); - return whiteList; - } - // Adds stored entries to WebKit. - void WhiteListedOriginDomainsList::AddWhiteListToWebKit(WebKitWebExtension* extension) + void CORSWhiteListedOriginDomainsList::AddToWebKit(WebKitWebExtension* extension) { WhiteMap::const_iterator index(_whiteMap.begin()); diff --git a/WebKitBrowser/Extension/WhiteListedOriginDomainsList.h b/WebKitBrowser/Extension/CORSWhiteListedOriginDomainsList.h similarity index 57% rename from WebKitBrowser/Extension/WhiteListedOriginDomainsList.h rename to WebKitBrowser/Extension/CORSWhiteListedOriginDomainsList.h index 9ee1f0ed4d..d3cf02f864 100644 --- a/WebKitBrowser/Extension/WhiteListedOriginDomainsList.h +++ b/WebKitBrowser/Extension/CORSWhiteListedOriginDomainsList.h @@ -17,47 +17,33 @@ * limitations under the License. */ -#ifndef __WHITELISTEDORIGINDOMAINSLIST_H -#define __WHITELISTEDORIGINDOMAINSLIST_H +#ifndef __CORSWHITELISTEDORIGINDOMAINSLIST_H +#define __CORSWHITELISTEDORIGINDOMAINSLIST_H #include "Module.h" -#include -#include -#include -#include +#include "WhitelistBase.h" namespace WPEFramework { namespace WebKit { - class WhiteListedOriginDomainsList { + class CORSWhiteListedOriginDomainsList final : public WhitelistBase { + public: + CORSWhiteListedOriginDomainsList(const char* jsonString); + ~CORSWhiteListedOriginDomainsList() {} + + void AddToWebKit(WebKitWebExtension* extension); private: - WhiteListedOriginDomainsList(const WhiteListedOriginDomainsList&) = delete; - WhiteListedOriginDomainsList& operator=(const WhiteListedOriginDomainsList&) = delete; + CORSWhiteListedOriginDomainsList(const CORSWhiteListedOriginDomainsList&) = delete; + CORSWhiteListedOriginDomainsList& operator=(const CORSWhiteListedOriginDomainsList&) = delete; - public: typedef std::pair Domain; typedef std::vector Domains; typedef std::map WhiteMap; - public: - static std::unique_ptr Parse(const char* whitelist); - - ~WhiteListedOriginDomainsList() - { - } - - public: - void AddWhiteListToWebKit(WebKitWebExtension* extension); - - private: - WhiteListedOriginDomainsList() - { - } - WhiteMap _whiteMap; }; } } -#endif // __WHITELISTEDORIGINDOMAINSLIST_H +#endif // __CORSWHITELISTEDORIGINDOMAINSLIST_H diff --git a/WebKitBrowser/Extension/MixedContentWhiteListedOriginDomainsList.cpp b/WebKitBrowser/Extension/MixedContentWhiteListedOriginDomainsList.cpp new file mode 100644 index 0000000000..0b37c1e105 --- /dev/null +++ b/WebKitBrowser/Extension/MixedContentWhiteListedOriginDomainsList.cpp @@ -0,0 +1,41 @@ +#include "MixedContentWhiteListedOriginDomainsList.h" + +using std::unique_ptr; +using std::vector; + +namespace WPEFramework { +namespace WebKit { + + MixedContentWhiteListedOriginDomainsList::MixedContentWhiteListedOriginDomainsList(const char* jsonString) + { + Core::JSON::ArrayType entries; + entries.FromString(jsonString); + Core::JSON::ArrayType::Iterator originIndex(entries.Elements()); + + while (originIndex.Next() == true) { + if ((originIndex.Current().Origin.IsSet() == true) && (originIndex.Current().Domain.IsSet() == true)) { + MixedContentWhiteListedOriginDomainsList::Domains& domains(_whiteMap[originIndex.Current().Origin.Value()]); + + Core::JSON::ArrayType::Iterator domainIndex(originIndex.Current().Domain.Elements()); + + while (domainIndex.Next()) { + domains.emplace_back(domainIndex.Current().Value()); + } + } + } + } + + // Adds stored entries to WebKit. + void MixedContentWhiteListedOriginDomainsList::AddToWebKit(WebKitWebExtension* extension) + { + auto it = _whiteMap.begin(); + + while (it != _whiteMap.end()) { + for (const std::string& domain : it->second) { + webkit_web_extension_add_mixed_content_whitelist_entry(extension, it->first.c_str(), domain.c_str()); + } + it++; + } + } +} +} diff --git a/WebKitBrowser/Extension/MixedContentWhiteListedOriginDomainsList.h b/WebKitBrowser/Extension/MixedContentWhiteListedOriginDomainsList.h new file mode 100644 index 0000000000..092d23c472 --- /dev/null +++ b/WebKitBrowser/Extension/MixedContentWhiteListedOriginDomainsList.h @@ -0,0 +1,27 @@ +#ifndef __MIXEDCONTENTWHITELISTEDORIGINDOMAINSLIST_H +#define __MIXEDCONTENTWHITELISTEDORIGINDOMAINSLIST_H + +#include "Module.h" + +#include "WhitelistBase.h" + +namespace WPEFramework { +namespace WebKit { + + class MixedContentWhiteListedOriginDomainsList final : public WhitelistBase { + public: + MixedContentWhiteListedOriginDomainsList(const char* jsonString); + ~MixedContentWhiteListedOriginDomainsList() {} + + void AddToWebKit(WebKitWebExtension* extension); + private: + MixedContentWhiteListedOriginDomainsList(const MixedContentWhiteListedOriginDomainsList&) = delete; + MixedContentWhiteListedOriginDomainsList& operator=(const MixedContentWhiteListedOriginDomainsList&) = delete; + + typedef std::vector Domains; + std::map _whiteMap; + }; +} +} + +#endif // __MIXEDCONTENTWHITELISTEDORIGINDOMAINSLIST_H diff --git a/WebKitBrowser/Extension/Module.h b/WebKitBrowser/Extension/Module.h index d56663c027..9231ec7682 100644 --- a/WebKitBrowser/Extension/Module.h +++ b/WebKitBrowser/Extension/Module.h @@ -25,7 +25,10 @@ #include #include + +#ifndef __CORE_MESSAGING__ #include +#endif #undef EXTERNAL #define EXTERNAL diff --git a/WebKitBrowser/Extension/WhitelistBase.h b/WebKitBrowser/Extension/WhitelistBase.h new file mode 100644 index 0000000000..c9c036b536 --- /dev/null +++ b/WebKitBrowser/Extension/WhitelistBase.h @@ -0,0 +1,56 @@ +#ifndef __WHITELISTBASE_H +#define __WHITELISTBASE_H + +#include "Module.h" + +#include +#include +#include +#include + +namespace WPEFramework { +namespace WebKit { + + // Origin/Domain pair stored in JSON string. + class JSONEntry : public Core::JSON::Container { + private: + JSONEntry& operator=(const JSONEntry&) = delete; + + public: + JSONEntry() + : Core::JSON::Container() + , Origin() + , Domain() + { + Add(_T("origin"), &Origin); + Add(_T("domain"), &Domain); + } + + JSONEntry(const JSONEntry& rhs) + : Core::JSON::Container() + , Origin(rhs.Origin) + , Domain(rhs.Domain) + { + Add(_T("origin"), &Origin); + Add(_T("domain"), &Domain); + } + + virtual ~JSONEntry() {} + + Core::JSON::String Origin; + Core::JSON::ArrayType Domain; + }; + + class WhitelistBase { + public: + virtual ~WhitelistBase() {} + virtual void AddToWebKit(WebKitWebExtension* extension) = 0; + + private: + WhitelistBase& operator=(const JSONEntry&) = delete; + }; + +} +} + +#endif // __WHITELISTBASE_H diff --git a/WebKitBrowser/Extension/main.cpp b/WebKitBrowser/Extension/main.cpp index 58d28d2b45..77cf98143e 100644 --- a/WebKitBrowser/Extension/main.cpp +++ b/WebKitBrowser/Extension/main.cpp @@ -31,7 +31,8 @@ #include "Milestone.h" #include "NotifyWPEFramework.h" #include "RequestHeaders.h" -#include "WhiteListedOriginDomainsList.h" +#include "CORSWhiteListedOriginDomainsList.h" +#include "MixedContentWhiteListedOriginDomainsList.h" #ifdef ENABLE_SECURITY_AGENT #include "SecurityAgent.h" @@ -88,19 +89,20 @@ static class PluginHost { // We have something to report back, do so... uint32_t result = _comClient->Open(RPC::CommunicationTimeOut); if (result != Core::ERROR_NONE) { - TRACE(Trace::Error, (_T("Could not open connection to node %s. Error: %s"), _comClient->Source().RemoteId(), Core::NumberType(result).Text())); + TRACE(Trace::Error, (_T("Could not open connection to node %s. Error: %s"), _comClient->Source().RemoteId().c_str(), Core::NumberType(result).Text().c_str())); } else { // Due to the LXC container support all ID's get mapped. For the TraceBuffer, use the host given ID. - Trace::TraceUnit::Instance().Open(_comClient->ConnectionId()); + Messaging::MessageUnit::Instance().Open(_comClient->ConnectionId()); } _extension = WEBKIT_WEB_EXTENSION(g_object_ref(extension)); _logToSystemConsoleEnabled = FALSE; const char *uid; - const char *whitelist; + const char *CORSWhitelistJSON; + const char *mixedContentJSON; - g_variant_get((GVariant*) userData, "(&sm&sb)", &uid, &whitelist, &_logToSystemConsoleEnabled); + g_variant_get((GVariant*) userData, "(&sm&sbm&s)", &uid, &CORSWhitelistJSON, &_logToSystemConsoleEnabled, &mixedContentJSON); if (_logToSystemConsoleEnabled && Core::SystemInfo::GetEnvironment(string(_T("CLIENT_IDENTIFIER")), _consoleLogPrefix)) _consoleLogPrefix = _consoleLogPrefix.substr(0, _consoleLogPrefix.find(',')); @@ -117,11 +119,14 @@ static class PluginHost { G_CALLBACK(pageCreatedCallback), this); - if (whitelist != nullptr) { - auto list = WebKit::WhiteListedOriginDomainsList::Parse(whitelist); - if (list) { - list->AddWhiteListToWebKit(extension); - } + if (CORSWhitelistJSON != nullptr) { + WebKit::CORSWhiteListedOriginDomainsList whitelist(CORSWhitelistJSON); + whitelist.AddToWebKit(extension); + } + + if (mixedContentJSON != nullptr) { + WebKit::MixedContentWhiteListedOriginDomainsList whitelist(mixedContentJSON); + whitelist.AddToWebKit(extension); } #if defined(UPDATE_TZ_FROM_FILE) @@ -134,6 +139,8 @@ static class PluginHost { void Deinitialize() { + Messaging::MessageUnit::Instance().Close(); + #if defined(UPDATE_TZ_FROM_FILE) _tzSupport.Deinitialize(); #endif @@ -189,8 +196,8 @@ static class PluginHost { { string messageString = Core::ToString(webkit_console_message_get_text(message)); uint64_t line = static_cast(webkit_console_message_get_line(message)); - - TRACE_GLOBAL(BrowserConsoleLog, (host->_consoleLogPrefix, messageString, line, 0)); + const gchar* src = webkit_console_message_get_source_id(message); + TRACE_GLOBAL(Trace::Warning, (_T("consoleMessageSentCallback-> %s:%llu messageString: %s"), src, line, messageString.c_str())); } static gboolean userMessageReceivedCallback(WebKitWebPage* page, WebKitUserMessage* message) { diff --git a/WebKitBrowser/HTML5Notification.h b/WebKitBrowser/HTML5Notification.h index d151bf204d..76685dd2b8 100644 --- a/WebKitBrowser/HTML5Notification.h +++ b/WebKitBrowser/HTML5Notification.h @@ -20,7 +20,9 @@ #ifndef __HTML5NOTIFICATION_H #define __HTML5NOTIFICATION_H +#ifndef __CORE_MESSAGING__ #include +#endif using namespace WPEFramework; diff --git a/WebKitBrowser/InjectedBundle/main.cpp b/WebKitBrowser/InjectedBundle/main.cpp index 14880435f3..5ca674050d 100644 --- a/WebKitBrowser/InjectedBundle/main.cpp +++ b/WebKitBrowser/InjectedBundle/main.cpp @@ -108,7 +108,7 @@ static class PluginHost { TRACE(Trace::Error, (_T("Could not open connection to node %s. Error: %s"), _comClient->Source().RemoteId().c_str(), Core::NumberType(result).Text().c_str())); } else { // Due to the LXC container support all ID's get mapped. For the TraceBuffer, use the host given ID. - Trace::TraceUnit::Instance().Open(_comClient->ConnectionId()); + Messaging::MessageUnit::Instance().Open(_comClient->ConnectionId()); } _whiteListedOriginDomainPairs = WhiteListedOriginDomainsList::RequestFromWPEFramework(); @@ -119,6 +119,8 @@ static class PluginHost { void Deinitialize() { + Messaging::MessageUnit::Instance().Close(); + #if defined(UPDATE_TZ_FROM_FILE) _tzSupport.Deinitialize(); #endif @@ -337,7 +339,7 @@ static WKBundlePageUIClientV4 s_pageUIClient = { uint32_t columnNumber, WKStringRef url, const void* clientInfo) { auto prepareMessage = [&]() { string messageString = WebKit::Utils::WKStringToString(message); - const uint16_t maxStringLength = Trace::TRACINGBUFFERSIZE - 1; + const uint16_t maxStringLength = Messaging::MessageUnit::DataSize - 1; if (messageString.length() > maxStringLength) { messageString = messageString.substr(0, maxStringLength); } diff --git a/WebKitBrowser/WebKitBrowser.config b/WebKitBrowser/WebKitBrowser.config index 609d2b5fbd..144b5feb11 100644 --- a/WebKitBrowser/WebKitBrowser.config +++ b/WebKitBrowser/WebKitBrowser.config @@ -16,18 +16,26 @@ if(PLUGIN_WEBKITBROWSER_PERSISTENTPATHPOSTFIX) endif() map() - kv(mode ${PLUGIN_WEBKITBROWSER_MODE}) + kv(outofprocess true) kv(locator lib${PLUGIN_WEBKITBROWSER_IMPLEMENTATION}.so) + kv(mode "Container") if(PLUGIN_WEBKITBROWSER_USER) kv(user ${PLUGIN_WEBKITBROWSER_USER}) endif() if(PLUGIN_WEBKITBROWSER_GROUP) kv(group ${PLUGIN_WEBKITBROWSER_GROUP}) endif() + key(configuration) + map() + kv(awc_container "proxy") + end() end() ans(rootobject) map() + if(PLUGIN_WEBKITBROWSER_BROWSERVERSION) + kv(browserversion ${PLUGIN_WEBKITBROWSER_BROWSERVERSION}) + endif() kv(url ${PLUGIN_WEBKITBROWSER_STARTURL}) if(PLUGIN_WEBKITBROWSER_USERAGENT) semicolon_safe_string(PLUGIN_WEBKITBROWSER_USERAGENT) @@ -41,6 +49,7 @@ map() kv(transparent ${PLUGIN_WEBKITBROWSER_TRANSPARENT}) kv(compositor "noaa") kv(inspector ${PLUGIN_WEBKITBROWSER_WEBINSPECTOR_ADDRESS}) + kv(inspectorport ${PLUGIN_WEBKITBROWSER_WEBINSPECTOR_PORT}) kv(fps true) kv(cursor false) kv(touch false) @@ -56,6 +65,9 @@ map() kv(xhrcache ${PLUGIN_WEBKITBROWSER_XHRCACHE}) kv(webgl ${PLUGIN_WEBKITBROWSER_WEBGL}) kv(threadedpainting ${PLUGIN_WEBKITBROWSER_THREADEDPAINTING}) + kv(gstdebug "3,GST_STATES:3,GST_PIPELINE:4,GST_EVENT:4,GST_BUS:4,webkit*:5,brcm*:5,westerossink*:5") + kv(webkitdebug "MediaSource,Media,MemoryPressure") + kv(gstnocolor true) if(PLUGIN_WEBKITBROWSER_HEIGHT) kv(height ${PLUGIN_WEBKITBROWSER_HEIGHT}) endif() @@ -84,6 +96,9 @@ map() if(PLUGIN_WEBKITBROWSER_WINDOWCLOSE) kv(windowclose ${PLUGIN_WEBKITBROWSER_WINDOWCLOSE}) endif() + if(PLUGIN_WEBKITBROWSER_SUSPEND_ON_WINDOW_CLOSE) + kv(allowmovetosuspendonwindowclose ${PLUGIN_WEBKITBROWSER_SUSPEND_ON_WINDOW_CLOSE}) + endif() if(PLUGIN_WEBKITBROWSER_ALTERNATIVE_EXEC_PATH) kv(execpath ${PLUGIN_WEBKITBROWSER_ALTERNATIVE_EXEC_PATH}) endif() @@ -97,6 +112,9 @@ map() kv(clientcert ${PLUGIN_WEBKITBROWSER_CLIENT_CERT}) kv(clientcertkey ${PLUGIN_WEBKITBROWSER_CLIENT_CERT_KEY}) endif() + if(PLUGIN_WEBKITBROWSER_CLIENT_CERTS_INITIAL_CONF) + kv(clientcertsinitialconf ${PLUGIN_WEBKITBROWSER_CLIENT_CERTS_INITIAL_CONF}) + endif() if(PLUGIN_WEBKITBROWSER_LOGTOSYSTEMCONSOLE) kv(logtosystemconsoleenabled ${PLUGIN_WEBKITBROWSER_LOGTOSYSTEMCONSOLE}) endif() @@ -105,6 +123,9 @@ map() endif() kv(watchdogchecktimeoutinseconds 10) kv(watchdoghangthresholdtinseconds 60) + kv(webaudio true) + kv(mixedcontentwhitelist "[{\"origin\":\"https://*\", \"domain\":[\"ws://127.0.0.1:10415\", \"ws://127.0.0.1:10016\", \"ws://localhost:10415\", \"ws://localhost:10016\", \"http://127.0.0.1:83\", \"http://127.0.0.1:10414\", \"http://localhost:83\", \"http://localhost:10414\"]}, {\"origin\":\"https://widgets.metrological.com*\", \"domain\":[\"http://tv.metrological.api.radioline.fr*\"]}]") + kv(icecandidatefiltering false) end() ans(configuration) @@ -129,6 +150,9 @@ map() if(PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_WEBPROCESSLIMIT) kv(webprocesslimit ${PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_WEBPROCESSLIMIT}) endif() +if(PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_WEBPROCESSKILLTHRESHOLD) + kv(webprocesskillthreshold ${PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_WEBPROCESSKILLTHRESHOLD}) +endif() if(PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_NETWORKPROCESSLIMIT) kv(networkprocesslimit ${PLUGIN_WEBKITBROWSER_MEMORYPRESSURE_NETWORKPROCESSLIMIT}) endif() diff --git a/WebKitBrowser/WebKitBrowser.cpp b/WebKitBrowser/WebKitBrowser.cpp index 455ab7e2bf..c815f54fe1 100644 --- a/WebKitBrowser/WebKitBrowser.cpp +++ b/WebKitBrowser/WebKitBrowser.cpp @@ -21,7 +21,7 @@ #define API_VERSION_NUMBER_MAJOR 1 #define API_VERSION_NUMBER_MINOR 1 -#define API_VERSION_NUMBER_PATCH 16 +#define API_VERSION_NUMBER_PATCH 17 namespace WPEFramework { @@ -115,6 +115,13 @@ namespace Plugin { RegisterAll(); Exchange::JWebBrowser::Register(*this, _browser); + _browserResources = _browser->QueryInterface(); + if (!_browserResources) { + TRACE(Trace::Error, (_T("Failed to get IBrowserResources interface"))); + } else { + Exchange::JBrowserResources::Register(*this, _browserResources); + } +#ifdef RDK6_SUPPORT _cookieJar = _browser->QueryInterface(); if (_cookieJar) { _cookieJar->Register(&_notification); @@ -125,6 +132,13 @@ namespace Plugin { if (_browserScripting) { Exchange::JBrowserScripting::Register(*this, _browserScripting); } +#endif + _browserSecurity = _browser->QueryInterface(); + if (!_browserSecurity) { + TRACE(Trace::Error, (_T("Failed to get IBrowserSecurity interface"))); + } else { + Exchange::JBrowserSecurity::Register(*this, _browserSecurity); + } } return message; @@ -146,6 +160,7 @@ namespace Plugin { _memory->Release(); _application->Release(); Exchange::JWebBrowser::Unregister(*this); +#ifdef RDK6_SUPPORT if (_browserScripting) { Exchange::JBrowserScripting::Unregister(*this); _browserScripting->Release(); @@ -155,6 +170,19 @@ namespace Plugin { _cookieJar->Unregister(&_notification); _cookieJar->Release(); } +#endif + if(_browserResources) { + Exchange::JBrowserResources::Unregister(*this); + _browserResources->Release(); + _browserResources = nullptr; + } + + if (_browserSecurity) { + Exchange::JBrowserSecurity::Unregister(*this); + _browserSecurity->Release(); + _browserSecurity = nullptr; + } + UnregisterAll(); PluginHost::IStateControl* stateControl(_browser->QueryInterface()); @@ -212,7 +240,7 @@ namespace Plugin { Core::ProxyType result(PluginHost::IFactories::Instance().Response()); Core::TextSegmentIterator index( - Core::TextFragment(request.Path, _skipURL, request.Path.length() - _skipURL), false, '/'); + Core::TextFragment(request.Path, static_cast(_skipURL), static_cast(request.Path.length() - _skipURL)), false, '/'); result->ErrorCode = Web::STATUS_BAD_REQUEST; result->Message = "Unknown error"; @@ -279,7 +307,11 @@ namespace Plugin { if (path.empty() == false) { string fullPath = _persistentStoragePath + path; Core::Directory dir(fullPath.c_str()); +#ifdef RDK6_SUPPORT + if (!dir.Destroy()) { +#else if (!dir.Destroy(true)) { +#endif TRACE(Trace::Error, (_T("Failed to delete %s\n"), fullPath.c_str())); result = Core::ERROR_GENERAL; } @@ -335,7 +367,9 @@ namespace Plugin { void WebKitBrowser::CookieJarChanged() { +#ifdef RDK6_SUPPORT Exchange::JBrowserCookieJar::Event::CookieJarChanged(*this); +#endif } void WebKitBrowser::StateChange(const PluginHost::IStateControl::state state) @@ -454,7 +488,11 @@ namespace WebKitBrowser { _children = Core::ProcessInfo::Iterator(_main.Id()); return ((_startTime == TimePoint::min()) || (_main.IsActive() == true) ? 1 : 0) + _children.Count(); } +#ifdef RDK6_SUPPORT + bool IsOperational() const override +#else const bool IsOperational() const override +#endif { uint32_t requiredProcesses = 0; diff --git a/WebKitBrowser/WebKitBrowser.h b/WebKitBrowser/WebKitBrowser.h index c2cad5f920..4583a9721b 100644 --- a/WebKitBrowser/WebKitBrowser.h +++ b/WebKitBrowser/WebKitBrowser.h @@ -28,9 +28,13 @@ #include #include #include +#include #include +#include +#ifdef RDK6_SUPPORT #include #include +#endif namespace WPEFramework { @@ -72,8 +76,11 @@ namespace Plugin { class Notification : public RPC::IRemoteConnection::INotification, public PluginHost::IStateControl::INotification, - public Exchange::IWebBrowser::INotification, - public Exchange::IBrowserCookieJar::INotification { + public Exchange::IWebBrowser::INotification + #ifdef RDK6_SUPPORT + , public Exchange::IBrowserCookieJar::INotification + #endif + { private: Notification() = delete; Notification(const Notification&) = delete; @@ -125,16 +132,20 @@ namespace Plugin { { _parent.Deactivated(connection); } +#ifdef RDK6_SUPPORT void CookieJarChanged() override { _parent.CookieJarChanged(); } +#endif BEGIN_INTERFACE_MAP(Notification) INTERFACE_ENTRY(Exchange::IWebBrowser::INotification) INTERFACE_ENTRY(PluginHost::IStateControl::INotification) INTERFACE_ENTRY(RPC::IRemoteConnection::INotification) +#ifdef RDK6_SUPPORT INTERFACE_ENTRY(Exchange::IBrowserCookieJar::INotification) +#endif END_INTERFACE_MAP private: @@ -180,10 +191,14 @@ namespace Plugin { , _connectionId(0) , _service(nullptr) , _browser(nullptr) + , _browserSecurity(nullptr) , _memory(nullptr) , _application(nullptr) + , _browserResources(nullptr) +#ifdef RDK6_SUPPORT , _browserScripting(nullptr) , _cookieJar(nullptr) +#endif , _notification(this) , _jsonBodyDataFactory(2) { @@ -214,9 +229,13 @@ namespace Plugin { INTERFACE_AGGREGATE(Exchange::IBrowser, _browser) INTERFACE_AGGREGATE(Exchange::IApplication, _application) INTERFACE_AGGREGATE(Exchange::IWebBrowser, _browser) +#ifdef RDK6_SUPPORT INTERFACE_AGGREGATE(Exchange::IBrowserScripting, _browserScripting) INTERFACE_AGGREGATE(Exchange::IBrowserCookieJar, _cookieJar) +#endif + INTERFACE_AGGREGATE(Exchange::IBrowserSecurity, _browser) INTERFACE_AGGREGATE(Exchange::IMemory, _memory) + INTERFACE_AGGREGATE(Exchange::IBrowserResources, _browser) END_INTERFACE_MAP public: @@ -274,14 +293,18 @@ namespace Plugin { void event_statechange(const bool& suspended); // StateControl private: - uint8_t _skipURL; + size_t _skipURL; uint32_t _connectionId; PluginHost::IShell* _service; Exchange::IWebBrowser* _browser; + Exchange::IBrowserSecurity* _browserSecurity; Exchange::IMemory* _memory; Exchange::IApplication* _application; + Exchange::IBrowserResources* _browserResources; +#ifdef RDK6_SUPPORT Exchange::IBrowserScripting* _browserScripting; Exchange::IBrowserCookieJar* _cookieJar; +#endif Core::Sink _notification; Core::ProxyPoolType> _jsonBodyDataFactory; string _persistentStoragePath; diff --git a/WebKitBrowser/WebKitBrowserJsonRpc.cpp b/WebKitBrowser/WebKitBrowserJsonRpc.cpp index f600ab3596..04a81b6b5c 100644 --- a/WebKitBrowser/WebKitBrowserJsonRpc.cpp +++ b/WebKitBrowser/WebKitBrowserJsonRpc.cpp @@ -177,6 +177,7 @@ namespace Plugin { // - ERROR_NONE: Success uint32_t WebKitBrowser::get_cookiejar(CookieJarParamsData& response) const { +#ifdef RDK6_SUPPORT if (_cookieJar == nullptr) return Core::ERROR_UNAVAILABLE; @@ -194,6 +195,8 @@ namespace Plugin { } return result; +#endif + return 0; } // Property: cookiejar @@ -201,6 +204,7 @@ namespace Plugin { // - ERROR_NONE: Success uint32_t WebKitBrowser::set_cookiejar(const CookieJarParamsData& param) { +#ifdef RDK6_SUPPORT if (_cookieJar == nullptr) return Core::ERROR_UNAVAILABLE; @@ -209,6 +213,8 @@ namespace Plugin { const string& payload = param.Payload.Value(); return _cookieJar->CookieJar(version, checksum, payload); +#endif + return 0; } // Event: statechange - Signals a state change of the service diff --git a/WebKitBrowser/WebKitBrowserPlugin.json b/WebKitBrowser/WebKitBrowserPlugin.json index 7322539e29..32858fa3ed 100644 --- a/WebKitBrowser/WebKitBrowserPlugin.json +++ b/WebKitBrowser/WebKitBrowserPlugin.json @@ -13,6 +13,10 @@ "configuration": { "type": "object", "properties": { + "browserversion": { + "description": "Browser version specified in semver (https://semver.org) that is assigned by the team porting the browser, e.g. 2.22.0", + "type": "string" + }, "useragent": { "description": "The UserAgent used during communication with the web server", "type": "string" @@ -92,6 +96,23 @@ }, "required": [] }, + "mixedcontentwhitelist": { + "type": "object", + "properties": { + "origin": { + "description": "Origin domain allowed to access mixed content in domain, * can be used as a wildcard", + "type": "string" + }, + "domain": { + "description": "Mixed content domain allowed to access from origin, * can be used as wildcard", + "type": "array", + "items": { + "type": "string" + } + }, + }, + "required": [] + }, "localstorageenabled": { "summary": "Controls the local storage availability", "type": "boolean" diff --git a/WebKitBrowser/WebKitImplementation.cpp b/WebKitBrowser/WebKitImplementation.cpp index 8feef5cfb4..032f282960 100644 --- a/WebKitBrowser/WebKitImplementation.cpp +++ b/WebKitBrowser/WebKitImplementation.cpp @@ -17,7 +17,9 @@ * limitations under the License. */ +#include #include +#include #include #include #include @@ -26,6 +28,10 @@ #include "Module.h" +#include "odhlog.h" + +#include + #ifdef WEBKIT_GLIB_API #include #include "Tags.h" @@ -46,6 +52,9 @@ #include "BrowserConsoleLog.h" #include "Tags.h" +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -78,6 +87,11 @@ WK_EXPORT void WKPreferencesSetPageCacheEnabled(WKPreferencesRef preferences, bo #include "LoggingUtils.h" #endif +#include +#include +#include +#include + #if !WEBKIT_GLIB_API #define HAS_MEMORY_PRESSURE_SETTINGS_API 0 @@ -85,10 +99,183 @@ WK_EXPORT void WKPreferencesSetPageCacheEnabled(WKPreferencesRef preferences, bo #define HAS_MEMORY_PRESSURE_SETTINGS_API WEBKIT_CHECK_VERSION(2, 38, 0) #endif +#define HAS_SCREEN_HDR_API WEBKIT_CHECK_VERSION(2, 38, 0) +#if HAS_SCREEN_HDR_API +#include +#endif + +#define URL_LOAD_RESULT_TIMEOUT_MS (15 * 1000) + +#define GCCLEANER_PERIOD_SEC 4 +#define GCCLEANER_CLEANUP_TIMES 4 + +namespace +{ + static constexpr char ENV_WPE_CLIENT_CERTIFICATES_PATH[] = "/etc/ssl/private/"; + static constexpr char ENV_WPE_CLIENT_CERTIFICATES_URLS[] = "WPE_CLIENT_CERTIFICATES_URLS"; + static constexpr char ENV_URL_LIST_DELIMITER[] = "|"; // not allowed in URI + static constexpr char MSG_CLIENT_CERT_DELIMITER[] = "\r\n"; + + bool isASCIISpace(char c) + { + return c == ' ' || c == '\t'; + } + + void trimLeadingAndTrailingWs(std::string *s) + { + while (!s->empty() && isASCIISpace(s->front())) + { + s->erase(0, 1); + } + + while (!s->empty() && isASCIISpace(s->back())) + { + s->pop_back(); + } + } + + std::vector tokenize(const std::string &s, const char *delimiters) + { + auto lastPos = std::string::npos; + std::vector result; + auto pos = std::string::npos; + lastPos = 0; + do + { + pos = s.find_first_of(delimiters, lastPos); + if (pos != std::string::npos) + { + auto toAdd = s.substr(lastPos, pos - lastPos); + trimLeadingAndTrailingWs(&toAdd); + result.push_back(std::move(toAdd)); + // Skip more delimiters. + lastPos = s.find_first_not_of(delimiters, pos + 1); + } + else + { + auto toAdd = s.substr(lastPos); + trimLeadingAndTrailingWs(&toAdd); + // No more delimiters in the front so push the last bit. + result.push_back(std::move(toAdd)); + } + } while (pos != std::string::npos && lastPos != std::string::npos); + + return result; + } + + void inline replaceString(string &subject, const string &search, const string &replace) + { + size_t pos = 0; + while ((pos = subject.find(search, pos)) != string::npos) + { + subject.replace(pos, search.length(), replace); + pos += replace.length(); + } + } + + void inline decodeUrlCertPath(string &url_path) + { + replaceString(url_path, "%2f", "/"); + replaceString(url_path, "%2F", "/"); + } + + // based on + // Messenger/MessengerSecurity.cpp: string GetUrlOrigin(const string& input) + string getUrlOrigin(const string &input) + { + // see https://tools.ietf.org/html/rfc3986 + auto path = input.find('/', input.find("//") + 2); + auto fragment = input.rfind('#', path); + auto end = fragment == string::npos ? path : fragment; + auto query = input.rfind('?', end); + if (query != string::npos) + end = query; + return input.substr(0, end).append("/"); + } + + string normalizedHostNameInURL(const string& url) { + static thread_local CURLU *normalizedUrl = curl_url(); + string ret {url}; + char *normalizedCstr = nullptr; + CURLUcode ecode; + if (CURLUE_OK == (ecode = curl_url_set(normalizedUrl, CURLUPART_URL, url.c_str(), 0))) { + if (CURLUE_OK == (ecode = curl_url_get(normalizedUrl, CURLUPART_HOST, &normalizedCstr, 0))) { + ret = normalizedCstr; + } + } + if (ecode != CURLUE_OK) { + SYSLOG_GLOBAL(Logging::Error, (_T("Error normalizing url '%s' : %d. Will return the input."), url.c_str(), ecode)); + } + if (normalizedCstr) { + curl_free(normalizedCstr); + normalizedCstr = nullptr; + } + return ret; + } + + bool normalizedHostNamesInUrlsEqual(const string& url1, const string& url2) { + + std::string host1 = normalizedHostNameInURL(url1); + std::string host2 = normalizedHostNameInURL(url2); + + /*Case insensitive string comparison for host names in URLs*/ + std::transform(host1.begin(), host1.end(), host1.begin(), + [](unsigned char c){ return std::tolower(c); }); + std::transform(host2.begin(), host2.end(), host2.begin(), + [](unsigned char c){ return std::tolower(c); }); + + return host1 == host2; + } +} namespace WPEFramework { namespace Plugin { + std::string getMainConfigValue(const std::string& key) + { + std::string result; + + GDBusProxy* proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, + (GDBusProxyFlags) (G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS), + nullptr, + "com.lgi.rdk.as", + "/com/lgi/rdk/as/Configuration1", + "com.lgi.rdk.as.Configuration1", + nullptr, + nullptr); + if (proxy) { + GVariant* gv_result = g_dbus_proxy_call_sync(proxy, + "GetConfig", + g_variant_new("(s)", key.c_str()), + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + nullptr); + if (gv_result) { + if (g_variant_is_of_type(gv_result, G_VARIANT_TYPE_TUPLE) && 1 == g_variant_n_children(gv_result)) { + gchar* gchar_result; + g_variant_get(gv_result, "(s)", &gchar_result); + result.assign(gchar_result); + g_free(gchar_result); + // strip enclosing "" + if ('"' == result.front() && '"' == result.back()) { + result.assign(result, 1, result.size() - 2); + } + SYSLOG_GLOBAL(Logging::Notification, (_T("Config value for %s : %s"), key.c_str(), result.c_str())); + } else { + SYSLOG_GLOBAL(Logging::Error, (_T("Unexpected result type: %s"), g_variant_get_type_string(gv_result))); + } + g_variant_unref(gv_result); + } else { + SYSLOG_GLOBAL(Logging::Error, (_T("g_dbus_proxy_call_sync result is null!"))); + } + g_object_unref(proxy); + } else { + SYSLOG_GLOBAL(Logging::Error, (_T("Failed to get DBus proxy!"))); + } + return result; + } + #ifndef WEBKIT_GLIB_API static void onDidReceiveSynchronousMessageFromInjectedBundle(WKContextRef context, WKStringRef messageName, WKTypeRef messageBodyObj, WKTypeRef* returnData, const void* clientInfo); @@ -367,12 +554,20 @@ static GSourceFuncs _handlerIntervention = class WebKitImplementation : public Core::Thread, public Exchange::IBrowser, public Exchange::IWebBrowser, + public Exchange::IBrowserSecurity, public Exchange::IApplication, + #ifdef RDK6_SUPPORT public Exchange::IBrowserScripting, + #endif #if defined(ENABLE_CLOUD_COOKIE_JAR) public Exchange::IBrowserCookieJar, #endif - public PluginHost::IStateControl { + public PluginHost::IStateControl, + public Exchange::IBrowserResources +#if HAS_SCREEN_HDR_API + , public Exchange::IConnectionProperties::INotification +#endif + { public: class BundleConfig : public Core::JSON::Container { private: @@ -495,9 +690,11 @@ static GSourceFuncs _handlerIntervention = MemorySettings() : Core::JSON::Container() , WebProcessLimit() + , WebProcessKillThreshold() , NetworkProcessLimit() { Add(_T("webprocesslimit"), &WebProcessLimit); + Add(_T("webprocesskillthreshold"), &WebProcessKillThreshold); Add(_T("networkprocesslimit"), &NetworkProcessLimit); } ~MemorySettings() @@ -506,15 +703,20 @@ static GSourceFuncs _handlerIntervention = public: Core::JSON::DecUInt32 WebProcessLimit; + Core::JSON::Double WebProcessKillThreshold; Core::JSON::DecUInt32 NetworkProcessLimit; }; public: Config() : Core::JSON::Container() + , WebkitDebug() + , GstDebug() + , GstNoColor() , UserAgent() , URL(_T("http://www.google.com")) , Whitelist() + , MixedContentWhitelist() , PageGroup(_T("WPEPageGroup")) , CookieStorage() , CloudCookieJarEnabled(false) @@ -530,6 +732,7 @@ static GSourceFuncs _handlerIntervention = , Compositor() , Inspector() , InspectorNative() + , InspectorPort() , FPS(false) , Cursor(false) , Touch(false) @@ -546,6 +749,7 @@ static GSourceFuncs _handlerIntervention = , CertificateCheck(true) , ClientIdentifier() , AllowWindowClose(false) + , AllowMoveToSuspendOnWindowClose(false) , NonCompositedWebGLEnabled(false) , EnvironmentOverride(false) , Automation(false) @@ -575,10 +779,16 @@ static GSourceFuncs _handlerIntervention = , ContentFilter() , LoggingTarget() , WebAudioEnabled(false) + , ICECandidateFilteringEnabled() + , HDRRefreshDelay(1000) // Default to 1 second for HDR refresh delay { + Add(_T("webkitdebug"), &WebkitDebug); + Add(_T("gstdebug"), &GstDebug); + Add(_T("gstnocolor"), &GstNoColor); Add(_T("useragent"), &UserAgent); Add(_T("url"), &URL); Add(_T("whitelist"), &Whitelist); + Add(_T("mixedcontentwhitelist"), &MixedContentWhitelist); Add(_T("pagegroup"), &PageGroup); Add(_T("cookiestorage"), &CookieStorage); Add(_T("cloudcookiejarenabled"), &CloudCookieJarEnabled); @@ -594,6 +804,7 @@ static GSourceFuncs _handlerIntervention = Add(_T("compositor"), &Compositor); Add(_T("inspector"), &Inspector); Add(_T("inspectornative"), &InspectorNative); + Add(_T("inspectorport"), &InspectorPort); Add(_T("fps"), &FPS); Add(_T("cursor"), &Cursor); Add(_T("touch"), &Touch); @@ -611,6 +822,7 @@ static GSourceFuncs _handlerIntervention = Add(_T("javascript"), &JavaScript); Add(_T("clientidentifier"), &ClientIdentifier); Add(_T("windowclose"), &AllowWindowClose); + Add(_T("allowmovetosuspendonwindowclose"), &AllowMoveToSuspendOnWindowClose); Add(_T("noncompositedwebgl"), &NonCompositedWebGLEnabled); Add(_T("environmentoverride"), &EnvironmentOverride); Add(_T("automation"), &Automation); @@ -629,6 +841,7 @@ static GSourceFuncs _handlerIntervention = Add(_T("tcpkeepalive"), &TCPKeepAlive); Add(_T("clientcert"), &ClientCert); Add(_T("clientcertkey"), &ClientCertKey); + Add(_T("clientcertsinitialconf"), &ClientCertsInitialConf); Add(_T("logtosystemconsoleenabled"), &LogToSystemConsoleEnabled); Add(_T("watchdogchecktimeoutinseconds"), &WatchDogCheckTimeoutInSeconds); Add(_T("watchdoghangthresholdtinseconds"), &WatchDogHangThresholdInSeconds); @@ -641,15 +854,21 @@ static GSourceFuncs _handlerIntervention = Add(_T("contentfilter"), &ContentFilter); Add(_T("loggingtarget"), &LoggingTarget); Add(_T("webaudio"), &WebAudioEnabled); + Add(_T("icecandidatefiltering"), &ICECandidateFilteringEnabled); + Add(_T("hdrrefreshDelay"), &HDRRefreshDelay); } ~Config() { } public: + Core::JSON::String WebkitDebug; + Core::JSON::String GstDebug; + Core::JSON::Boolean GstNoColor; Core::JSON::String UserAgent; Core::JSON::String URL; Core::JSON::String Whitelist; + Core::JSON::String MixedContentWhitelist; Core::JSON::String PageGroup; Core::JSON::String CookieStorage; Core::JSON::Boolean CloudCookieJarEnabled; @@ -665,6 +884,7 @@ static GSourceFuncs _handlerIntervention = Core::JSON::String Compositor; Core::JSON::String Inspector; Core::JSON::Boolean InspectorNative; + Core::JSON::String InspectorPort; Core::JSON::Boolean FPS; Core::JSON::Boolean Cursor; Core::JSON::Boolean Touch; @@ -682,6 +902,7 @@ static GSourceFuncs _handlerIntervention = JavaScriptSettings JavaScript; Core::JSON::String ClientIdentifier; Core::JSON::Boolean AllowWindowClose; + Core::JSON::Boolean AllowMoveToSuspendOnWindowClose; Core::JSON::Boolean NonCompositedWebGLEnabled; Core::JSON::Boolean EnvironmentOverride; Core::JSON::Boolean Automation; @@ -700,6 +921,7 @@ static GSourceFuncs _handlerIntervention = Core::JSON::Boolean TCPKeepAlive; Core::JSON::String ClientCert; Core::JSON::String ClientCertKey; + Core::JSON::String ClientCertsInitialConf; Core::JSON::Boolean LogToSystemConsoleEnabled; Core::JSON::DecUInt16 WatchDogCheckTimeoutInSeconds; // How often to check main event loop for responsiveness Core::JSON::DecUInt16 WatchDogHangThresholdInSeconds; // The amount of time to give a process to recover before declaring a hang state @@ -712,6 +934,8 @@ static GSourceFuncs _handlerIntervention = Core::JSON::String ContentFilter; Core::JSON::String LoggingTarget; Core::JSON::Boolean WebAudioEnabled; + Core::JSON::Boolean ICECandidateFilteringEnabled; + Core::JSON::DecUInt16 HDRRefreshDelay; // Delay in miliseconds to refresh HDR support }; class HangDetector @@ -802,15 +1026,88 @@ static GSourceFuncs _handlerIntervention = HangDetector& operator=(const HangDetector&) = delete; }; + class GCCleaner + { + WebKitImplementation& _browser; + GSource* _timerSource { nullptr }; + + std::atomic_int gc_executed_since_last_start; + public: + + void start() { + if (_timerSource) { + TRACE(Trace::Information, (_T("GCCleaner already running; stop it first"))); + stop(); + } + + _timerSource = g_timeout_source_new_seconds ( GCCLEANER_PERIOD_SEC ); + + g_source_set_callback ( + _timerSource, + [](gpointer data) -> gboolean + { + if (static_cast(data)->timerCallback()) { + return G_SOURCE_CONTINUE; + } else { + return G_SOURCE_REMOVE; + } + }, + this, + nullptr + ); + g_source_attach ( _timerSource, _browser._context ); + } + + void stop() { + if (_timerSource) { + g_source_destroy(_timerSource); + g_source_unref(_timerSource); + _timerSource = nullptr; + if (gc_executed_since_last_start.load() == 0) { + // we want to run at least once + TRACE(Trace::Information, (_T("GCCleaner: stopping without at least one invocation; execute GC now"))); + webkit_web_context_garbage_collect_javascript_objects(webkit_web_view_get_context(_browser._view)); + } + gc_executed_since_last_start.store(0); + } + } + + bool timerCallback() + { + TRACE(Trace::Information, (_T("GCCleaner: running GC now"))); + webkit_web_context_garbage_collect_javascript_objects(webkit_web_view_get_context(_browser._view)); + ++gc_executed_since_last_start; + return gc_executed_since_last_start.load() < GCCLEANER_CLEANUP_TIMES; + } + + ~GCCleaner() + { + if (_timerSource) { + g_source_destroy(_timerSource); + g_source_unref(_timerSource); + _timerSource = nullptr; + } + } + + GCCleaner(WebKitImplementation& browser) + : _browser(browser), gc_executed_since_last_start(0) + { + } + + GCCleaner(const HangDetector&) = delete; + GCCleaner& operator=(const HangDetector&) = delete; + }; + private: WebKitImplementation(const WebKitImplementation&) = delete; WebKitImplementation& operator=(const WebKitImplementation&) = delete; + std::string _bootUrl; + public: WebKitImplementation() : Core::Thread(0, _T("WebKitBrowser")) , _config() - , _URL() , _dataPath() , _service(nullptr) , _headers() @@ -849,6 +1146,13 @@ static GSourceFuncs _handlerIntervention = , _unresponsiveReplyNum(0) , _frameCount(0) , _lastDumpTime(g_get_monotonic_time()) + , _userScripts() + , _userStyleSheets() +#if HAS_SCREEN_HDR_API + ,_displayInfoPlugin(nullptr) + ,_displayConnectionProps(nullptr) + ,_hdrSupported(false) +#endif // HAS_SCREEN_HDR_API { // Register an @Exit, in case we are killed, with an incorrect ref count !! if (atexit(CloseDown) != 0) { @@ -859,13 +1163,38 @@ static GSourceFuncs _handlerIntervention = // The WebKitBrowser (WPE) can only be instantiated once (it is a process wide singleton !!!!) ASSERT(implementation == nullptr); +#ifdef USE_ODH_TELEMETRY + // Initialize ODH reporting for WebKitBrowser + if (odh_error_report_init("WebKitBrowser")) { + TRACE(Trace::Error, (_T("Failed to initialize ODH reporting"))); + } else { + ODH_WARNING("WPE0010", WPE_CONTEXT, "ThunderWebKitBrowser started: %p, WebKit version: %d.%d.%d", + this, WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION); + } +#endif implementation = this; } ~WebKitImplementation() override { Block(); - +#ifdef USE_ODH_TELEMETRY + odh_error_report_deinit(ODH_ERROR_REPORT_DEINIT_MODE_DEFERRED); +#endif if (_loop != nullptr) { + + g_main_context_invoke( + _context, [](gpointer obj) -> gboolean { + WebKitImplementation* object = static_cast(obj); + #ifdef WEBKIT_GLIB_API + webkit_web_view_terminate_web_process(object->_view); + #else + TRACE(Trace::Information, (_T("WKPageTerminate *not* used"))); + // this should work for c api (this would need to be tested): + // WKPageTerminate(object->_page); + #endif + return G_SOURCE_REMOVE; + }, this); + if (g_main_loop_is_running(_loop) == FALSE) { g_main_context_invoke(_context, [](gpointer data) -> gboolean { g_main_loop_quit(reinterpret_cast(data)); @@ -879,10 +1208,33 @@ static GSourceFuncs _handlerIntervention = TRACE(Trace::Information, (_T("Bailed out before the end of the WPE main app was reached. %d"), 6000)); } +#if HAS_SCREEN_HDR_API + UnsubscribeHDRCapabilities(); +#endif + implementation = nullptr; } public: + uint32_t SecurityProfile(string& profile) const override + { + profile = "strict"; + return Core::ERROR_NONE; + } + uint32_t SecurityProfile(const string& profile) override { return Core::ERROR_NONE; } + + uint32_t MixedContentPolicy(MixedContentPolicyType& policy) const override + { + policy = MixedContentPolicyType::BLOCKED; + return Core::ERROR_NONE; + } + + uint32_t MixedContentPolicy(const MixedContentPolicyType policy) override + { + SYSLOG_GLOBAL(Logging::Notification, (_T("Mixed content policy has NOT been changed\n"))); + return Core::ERROR_NONE; + } + uint32_t HeaderList(string& headerlist) const override { _adminLock.Lock(); @@ -1216,6 +1568,148 @@ static GSourceFuncs _handlerIntervention = return Core::ERROR_NONE; } + uint32_t Headers(IStringIterator*& header) const override + { + return Core::ERROR_NONE; + } + + uint32_t Headers(IStringIterator* const header) override + { + return Core::ERROR_NONE; + } + + uint32_t UserScripts(IStringIterator*& uris) const override + { + _adminLock.Lock(); + uris = Core::Service::Create(_userScripts); + _adminLock.Unlock(); + return Core::ERROR_NONE; + } + + uint32_t UserScripts(IStringIterator* const uris) override + { + string entry; + std::vector userScriptsContent; + std::list userScriptsUris; + while (uris->Next(entry)) { + auto content = GetFileContent(entry); + if (!content.empty()) { + userScriptsUris.push_back(entry); + userScriptsContent.push_back(content); + } + TRACE_L1("Adding user's script (uri: %s, empty: %d)", entry.c_str(), content.empty()); + } + using SetUserScriptsData = std::tuple, std::vector>; + auto* data = new SetUserScriptsData(this, userScriptsUris, userScriptsContent); + + g_main_context_invoke_full( + _context, + G_PRIORITY_DEFAULT, + [](gpointer customdata) -> gboolean { + auto& data = *static_cast(customdata); + WebKitImplementation* object = std::get<0>(data); + std::list scriptsUris = std::get<1>(data); + std::vector scriptsContent = std::get<2>(data); + + object->_adminLock.Lock(); + object->_userScripts = scriptsUris; + object->_adminLock.Unlock(); + +#ifdef WEBKIT_GLIB_API + auto* userContentManager = webkit_web_view_get_user_content_manager(object->_view); + webkit_user_content_manager_remove_all_scripts(userContentManager); + + for (string entry : scriptsContent) { + if (!entry.empty()) { + auto* script = webkit_user_script_new( + entry.c_str(), + WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, + WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, + nullptr, + nullptr + ); + webkit_user_content_manager_add_script(userContentManager, script); + webkit_user_script_unref(script); + } + } +#endif + + return G_SOURCE_REMOVE; + }, + data, + [](gpointer customdata) { + delete static_cast(customdata); + }); + + return Core::ERROR_NONE; + } + + uint32_t UserStyleSheets(IStringIterator*& uris) const override + { + _adminLock.Lock(); + uris = Core::Service::Create(_userStyleSheets); + _adminLock.Unlock(); + return Core::ERROR_NONE; + } + + uint32_t UserStyleSheets(IStringIterator* const uris) override + { + string entry; + std::vector userStyleSheetsContent; + std::list userStyleSheetsUris; + while (uris->Next(entry)) { + auto content = GetFileContent(entry); + if (!content.empty()) { + userStyleSheetsUris.push_back(entry); + userStyleSheetsContent.push_back(content); + } + TRACE_L1("Adding user's style sheet (uri: %s, empty: %d)", entry.c_str(), content.empty()); + } + using SetUserStyleSheetsData = std::tuple, std::vector>; + auto* data = new SetUserStyleSheetsData(this, userStyleSheetsUris, userStyleSheetsContent); + + g_main_context_invoke_full( + _context, + G_PRIORITY_DEFAULT, + [](gpointer customdata) -> gboolean { + auto& data = *static_cast(customdata); + WebKitImplementation* object = std::get<0>(data); + std::list styleSheetsUris = std::get<1>(data); + std::vector styleSheetsContent = std::get<2>(data); + + object->_adminLock.Lock(); + object->_userStyleSheets = styleSheetsUris; + object->_adminLock.Unlock(); + +#ifdef WEBKIT_GLIB_API + auto* userContentManager = webkit_web_view_get_user_content_manager(object->_view); + webkit_user_content_manager_remove_all_style_sheets(userContentManager); + for (string entry : styleSheetsContent) { + if (!entry.empty()) { + auto* stylesheet = webkit_user_style_sheet_new( + entry.c_str(), + WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, + WEBKIT_USER_STYLE_LEVEL_USER, + nullptr, + nullptr + ); + webkit_user_content_manager_add_style_sheet(userContentManager, stylesheet); + webkit_user_style_sheet_unref(stylesheet); + } + } +#endif + + return G_SOURCE_REMOVE; + }, + data, + [](gpointer customdata) { + delete static_cast(customdata); + }); + + return Core::ERROR_NONE; + } + +#ifdef RDK6_SUPPORT uint32_t RunJavaScript(const string& script) override { if (_context == nullptr) @@ -1293,6 +1787,7 @@ static GSourceFuncs _handlerIntervention = nullptr); return Core::ERROR_NONE; } +#endif #if defined(ENABLE_CLOUD_COOKIE_JAR) uint32_t CookieJar(uint32_t& version /* @out */, uint32_t& checksum /* @out */, string& payload /* @out */) const override @@ -1608,13 +2103,197 @@ static GSourceFuncs _handlerIntervention = return 0; } - uint32_t URL(const string& URL) override + std::string urlValue() const + { + std::unique_lock lock{urlData_.mutex}; + return urlData_.url; + } + + std::string urlValue(std::string url) + { + std::unique_lock lock{urlData_.mutex}; + std::swap(urlData_.url, url); + return url; + } + + string extractDomain(const string& URL) + { + size_t beginDomain = URL.find("//"); + if (beginDomain == string::npos) { + return ""; + } + beginDomain += 2; + + size_t endDomain = URL.find("/", beginDomain); + if (endDomain == string::npos) { + return URL.substr(beginDomain); + } + + return URL.substr(beginDomain, endDomain - beginDomain); + } + + void AppendClientCertificate(const std::string& newWpeHost, const std::string& newWpeClientCert, const std::string& newWpeClientCertKey, std::string& newCertContents) { - TRACE(Trace::Information, (_T("New URL: %s"), URL.c_str())); + if (newWpeHost.empty() || newWpeClientCert.empty() || newWpeClientCertKey.empty()) { + newCertContents.clear(); + TRACE(Trace::Error, (_T("Invalid arguments (%d,%d,%d)\n"), newWpeHost.empty(), newWpeClientCert.empty(), newWpeClientCertKey.empty())); + return; + } + + OpenSSL_add_all_algorithms(); + + auto *certsUrls = getenv(ENV_WPE_CLIENT_CERTIFICATES_URLS); + if (certsUrls && *certsUrls) + { + constexpr auto kUrlsSep = ENV_URL_LIST_DELIMITER; // not allowed in URI + std::string current_certsUrls(certsUrls); + auto urlsList = tokenize(current_certsUrls, kUrlsSep); + auto append_newHost = true; + auto isValid_certContents = false; + for (const auto &url : urlsList) + { + if (url == newWpeHost) + { + TRACE(Trace::Information, (_T("Already found on the list %s\n "), newWpeHost.c_str())); + append_newHost = false; + break; + } + } + + if (append_newHost) + { + if (!current_certsUrls.empty()) + { + current_certsUrls += kUrlsSep; + } + current_certsUrls += newWpeHost; + } + + const std::string filepath_WpeClientCert = std::string(ENV_WPE_CLIENT_CERTIFICATES_PATH).append(newWpeClientCert); + std::string certContents = GetFileContent(filepath_WpeClientCert); + + if (certContents.empty()) + { + TRACE(Trace::Error, (_T("Empty certificate for %s %s"), newWpeClientCert.c_str(), newWpeHost.c_str())); + } + else + { + const std::string filepath_WpeClientCertKey = std::string(ENV_WPE_CLIENT_CERTIFICATES_PATH).append(newWpeClientCertKey); + std::string keyContents = GetFileContent(filepath_WpeClientCertKey); + if (keyContents.empty()) + { + TRACE(Trace::Error, (_T("Empty private key for %s %s"), newWpeClientCertKey.c_str(), newWpeHost.c_str())); + } + else if (DecryptWithOpenSSL(&keyContents)) + { + certContents += "\n" + keyContents; + isValid_certContents = true; + } + else + { + TRACE(Trace::Error, (_T("Failed decrypting private key for %s %s"), newWpeClientCertKey.c_str(), newWpeHost.c_str())); + } + } + + if (isValid_certContents) { + Core::SystemInfo::SetEnvironment(_T(ENV_WPE_CLIENT_CERTIFICATES_URLS), current_certsUrls.c_str()); + Core::SystemInfo::SetEnvironment(_T(newWpeHost.c_str()), certContents.c_str()); + newCertContents = std::string(ENV_WPE_CLIENT_CERTIFICATES_URLS); + newCertContents.append(MSG_CLIENT_CERT_DELIMITER); + newCertContents.append(current_certsUrls); + newCertContents.append(MSG_CLIENT_CERT_DELIMITER); + newCertContents.append(newWpeHost); + newCertContents.append(MSG_CLIENT_CERT_DELIMITER); + newCertContents.append(certContents); + TRACE(Trace::Information, (_T("Update environment (data size %d/%d)"), newCertContents.length(), certContents.length())); + } + } else { + TRACE(Trace::Error, (_T("Failed to update environment, host %s:\n"), newWpeHost.c_str())); + } + } + + std::string getClientCertFromUrl(const std::string& url, std::string& newCertContents) + { + std::vector urlparm_str; + string new_url; + urlparm_str = tokenize(url, "&?"); + // parse 'url' if it contains params + if(urlparm_str.size() > 1) { + static constexpr char payload_cert[] = "clientcert="; + static constexpr char payload_certkey[] = "clientcertkey="; + static constexpr int payload_cert_len = sizeof(payload_cert) - 1; + static constexpr int payload_certkey_len = sizeof(payload_certkey) - 1; + + string new_host; + string new_payload_cert; + string new_payload_certkey; + bool useAmpersand = false; + for (auto const &urlKeyStr : urlparm_str) { + if (urlKeyStr.compare(0, payload_cert_len, payload_cert) == 0) { + if(new_payload_cert.size() == 0) { + new_payload_cert = urlKeyStr.substr(payload_cert_len); + decodeUrlCertPath(new_payload_cert); + TRACE(Trace::Information, (_T("found parameter clientcert: %s\n"), new_payload_cert.c_str())); + } + } else if ((urlKeyStr.compare(0, payload_certkey_len, payload_certkey)) == 0) { + if(new_payload_certkey.size() == 0) { + new_payload_certkey = urlKeyStr.substr(payload_certkey_len); + decodeUrlCertPath(new_payload_certkey); + TRACE(Trace::Information, (_T("found parameter clientcertkey: %s\n"), new_payload_certkey.c_str())); + } + } else { + if (new_host.size() == 0 ) { + new_host=getUrlOrigin(urlKeyStr); + } else { + if (useAmpersand) { + new_url.append("&"); + } else { + useAmpersand = true; + new_url.append("?"); + } + } + new_url.append(urlKeyStr); + } + } + if(new_host.size() && new_payload_cert.size() && new_payload_certkey.size() ) { + AppendClientCertificate(new_host, new_payload_cert, new_payload_certkey, newCertContents); + } + TRACE_L1("New URL: %s (Base URL: %s)", new_url.c_str(), url.c_str()); + + } else { + new_url = string(url); + } + return string(new_url); + } + + uint32_t URL(const string& URLwithParams) override + { + return SetupURLInternal(URLwithParams); + } + + uint32_t SetupURLInternal(const string& URLwithParams, bool waitForResult = true) + { + using namespace std::chrono; + std::string newCertContents; + std::string certsUrls; + const string URL = getClientCertFromUrl(URLwithParams, newCertContents); + + TRACE_L1("New URL: %s", URL.c_str()); + ODH_WARNING("WPE0020", WPE_CONTEXT_WITH_URL(URL.c_str()), "New URL: %s", URL.c_str()); if (_context != nullptr) { - using SetURLData = std::tuple; - auto *data = new SetURLData(this, URL); + using SetURLData = std::tuple; + auto *data = new SetURLData(this, URL, newCertContents); + const auto now = steady_clock::now(); + + if (waitForResult) { + std::unique_lock lock{urlData_.mutex}; + urlData_.result = Core::ERROR_TIMEDOUT; + urlData_.loadResult.loadUrl = URL; + urlData_.loadResult.waitForFailedOrFinished = true; + urlData_.loadResult.waitForExceptionalPageClosureAfterBootUrl = false; + } + g_main_context_invoke_full( _context, G_PRIORITY_DEFAULT, @@ -1623,16 +2302,23 @@ static GSourceFuncs _handlerIntervention = WebKitImplementation* object = std::get<0>(data); string url = std::get<1>(data); - object->_adminLock.Lock(); - object->_URL = url; - object->_adminLock.Unlock(); + // Pass new URL as an argument and don't store it here, it'll be stored in the load callback + //object->urlValue(url); + std::string certificatedata= std::get<2>(data);; object->SetResponseHTTPStatusCode(-1); #ifdef WEBKIT_GLIB_API - webkit_web_view_load_uri(object->_view, object->_URL.c_str()); + if (certificatedata.empty()) + { + webkit_web_view_load_uri(object->_view, url.c_str()); + } + else + { + webkit_web_view_load_uri_and_cert(object->_view, url.c_str(), certificatedata.c_str()); + } #else object->SetNavigationRef(nullptr); - auto shellURL = WKURLCreateWithUTF8CString(object->_URL.c_str()); + auto shellURL = WKURLCreateWithUTF8CString(url.c_str()); WKPageLoadURL(object->_page, shellURL); WKRelease(shellURL); #endif @@ -1642,17 +2328,46 @@ static GSourceFuncs _handlerIntervention = [](gpointer customdata) { delete static_cast(customdata); }); + if (waitForResult) { + std::unique_lock lock{urlData_.mutex}; + TRACE_L1("Start waiting for the load result of url: %s", URL.c_str()); + urlData_.cond.wait_for( + lock, + milliseconds{URL_LOAD_RESULT_TIMEOUT_MS}, + [this](){return Core::ERROR_TIMEDOUT != urlData_.result;}); + + const auto diff = steady_clock::now() - now; + + TRACE_L1( + "URL: %s, load result %s(%d), %dms", + urlData_.url.c_str(), + Core::ERROR_NONE == urlData_.result ? "OK" : "NOK", + int(urlData_.result), + int(duration_cast(diff).count())); + + ODH_WARNING( + "WPE0040", + WPE_CONTEXT_WITH_URL(urlData_.url.c_str()), + "URL: %s, load result %s(%d), %dms", + urlData_.url.c_str(), + Core::ERROR_NONE == urlData_.result ? "OK" : "NOK", + int(urlData_.result), + int(duration_cast(diff).count())); + + return urlData_.result; + } else { + return Core::ERROR_NONE; + } + } + else + { + return Core::ERROR_ILLEGAL_STATE; } - - return Core::ERROR_NONE; } uint32_t URL(string& url) const override { - _adminLock.Lock(); - url = _URL; - _adminLock.Unlock(); - + url = urlValue(); return 0; } @@ -2000,9 +2715,49 @@ static GSourceFuncs _handlerIntervention = void OnURLChanged(const string& URL) { - _adminLock.Lock(); + static const auto metroDomain = _bootUrl.substr(0, _bootUrl.find('#')); - _URL = URL; + TRACE_L1("%s", URL.c_str()); + + const bool isCurrentUrlBootUrl = urlValue() == _bootUrl; + const bool isCurrUrlMetroSubdomain = urlValue().find(metroDomain) != string::npos; + const bool isNewUrlBootUrl = URL == _bootUrl; + const bool isNewUrlBlankUrl = URL.find("about:blank") != string::npos; + const bool isNewUrlMetroSubdomain = URL.find(metroDomain) != string::npos; + + urlValue(URL); + + if(!isCurrentUrlBootUrl && isNewUrlBootUrl && !_bootUrl.empty()) { + TRACE_L1("New URL: %s", URL.c_str()); + ODH_WARNING("WPE0040", WPE_CONTEXT_WITH_URL(URL.c_str()), "New URL: %s", URL.c_str()); + TRACE_L1("Starting GCCleaner"); + gcCleaner->start(); + } else { + TRACE_L1("Stopping GCCleaner"); + gcCleaner->stop(); + } + + if (isNewUrlBlankUrl || (isCurrUrlMetroSubdomain && isNewUrlMetroSubdomain)) { + /* + * When loading URL from the same domain only notify::uri signal is being sent. + * This scenario happens only for Metro domain addresses. + * When those addresses are detected and URL() waits for the result, send notification. + */ + notifyUrlLoadResult(URL, Core::ERROR_NONE); + } else { + /* + * When domains of changed URL and saved URL match, store an updated URL. + * URL should *NOT* be updated unconditionally as in case of load failure, + * notify:uri signal is being sent for previously loaded URL. + */ + std::unique_lock lock{urlData_.mutex}; + if (extractDomain(URL) == extractDomain(urlData_.loadResult.loadUrl)) { + TRACE_L1("URL updated, storing new loadResult URL: %s", URL.c_str()); + urlData_.loadResult.loadUrl = URL; + } + } + + _adminLock.Lock(); std::list::iterator index(_notificationClients.begin()); { @@ -2025,6 +2780,8 @@ static GSourceFuncs _handlerIntervention = #ifndef WEBKIT_GLIB_API void OnLoadFinished(const string& URL, WKNavigationRef navigation) { + TRACE(Trace::Information, (_T("%s (%p|%p)"), URL.c_str(), _navigationRef, navigation)); + if (_navigationRef != navigation) { TRACE(Trace::Information, (_T("Ignore 'loadfinished' for previous navigation request"))); return; @@ -2034,9 +2791,17 @@ static GSourceFuncs _handlerIntervention = #endif void OnLoadFinished(const string& URL) { + uint32_t status = Core::ERROR_NONE; + + TRACE_L1("%s , _httpStatusCode: %d", URL.c_str(), _httpStatusCode); + urlValue(URL); + if ( (_httpStatusCode != 200 ) && (_httpStatusCode != -1) ) { + status = Core::ERROR_INCORRECT_URL; + } + notifyUrlLoadResult(URL, status); + _adminLock.Lock(); - _URL = URL; { std::list::iterator index(_notificationClients.begin()); @@ -2056,8 +2821,28 @@ static GSourceFuncs _handlerIntervention = _adminLock.Unlock(); } + + bool OnLoadFailedCheckWaitingForBootUrl(const string& URL) { + bool postponeNotification = false; + if (URL == _bootUrl) { + std::unique_lock lock{urlData_.mutex}; + if (urlData_.loadResult.waitForFailedOrFinished && urlData_.loadResult.loadUrl == _bootUrl) { + urlData_.loadResult.waitForExceptionalPageClosureAfterBootUrl = true; + postponeNotification = true; + } + } + return postponeNotification; + } + void OnLoadFailed(const string& URL) { + urlValue(URL); + const auto url = urlValue(); + + TRACE_L1("%s", url.c_str()); + + notifyUrlLoadResult(URL, Core::ERROR_INCORRECT_URL); + _adminLock.Lock(); std::list::iterator index(_notificationClients.begin()); @@ -2068,6 +2853,15 @@ static GSourceFuncs _handlerIntervention = } _adminLock.Unlock(); + ODH_ERROR("WPE0030", WPE_CONTEXT_WITH_URL(url.c_str()), "Failed to load URL: %s", url.c_str()); + } + void OnLoadRedirected(const string& URL) + { + std::unique_lock lock{urlData_.mutex}; + if (urlData_.loadResult.waitForFailedOrFinished) { + TRACE_L1("Redirected, storing new loadResult URL: %s", URL.c_str()); + urlData_.loadResult.loadUrl = URL; + } } void OnStateChange(const PluginHost::IStateControl::state newState) { @@ -2144,6 +2938,100 @@ static GSourceFuncs _handlerIntervention = _httpStatusCode = code; } +#if !defined(CLIENT_CERTS_PRIV_KEY_PASSWD) +#error "Client certificates private key password not provided" +#endif + + + + static int passCb(char* buf, int size, int rwflag, void* u) { + if (rwflag) + return -1; + + int passLen = strlen(CLIENT_CERTS_PRIV_KEY_PASSWD); + if (size < passLen + 1) { + fprintf(stderr, "Error decrypting private key. Password won't fit to the provided buffer.\n"); + return -1; + } + strncpy(buf, CLIENT_CERTS_PRIV_KEY_PASSWD, passLen + 1); + return passLen; + } + + bool DecryptWithOpenSSL(std::string* key) { + using AutoMemBio = std::unique_ptr>; + using AutoEVPKey = std::unique_ptr>; + + std::vector readBuf(key->begin(), key->end()); + AutoMemBio memBio( + BIO_new_mem_buf(static_cast(readBuf.data()), readBuf.size()), + BIO_free); + if (!memBio) + return false; + + AutoEVPKey decryptedKey( + PEM_read_bio_PrivateKey(memBio.get(), nullptr, passCb, nullptr), + EVP_PKEY_free); + if (!decryptedKey.get()) + return false; + + memBio.reset(BIO_new(BIO_s_mem())); + if (!PEM_write_bio_PrivateKey(memBio.get(), decryptedKey.get(), + nullptr, nullptr, 0, nullptr, + nullptr)) { + return false; + } + + char* data = nullptr; + auto dataSize = BIO_get_mem_data(memBio.get(), &data); + key->clear(); + key->append(data, dataSize); + return true; + } + + void SetupClientCertificates() { + OpenSSL_add_all_algorithms(); + const std::string wpeClientCertsConf = _config.ClientCertsInitialConf; + TRACE(Trace::Information, (_T("Client Certs = '%s'"), wpeClientCertsConf.c_str())); + + std::string urls; + constexpr auto kUrlsSep = ENV_URL_LIST_DELIMITER; + auto lines = tokenize(wpeClientCertsConf, kUrlsSep); + for (const auto& line : lines) { + auto urlToFiles = tokenize(line, "="); + if (urlToFiles.size() == 2) { + auto files = tokenize(urlToFiles[1], ","); + if (files.size() == 2) { + if (!urls.empty()) + urls += kUrlsSep; + urls += urlToFiles[0]; + + std::string certContents = GetFileContent(files[0]); + + if (certContents.empty()) { + TRACE(Trace::Error, (_T("Empty certificate for %s %s"), files[0].c_str(), urlToFiles[0].c_str())); + continue; + } + std::string keyContents = GetFileContent(files[1]); + if (keyContents.empty()) { + TRACE(Trace::Error, (_T("Empty private key for %s %s"), files[1].c_str(), urlToFiles[0].c_str())); + continue; + } + if (!DecryptWithOpenSSL(&keyContents)) { + TRACE(Trace::Error, (_T("Failed decrypting private key for %s %s"), files[1].c_str(), urlToFiles[0].c_str())); + continue; + } + certContents += "\n" + keyContents; + Core::SystemInfo::SetEnvironment(_T(urlToFiles[0].c_str()), certContents.c_str()); + } + } + } + + if (!urls.empty()) { + Core::SystemInfo::SetEnvironment(_T(ENV_WPE_CLIENT_CERTIFICATES_URLS), urls.c_str()); + TRACE(Trace::Information, (_T("%s:%d %s[%d]"), __func__, __LINE__, ENV_WPE_CLIENT_CERTIFICATES_URLS, urls.size())); + } + } + uint32_t Configure(PluginHost::IShell* service) override { #ifndef WEBKIT_GLIB_API @@ -2153,6 +3041,8 @@ static GSourceFuncs _handlerIntervention = _dataPath = service->DataPath(); + _bootUrl = getMainConfigValue("app.metroBootPath"); + string configLine = service->ConfigLine(); Core::OptionalType error; if (_config.FromString(configLine, error) == false) { @@ -2173,8 +3063,12 @@ static GSourceFuncs _handlerIntervention = bool environmentOverride(WebKitBrowser::EnvironmentOverride(_config.EnvironmentOverride.Value())); - if ((environmentOverride == false) || (Core::SystemInfo::GetEnvironment(_T("WPE_WEBKIT_URL"), _URL) == false)) { - _URL = _config.URL.Value(); + { + std::string url; + + if ((environmentOverride == false) || (Core::SystemInfo::GetEnvironment(_T("WPE_WEBKIT_URL"), url) == false)) { + urlValue(_config.URL.Value()); + } } Core::SystemInfo::SetEnvironment(_T("QUEUEPLAYER_FLUSH_MODE"), _T("3"), false); @@ -2187,6 +3081,21 @@ static GSourceFuncs _handlerIntervention = Core::SystemInfo::SetEnvironment(_T("CLIENT_IDENTIFIER"), service->Callsign(), !environmentOverride); } + // Setup client certificates + SetupClientCertificates(); + + // WEBKIT_DEBUG + if (_config.WebkitDebug.Value().empty() == false) + Core::SystemInfo::SetEnvironment(_T("WEBKIT_DEBUG"), _config.WebkitDebug.Value(), !environmentOverride); + + // GST_DEBUG + if (_config.GstDebug.Value().empty() == false) + Core::SystemInfo::SetEnvironment(_T("GST_DEBUG"), _config.GstDebug.Value(), !environmentOverride); + + // GST_DEBUG_NO_COLOR + if (_config.GstNoColor.IsSet()) + Core::SystemInfo::SetEnvironment(_T("GST_DEBUG_NO_COLOR"), _T("1"), !environmentOverride); + // Set dummy window for gst-gl Core::SystemInfo::SetEnvironment(_T("GST_GL_WINDOW"), _T("dummy"), !environmentOverride); @@ -2252,6 +3161,9 @@ static GSourceFuncs _handlerIntervention = } else { Core::SystemInfo::SetEnvironment(_T("WEBKIT_INSPECTOR_HTTP_SERVER"), _config.Inspector.Value(), !environmentOverride); } + if (_config.InspectorPort.Value().empty() == false) { + Core::SystemInfo::SetEnvironment(_T("WEBKIT_INSPECTOR_PORT"), _config.InspectorPort.Value(), !environmentOverride); + } #else if (_config.Automation.Value()) { Core::SystemInfo::SetEnvironment(_T("WEBKIT_INSPECTOR_SERVER"), _config.Inspector.Value(), !environmentOverride); @@ -2351,6 +3263,8 @@ static GSourceFuncs _handlerIntervention = string maxFPS(Core::NumberType(_config.MaxFPS.Value()).Text()); Core::SystemInfo::SetEnvironment(_T("WEBKIT_RESOLUTION_WIDTH"), width, !environmentOverride); Core::SystemInfo::SetEnvironment(_T("WEBKIT_RESOLUTION_HEIGHT"), height, !environmentOverride); + Core::SystemInfo::SetEnvironment(_T("WPE_INIT_VIEW_WIDTH"), width, !environmentOverride); + Core::SystemInfo::SetEnvironment(_T("WPE_INIT_VIEW_HEIGHT"), height, !environmentOverride); Core::SystemInfo::SetEnvironment(_T("WEBKIT_MAXIMUM_FPS"), maxFPS, !environmentOverride); if (width.empty() == false) { @@ -2374,8 +3288,23 @@ static GSourceFuncs _handlerIntervention = return (Core::ERROR_NONE); } + bool RepeatLoadUrlWhenPageClosureAndLoadFailedWithReasonCancelledOnBootUrl() { + std::unique_lock lock{urlData_.mutex}; + bool repeat = urlData_.loadResult.waitForExceptionalPageClosureAfterBootUrl && urlData_.loadResult.waitForFailedOrFinished; + urlData_.loadResult.waitForExceptionalPageClosureAfterBootUrl = false; + return repeat; + } + void NotifyClosure() { + if (RepeatLoadUrlWhenPageClosureAndLoadFailedWithReasonCancelledOnBootUrl()) { + // our setup of boot url was interrupted by window.close in the middle of loading the boot url + // here we need to "fix the reality" by doing extra _bootUrl setup + SYSLOG(Logging::Notification, (_T("boot URL setup + window.close: NotifyClosure: Repeat load boot url started"))); + SetupURLInternal(_bootUrl, false); + SYSLOG(Logging::Notification, (_T("boot URL setup + window.close: NotifyClosure: Repeat load boot url finished"))); + return; + } _adminLock.Lock(); { @@ -2447,12 +3376,19 @@ static GSourceFuncs _handlerIntervention = BEGIN_INTERFACE_MAP(WebKitImplementation) INTERFACE_ENTRY(Exchange::IWebBrowser) INTERFACE_ENTRY(Exchange::IBrowser) + INTERFACE_ENTRY(Exchange::IBrowserSecurity) INTERFACE_ENTRY (Exchange::IApplication) +#ifdef RDK6_SUPPORT INTERFACE_ENTRY (Exchange::IBrowserScripting) +#endif #if defined(ENABLE_CLOUD_COOKIE_JAR) INTERFACE_ENTRY (Exchange::IBrowserCookieJar) #endif INTERFACE_ENTRY(PluginHost::IStateControl) + INTERFACE_ENTRY(Exchange::IBrowserResources) +#if HAS_SCREEN_HDR_API + INTERFACE_ENTRY(Exchange::IConnectionProperties::INotification) +#endif END_INTERFACE_MAP private: @@ -2511,9 +3447,9 @@ static GSourceFuncs _handlerIntervention = WebKitImplementation* object = static_cast(customdata); if (object->_config.LoadBlankPageOnSuspendEnabled.Value()) { const char kBlankURL[] = "about:blank"; - if (object->_URL != kBlankURL) + if (object->urlValue() != kBlankURL) object->SetURL(kBlankURL); - ASSERT(object->_URL == kBlankURL); + ASSERT(object->urlValue() == kBlankURL); } #ifdef WEBKIT_GLIB_API webkit_web_view_suspend(object->_view); @@ -2556,12 +3492,28 @@ static GSourceFuncs _handlerIntervention = this); } } + std::string GetFileContent(const std::string& fileName) + { + std::string content; + auto stream = std::ifstream(fileName); + + if (stream.fail()) { + TRACE(Trace::Error, (_T("Failed to get content from file: %s"), fileName.c_str())); + } else { + content = std::string((std::istreambuf_iterator(stream)), std::istreambuf_iterator()); + } + return content; + } #ifdef WEBKIT_GLIB_API static void initializeWebExtensionsCallback(WebKitWebContext* context, WebKitImplementation* browser) { webkit_web_context_set_web_extensions_directory(context, browser->_extensionPath.c_str()); - // FIX it - GVariant* data = g_variant_new("(smsb)", std::to_string(browser->_guid).c_str(), !browser->_config.Whitelist.Value().empty() ? browser->_config.Whitelist.Value().c_str() : nullptr, browser->_config.LogToSystemConsoleEnabled.Value()); + // FIX it + GVariant* data = g_variant_new("(smsbms)", + std::to_string(browser->_guid).c_str(), //s + !browser->_config.Whitelist.Value().empty() ? browser->_config.Whitelist.Value().c_str() : nullptr, //ms + browser->_config.LogToSystemConsoleEnabled.Value(), //b + !browser->_config.MixedContentWhitelist.Value().empty() ? browser->_config.MixedContentWhitelist.Value().c_str() : nullptr); //ms webkit_web_context_set_web_extensions_initialization_user_data(context, data); } static void wpeNotifyWPEFrameworkMessageReceivedCallback(WebKitUserContentManager*, WebKitJavascriptResult* message, WebKitImplementation* browser) @@ -2612,39 +3564,67 @@ static GSourceFuncs _handlerIntervention = } browser->OnLoadFinished(Core::ToString(uri.c_str())); } + else if (loadEvent == WEBKIT_LOAD_REDIRECTED) + { + const std::string uri = webkit_web_view_get_uri(webView); + browser->OnLoadRedirected(uri); + } } + + static void documentLoadedCallback(WebKitWebView *webView, WebKitImplementation* browser) + { + const std::string uri = webkit_web_view_get_uri(webView); + browser->OnLoadFinished(Core::ToString(uri.c_str())); + } + static void loadFailedCallback(WebKitWebView*, WebKitLoadEvent loadEvent, const gchar* failingURI, GError* error, WebKitImplementation* browser) { string message(string("{ \"url\": \"") + failingURI + string("\", \"Error message\": \"") + error->message + string("\", \"loadEvent\":") + Core::NumberType(loadEvent).Text() + string(" }")); - SYSLOG(Trace::Information, (_T("LoadFailed: %s"), message.c_str())); + SYSLOG_GLOBAL(Logging::Notification, (_T("LoadFailed: %s"), message.c_str())); if (g_error_matches(error, WEBKIT_NETWORK_ERROR, WEBKIT_NETWORK_ERROR_CANCELLED)) { browser->_ignoreLoadFinishedOnce = true; - return; + if (browser->OnLoadFailedCheckWaitingForBootUrl(failingURI)) { + SYSLOG_GLOBAL(Logging::Notification, (_T("boot URL setup + window.close detected: will wait for page closure event"))); + return; + } } browser->OnLoadFailed(failingURI); } + static void postExitJob() + { + struct ExitJob : public Core::IDispatch + { + void Dispatch() override { exit(1); } + }; + + Core::IWorkerPool::Instance().Submit(Core::ProxyType(Core::ProxyType::Create())); + } static void webProcessTerminatedCallback(VARIABLE_IS_NOT_USED WebKitWebView* webView, WebKitWebProcessTerminationReason reason, WebKitImplementation* browser) { switch (reason) { case WEBKIT_WEB_PROCESS_CRASHED: - SYSLOG(Trace::Fatal, (_T("CRASH: WebProcess crashed: exiting ..."))); + SYSLOG_GLOBAL(Logging::Fatal, (_T("CRASH: WebProcess crashed: exiting ..."))); break; case WEBKIT_WEB_PROCESS_EXCEEDED_MEMORY_LIMIT: - SYSLOG(Trace::Fatal, (_T("CRASH: WebProcess terminated due to memory limit: exiting ..."))); + SYSLOG_GLOBAL(Logging::Fatal, (_T("CRASH: WebProcess terminated due to memory limit: exiting ..."))); + ODH_ERROR( + "WPE0050", + WPE_CONTEXT_WITH_URL(browser->urlValue().c_str()), + "WPEWebProcess memory exceeded: URL(%s), limit(%u MB), kill threshold(%.2f), mem used(%.0f MB)", + browser->urlValue().c_str(), browser->_config.Memory.WebProcessLimit.Value(), + browser->_config.Memory.WebProcessKillThreshold.Value(), + browser->_config.Memory.WebProcessLimit.Value() * browser->_config.Memory.WebProcessKillThreshold.Value()); break; case WEBKIT_WEB_PROCESS_TERMINATED_BY_API: - SYSLOG(Trace::Fatal, (_T("CRASH: WebProcess terminated by API"))); + SYSLOG_GLOBAL(Logging::Fatal, (_T("CRASH: WebProcess terminated by API"))); break; } g_signal_handlers_block_matched(webView, G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, browser); - struct ExitJob : public Core::IDispatch - { - virtual void Dispatch() { exit(1); } - }; - Core::IWorkerPool::Instance().Submit(Core::proxy_cast(Core::ProxyType::Create())); + postExitJob(); } static void closeCallback(VARIABLE_IS_NOT_USED WebKitWebView* webView, WebKitImplementation* browser) { + SYSLOG_GLOBAL(Logging::Notification, (_T("closeCallback"))); browser->NotifyClosure(); } static gboolean decidePermissionCallback(VARIABLE_IS_NOT_USED WebKitWebView* webView, WebKitPermissionRequest* permissionRequest) @@ -2690,9 +3670,12 @@ static GSourceFuncs _handlerIntervention = } #if defined(ENABLE_CLOUD_COOKIE_JAR) static void cookieManagerChangedCallback(WebKitCookieManager* manager, WebKitImplementation* browser) { +#ifdef ENABLE_CLOUD_COOKIE_JAR browser->NotifyCookieJarChanged(); +#endif } #endif + std::unique_ptr gcCleaner; uint32_t Worker() override { _context = g_main_context_new(); @@ -2701,6 +3684,8 @@ static GSourceFuncs _handlerIntervention = HangDetector hangdetector(*this); + gcCleaner.reset(new GCCleaner{*this}); + bool automationEnabled = _config.Automation.Value(); WebKitWebContext* wkContext; @@ -2776,6 +3761,9 @@ static GSourceFuncs _handlerIntervention = if ((_config.Memory.IsSet() == true) && (_config.Memory.WebProcessLimit.IsSet() == true)) { WebKitMemoryPressureSettings* memoryPressureSettings = webkit_memory_pressure_settings_new(); webkit_memory_pressure_settings_set_memory_limit(memoryPressureSettings, _config.Memory.WebProcessLimit.Value()); + if (_config.Memory.WebProcessKillThreshold.IsSet() == true) { + webkit_memory_pressure_settings_set_kill_threshold(memoryPressureSettings, _config.Memory.WebProcessKillThreshold.Value()); + } // Pass web process memory pressure settings to WebKitWebContext constructor wkContext = WEBKIT_WEB_CONTEXT(g_object_new(WEBKIT_TYPE_WEB_CONTEXT, "website-data-manager", websiteDataManager, "memory-pressure-settings", memoryPressureSettings, nullptr)); webkit_memory_pressure_settings_free(memoryPressureSettings); @@ -2858,6 +3846,9 @@ static GSourceFuncs _handlerIntervention = webkit_settings_set_enable_tabs_to_links(preferences, _config.SpatialNavigation.Value()); } webkit_settings_set_allow_scripts_to_close_windows(preferences, _config.AllowWindowClose.Value()); +#if WEBKIT_CHECK_VERSION(2, 38, 0) + webkit_settings_set_allow_move_to_suspend_on_window_close(preferences, _config.AllowMoveToSuspendOnWindowClose.Value()); +#endif webkit_settings_set_enable_non_composited_webgl(preferences, _config.NonCompositedWebGLEnabled.Value()); // Media Content Types Requiring Hardware Support @@ -2870,7 +3861,7 @@ static GSourceFuncs _handlerIntervention = if (_config.UserAgent.IsSet() == true && _config.UserAgent.Value().empty() == false) { webkit_settings_set_user_agent(preferences, _config.UserAgent.Value().c_str()); } else { - webkit_settings_set_user_agent_with_application_details(preferences, "WPE", "1.0"); + webkit_settings_set_user_agent_with_application_details(preferences, "", ""); _config.UserAgent = webkit_settings_get_user_agent(preferences); } @@ -2880,19 +3871,33 @@ static GSourceFuncs _handlerIntervention = // webaudio support webkit_settings_set_enable_webaudio(preferences, _config.WebAudioEnabled.Value()); +#if HAS_SCREEN_HDR_API + SubscribeHDRCapabilities(); + // Query the current HDR capabilities before we start the browser + RefreshHDRSupport(); + UpdateHDRSettings(preferences); +#endif + // Allow mixed content. - bool enableWebSecurity = _config.Secure.Value(); + bool allowMixedContent = !_config.Secure.Value(); + SYSLOG(Logging::Notification, (_T("Mixed content is %s\n"), (allowMixedContent ? "allowed" : "blocked"))); #if WEBKIT_CHECK_VERSION(2, 38, 0) g_object_set(G_OBJECT(preferences), - "disable-web-security", !enableWebSecurity, - "allow-running-of-insecure-content", !enableWebSecurity, - "allow-display-of-insecure-content", !enableWebSecurity, nullptr); + "disable-web-security", allowMixedContent, + "allow-running-of-insecure-content", allowMixedContent, + "allow-display-of-insecure-content", allowMixedContent, nullptr); #else g_object_set(G_OBJECT(preferences), - "enable-websecurity", enableWebSecurity, - "allow-running-of-insecure-content", !enableWebSecurity, - "allow-display-of-insecure-content", !enableWebSecurity, nullptr); + "allow-running-of-insecure-content", allowMixedContent, + "allow-display-of-insecure-content", allowMixedContent, nullptr); #endif + + // ICE candidate filtering + if (_config.ICECandidateFilteringEnabled.IsSet()) { + g_object_set(G_OBJECT(preferences), + "enable-ice-candidate-filtering", _config.ICECandidateFilteringEnabled.Value(), nullptr); + } + _view = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, "backend", webkit_web_view_backend_new(wpe_view_backend_create(), nullptr, nullptr), "web-context", wkContext, @@ -2934,10 +3939,14 @@ static GSourceFuncs _handlerIntervention = g_signal_connect(_view, "user-message-received", reinterpret_cast(userMessageReceivedCallback), this); g_signal_connect(_view, "notify::is-web-process-responsive", reinterpret_cast(isWebProcessResponsiveCallback), this); g_signal_connect(_view, "load-failed", reinterpret_cast(loadFailedCallback), this); + g_signal_connect(_view, "document-loaded", reinterpret_cast(documentLoadedCallback), this); _configurationCompleted.SetState(true); - URL(static_cast(_URL)); + // that invocation is doing initial about:blank URL setup, that randomly causes aborts + // like descibred here: https://jira.lgi.io/browse/ONEM-34263?focusedCommentId=5340765 + // when overlaps with initial URL setup done via jsapp=>slauncher + //URL(static_cast(urlValue())); // Move into the correct state, as requested auto* backend = webkit_web_view_backend_get_wpe_backend(webkit_web_view_get_backend(_view)); @@ -2955,6 +3964,8 @@ static GSourceFuncs _handlerIntervention = g_main_loop_run(_loop); + gcCleaner.reset(nullptr); + if (frameDisplayedCallbackID) webkit_web_view_remove_frame_displayed_callback(_view, frameDisplayedCallbackID); // webkit_user_content_manager_unregister_script_message_handler_in_world(userContentManager, "wpeNotifyWPEFramework", std::to_string(_guid).c_str()); @@ -3183,7 +4194,7 @@ static GSourceFuncs _handlerIntervention = TRACE(Trace::Information, (_T("Current user agent: '%s'"), _config.UserAgent.Value().c_str())); } - URL(static_cast(_URL)); + URL(static_cast(urlValue())); // Move into the correct state, as requested _adminLock.Lock(); @@ -3253,7 +4264,7 @@ static GSourceFuncs _handlerIntervention = gchar* scriptContent; auto success = g_file_get_contents(path.c_str(), &scriptContent, nullptr, nullptr); if (!success) { - SYSLOG(Trace::Error, (_T("Unable to read user script '%s'"), path.c_str())); + SYSLOG(Logging::Error, (_T("Unable to read user script '%s'"), path.c_str())); return; } AddUserScriptImpl(scriptContent, false); @@ -3301,6 +4312,181 @@ static GSourceFuncs _handlerIntervention = #endif } +#if HAS_SCREEN_HDR_API + WPEFramework::PluginHost::IPlugin* QueryDisplayInfoPlugin() + { + if (_displayInfoPlugin) { + return _displayInfoPlugin; + } + if (!_service) { + return nullptr; + } + _displayInfoPlugin = _service->QueryInterfaceByCallsign(_T("DisplayInfo")); + if (!_displayInfoPlugin) { + SYSLOG(Logging::Error, (_T("Failed to query DisplayInfo plugin."))); + return nullptr; + } + return _displayInfoPlugin; + } + + bool SubscribeHDRCapabilities() + { + auto* displayInfoPlugin = QueryDisplayInfoPlugin(); + if (!displayInfoPlugin || _displayConnectionProps) { + return false; + } + _displayConnectionProps = displayInfoPlugin->QueryInterface(); + if (!_displayConnectionProps) { + SYSLOG(Logging::Error, (_T("Failed to query IConnectionProperties interface from DisplayInfo plugin."))); + return false; + } + _displayConnectionProps->Register(this); + SYSLOG(Logging::Notification, (_T("Subscribed to HDR capabilities updates from DisplayInfo plugin."))); + return true; + } + + void UnsubscribeHDRCapabilities() + { + if (_displayConnectionProps) { + _displayConnectionProps->Unregister(this); + _displayConnectionProps->Release(); + _displayConnectionProps = nullptr; + } + _adminLock.Lock(); + if (_hdrRefreshJob.IsValid()) { + Core::WorkerPool::Instance().Revoke(_hdrRefreshJob); + _hdrRefreshJob.Release(); + } + _adminLock.Unlock(); + if (_displayInfoPlugin) { + _displayInfoPlugin->Release(); + _displayInfoPlugin = nullptr; + } + SYSLOG(Logging::Notification, (_T("Unsubscribed from HDR capabilities updates from DisplayInfo plugin."))); + } + + void RefreshHDRSupport() + { + // HDR support will be set to false in case of any failure + bool isHDRSupported = false; + struct ScopeExit { + bool& isHDRSupported; + WebKitImplementation& browser; + ~ScopeExit() { + browser._adminLock.Lock(); + bool wasSupported = browser._hdrSupported; + browser._hdrSupported = isHDRSupported; + browser._adminLock.Unlock(); + if (wasSupported != isHDRSupported) { + SYSLOG(Logging::Notification, (_T("HDR support changed to %s"), isHDRSupported ? "true" : "false")); + } + } + } scope_exit{isHDRSupported, *this}; + + auto displayInfoPlugin = QueryDisplayInfoPlugin(); + if (!displayInfoPlugin) { + return; + } + Exchange::IHDRProperties* hdrPropIface = displayInfoPlugin->QueryInterface(); + if (!hdrPropIface) { + SYSLOG(Logging::Error, (_T("Failed to query IHDRProperties interface from DisplayInfo plugin."))); + return; + } + uint32_t rc = Core::ERROR_NONE; + Exchange::IHDRProperties::IHDRIterator* iter = nullptr; + if ((rc = hdrPropIface->STBCapabilities(iter)) != Core::ERROR_NONE) { + SYSLOG(Logging::Error, (_T("Failed to get STB HDR capabilities, error code: %u"), rc)); + hdrPropIface->Release(); + return; + } + + std::vector stbCaps; + Exchange::IHDRProperties::HDRType value; + while (iter->Next(value)) { + stbCaps.push_back(value); + } + iter->Release(); + iter = nullptr; + + if ((rc = hdrPropIface->TVCapabilities(iter)) != Core::ERROR_NONE) { + SYSLOG(Logging::Error, (_T("Failed to get TV HDR capabilities, error code: %u"), rc)); + hdrPropIface->Release(); + return; + } + + std::vector commonCaps; + while (iter->Next(value)) { + if (std::find(stbCaps.begin(), stbCaps.end(), value) != stbCaps.end()) { + commonCaps.push_back(value); + } + } + iter->Release(); + hdrPropIface->Release(); + + for (const auto& cap : commonCaps) { + if (cap > Exchange::IHDRProperties::HDRType::HDR_OFF) { + isHDRSupported = true; + break; + } + } + // _hdrSupported will be update in the scope exit handler + } + + void UpdateHDRSettings(WebKitSettings* settings) + { + ASSERT(settings != nullptr); + _adminLock.Lock(); + bool isHDRSupported = _hdrSupported; + _adminLock.Unlock(); + if (webkit_settings_get_screen_supports_hdr(settings) != isHDRSupported) { + SYSLOG(Logging::Notification, (_T("Updating HDR support setting to %s"), isHDRSupported ? "true" : "false")); + webkit_settings_set_screen_supports_hdr(settings, isHDRSupported); + } + } + + class HDRRefreshJob : public Core::IDispatch { + public: + HDRRefreshJob(WebKitImplementation& browser) + : _browser(browser) + { + } + ~HDRRefreshJob() override = default; + void Dispatch() override + { + _browser.RefreshHDRSupport(); + g_main_context_invoke( + _browser._context, + [](gpointer user_data) -> gboolean { + WebKitImplementation* browser = static_cast(user_data); + WebKitSettings* settings = webkit_web_view_get_settings(browser->_view); + browser->UpdateHDRSettings(settings); + return G_SOURCE_REMOVE; + }, + &_browser); + } + private: + WebKitImplementation& _browser; + }; + + // Exchange::IConnectionProperties::INotification implementation + // This method is called on the main thread of the plugin + // when the display connection properties are updated. + void Updated(const Exchange::IConnectionProperties::INotification::Source event) override + { + SYSLOG(Logging::Notification, (_T("Display connection properties updated, event: %s"), + Core::EnumerateType(event).Data())); + _adminLock.Lock(); + if (!_hdrRefreshJob.IsValid()) { + _hdrRefreshJob = Core::ProxyType::Create(*this); + // If a refresh is already scheduled, do nothing + } + // Delay the refresh to avoid too frequent updates + // and to allow the display connection properties to stabilize. + Core::WorkerPool::Instance().Reschedule(Core::Time::Now().Add(_config.HDRRefreshDelay.Value()), _hdrRefreshJob); + _adminLock.Unlock(); + } +#endif // HAS_SCREEN_HDR_API + void CheckWebProcess() { if ( _webProcessCheckInProgress ) @@ -3376,7 +4562,7 @@ static GSourceFuncs _handlerIntervention = _unresponsiveReplyNum = kWebProcessUnresponsiveReplyDefaultLimit; Logging::DumpSystemFiles(webprocessPID); if (syscall(__NR_tgkill, webprocessPID, webprocessPID, SIGFPE) == -1) { - SYSLOG(Trace::Error, (_T("tgkill failed, signal=%d process=%u errno=%d (%s)"), SIGFPE, webprocessPID, errno, strerror(errno))); + SYSLOG(Logging::Error, (_T("tgkill failed, signal=%d process=%u errno=%d (%s)"), SIGFPE, webprocessPID, errno, strerror(errno))); } } else { DeactivateBrowser(PluginHost::IShell::FAILURE); @@ -3388,7 +4574,7 @@ static GSourceFuncs _handlerIntervention = Logging::DumpSystemFiles(webprocessPID); if (syscall(__NR_tgkill, webprocessPID, webprocessPID, SIGFPE) == -1) { - SYSLOG(Trace::Error, (_T("tgkill failed, signal=%d process=%u errno=%d (%s)"), SIGFPE, webprocessPID, errno, strerror(errno))); + SYSLOG(Logging::Error, (_T("tgkill failed, signal=%d process=%u errno=%d (%s)"), SIGFPE, webprocessPID, errno, strerror(errno))); } } else if (_unresponsiveReplyNum == (2 * kWebProcessUnresponsiveReplyDefaultLimit)) { DeactivateBrowser(PluginHost::IShell::WATCHDOG_EXPIRED); @@ -3403,7 +4589,7 @@ static GSourceFuncs _handlerIntervention = if (self->_unresponsiveReplyNum > 0) { std::string activeURL(webkit_web_view_get_uri(self->_view)); - SYSLOG(Logging::Notification, (_T("WebProcess recovered after %d unresponsive replies, url=%s\n"), + SYSLOG_GLOBAL(Logging::Notification, (_T("WebProcess recovered after %d unresponsive replies, url=%s\n"), self->_unresponsiveReplyNum, activeURL.c_str())); self->_unresponsiveReplyNum = 0; } @@ -3417,7 +4603,7 @@ static GSourceFuncs _handlerIntervention = std::string activeURL = GetPageActiveURL(page); pid_t webprocessPID = WKPageGetProcessIdentifier(page); - SYSLOG(Logging::Notification, (_T("WebProcess recovered after %d unresponsive replies, pid=%u, url=%s\n"), + SYSLOG_GLOBAL(Logging::Notification, (_T("WebProcess recovered after %d unresponsive replies, pid=%u, url=%s\n"), self._unresponsiveReplyNum, webprocessPID, activeURL.c_str())); self._unresponsiveReplyNum = 0; } @@ -3426,12 +4612,26 @@ static GSourceFuncs _handlerIntervention = void DeactivateBrowser(PluginHost::IShell::reason reason) { ASSERT(_service != nullptr); - Core::IWorkerPool::Instance().Submit(PluginHost::IShell::Job::Create(_service, PluginHost::IShell::DEACTIVATED, reason)); + const char *reasonStr = Core::EnumerateType(reason).Data(); + SYSLOG(Logging::Fatal, (_T("Posting a job to exit, reason - %s"), (reasonStr ? reasonStr : ""))); + postExitJob(); } private: Config _config; - string _URL; + + struct { + mutable std::mutex mutex; + std::condition_variable cond; + string url; + uint32_t result = Core::ERROR_TIMEDOUT; + struct { + bool waitForFailedOrFinished = false; + bool waitForExceptionalPageClosureAfterBootUrl = false; + string loadUrl; + } loadResult; + } urlData_; + string _dataPath; PluginHost::IShell* _service; string _headers; @@ -3477,6 +4677,30 @@ static GSourceFuncs _handlerIntervention = uint32_t _unresponsiveReplyNum; unsigned _frameCount; gint64 _lastDumpTime; + std::list _userScripts{}; + std::list _userStyleSheets{}; +#if HAS_SCREEN_HDR_API + PluginHost::IPlugin* _displayInfoPlugin; + Exchange::IConnectionProperties* _displayConnectionProps; + Core::ProxyType _hdrRefreshJob; + bool _hdrSupported; +#endif // HAS_SCREEN_HDR_API + + void notifyUrlLoadResult(const string &URL, uint32_t result) + { + std::unique_lock lock{urlData_.mutex}; + TRACE_L1("waitForFailedOrFinished = %d, result = %s, url = %s", + urlData_.loadResult.waitForFailedOrFinished, + Core::ERROR_NONE == result ? "OK" : "NOK", + URL.c_str()); + if (urlData_.loadResult.waitForFailedOrFinished && normalizedHostNamesInUrlsEqual(URL, urlData_.loadResult.loadUrl)) { + TRACE_L1("Notyfying with result = %s, url: %s\n", Core::ERROR_NONE == result ? "OK" : "NOK", URL.c_str()); + urlData_.result = result; + urlData_.loadResult.waitForFailedOrFinished = false; + urlData_.loadResult.loadUrl = string(""); + urlData_.cond.notify_one(); + } + } }; SERVICE_REGISTRATION(WebKitImplementation, 1, 0); @@ -3655,7 +4879,7 @@ static GSourceFuncs _handlerIntervention = /* static */ void webProcessDidCrash(WKPageRef, const void*) { - SYSLOG(Trace::Fatal, (_T("CRASH: WebProcess crashed, exiting..."))); + SYSLOG_GLOBAL(Trace::Fatal, (_T("CRASH: WebProcess crashed, exiting..."))); exit(1); } @@ -3668,3 +4892,12 @@ static GSourceFuncs _handlerIntervention = } // namespace Plugin } // namespace WPEFramework + +extern "C" __attribute__((__visibility__("default"))) void ModuleUnload() + +{ + // Before plugin library is unloaded that function would be invoked by WPEProcess before dlclose call + // implement here important deinitialization of the library, in our case we know about the following issues with libWPEWebkit library: + // * terminate the BMScavenger thread + // * terminate the PressureMonitor thread +} diff --git a/WebKitBrowser/cmake/FindOdhErrTelemetry.cmake b/WebKitBrowser/cmake/FindOdhErrTelemetry.cmake new file mode 100644 index 0000000000..4c5654dd8f --- /dev/null +++ b/WebKitBrowser/cmake/FindOdhErrTelemetry.cmake @@ -0,0 +1,45 @@ +#============================================================================ +# Copyright (c) 2023 Liberty Global +#============================================================================ + +# - Try to find ODH telemetry +# +# Once done this will define +# OdhErrTelemetry_FOUND - System has the component +# OdhErrTelemetry_INCLUDE_DIRS - Component include directories +# OdhErrTelemetry_LIBRARIES - Libraries needed to use the component + +find_package(PkgConfig QUIET) + +find_path(OdhErrTelemetry_INCLUDE_DIR + NAMES odherr.h + HINTS ${TARGET_SYS_ROOT}/usr/include/rdk/libodherr + ) + +find_library(OdhErrTelemetry_LIBRARY + NAMES odherr + HINTS ${TARGET_SYS_ROOT}/usr/lib + ) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(OdhErrTelemetry + FOUND_VAR OdhErrTelemetry_FOUND + REQUIRED_VARS OdhErrTelemetry_LIBRARY OdhErrTelemetry_INCLUDE_DIR +) + +if (OdhErrTelemetry_LIBRARY AND NOT TARGET OdhErrTelemetry::OdhErrTelemetry) + add_library(OdhErrTelemetry::OdhErrTelemetry UNKNOWN IMPORTED GLOBAL) + set_target_properties(OdhErrTelemetry::OdhErrTelemetry PROPERTIES + IMPORTED_LOCATION "${OdhErrTelemetry_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${OdhErrTelemetry_COMPILE_OPTIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${OdhErrTelemetry_INCLUDE_DIR}" + ) +endif () + +mark_as_advanced(OdhErrTelemetry_INCLUDE_DIR OdhErrTelemetry_LIBRARY) + +if (OdhErrTelemetry_FOUND) + set(OdhErrTelemetry_INCLUDE_DIRS ${OdhErrTelemetry_INCLUDE_DIR}) + set(OdhErrTelemetry_LIBRARIES ${OdhErrTelemetry_LIBRARY}) + set(OdhErrTelemetry_PKG_EXTRA_LIBS "-lodherr") +endif () diff --git a/WebKitBrowser/odhlog.h b/WebKitBrowser/odhlog.h new file mode 100644 index 0000000000..72f5116618 --- /dev/null +++ b/WebKitBrowser/odhlog.h @@ -0,0 +1,40 @@ +/* + * Helper functions to send messages to ODH. + */ +#pragma once + +#ifdef USE_ODH_TELEMETRY + +#include +#include + +#define WPE_CONTEXT odh_ctx_create_json("wpe", "ss", "function", __PRETTY_FUNCTION__, "file", __FILE__) +#define WPE_CONTEXT_WITH_URL(url) odh_ctx_create_json("wpe", "sss", "function", __PRETTY_FUNCTION__, "file", __FILE__, "url", url) + +#define ODH_REPORT_SEND(level, code, ctx, fmt, ...) do { \ + char *msg = odh_error_report_sprintf(fmt, ##__VA_ARGS__); \ + odh_error_report_send_v3(ODH_ERROR_REPORT_SENSITIVITY_NONSENSITIVE, \ + level, \ + code, \ + NULL, \ + msg, \ + ctx, \ + ODH_ERROR_REPORT_BACKTRACE, \ + "browser"); \ + free(ctx); \ + free(msg); \ +} while (0) + +#define ODH_ERROR(code, ctx, fmt, ...) ODH_REPORT_SEND(ODH_ERROR_REPORT_LEVEL_ERROR, code, ctx, fmt, ##__VA_ARGS__) +#define ODH_WARNING(code, ctx, fmt, ...) ODH_REPORT_SEND(ODH_ERROR_REPORT_LEVEL_WARNING, code, ctx, fmt, ##__VA_ARGS__) +#define ODH_CRITICAL(code, ctx, fmt, ...) ODH_REPORT_SEND(ODH_ERROR_REPORT_LEVEL_CRITICAL, code, ctx, fmt, ##__VA_ARGS__) + +#else /* USE_ODH_TELEMETRY */ + +#define WPE_CONTEXT +#define WPE_CONTEXT_WITH_URL(url) +#define ODH_ERROR(code, ctx, fmt, ...) +#define ODH_WARNING(code, ctx, fmt, ...) +#define ODH_CRITICAL(code, ctx, fmt, ...) + +#endif /* USE_ODH_TELEMETRY */ diff --git a/WifiManager/CHANGELOG.md b/WifiManager/CHANGELOG.md index a001d91d82..8e372bfca7 100644 --- a/WifiManager/CHANGELOG.md +++ b/WifiManager/CHANGELOG.md @@ -23,10 +23,6 @@ All notable changes to this RDK Service will be documented in this file. ### Fixed - Fixed IARM_Bus_UnRegisterEventHandler call to IARM_Bus_RemoveEventHandler -## [1.0.5] - 2023-02-16 -### Fixed -- Fix for WPEFramework crash with callstack std::execute - ## [1.0.4] - 2022-12-13 ### Added - Added WifiManager plugin unitTest cases. diff --git a/WifiManager/CMakeLists.txt b/WifiManager/CMakeLists.txt index 178bc8a292..e55934cdf2 100644 --- a/WifiManager/CMakeLists.txt +++ b/WifiManager/CMakeLists.txt @@ -19,23 +19,41 @@ set(PLUGIN_NAME WifiManager) set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) set(PLUGIN_WIFI_STARTUPORDER "" CACHE STRING "To configure startup order of WifiManager plugin") +set(PLUGIN_WIFI_IMPL "impl" CACHE STRING "To select the implementation folder") find_package(${NAMESPACE}Plugins REQUIRED) add_library(${MODULE_NAME} SHARED WifiManager.cpp Module.cpp - impl/WifiManagerWPS.cpp - impl/WifiManagerSignalThreshold.cpp - impl/WifiManagerState.cpp - impl/WifiManagerConnect.cpp - impl/WifiManagerScan.cpp - impl/WifiManagerEvents.cpp) + ${PLUGIN_WIFI_IMPL}/WifiManagerWPS.cpp + ${PLUGIN_WIFI_IMPL}/WifiManagerSignalThreshold.cpp + ${PLUGIN_WIFI_IMPL}/WifiManagerState.cpp + ${PLUGIN_WIFI_IMPL}/WifiManagerConnect.cpp + ${PLUGIN_WIFI_IMPL}/WifiManagerScan.cpp + ${PLUGIN_WIFI_IMPL}/WifiManagerEvents.cpp) + +# for LG variant also add dbus implementation +if(${PLUGIN_WIFI_IMPL} STREQUAL "impl_lg") + find_package(LibGlib REQUIRED) + find_package(LibGio REQUIRED) + file(GLOB DBUS_SOURCES_CPP "impl_lg/dbus/*.cpp") + file(GLOB DBUS_SOURCES_C "impl_lg/dbus/*.c") + target_sources(${MODULE_NAME} PRIVATE ${DBUS_SOURCES_CPP} ${DBUS_SOURCES_C}) + include_directories(${LIBGLIB_INCLUDE_DIRS}) + include_directories(${LIBGIO_INCLUDE_DIRS}) + include_directories(./) + target_link_libraries(${MODULE_NAME} + PRIVATE + ${LIBGIO_LIBRARIES} + ${LIBGLIB_LIBRARIES}) + add_definitions( -DIMPL_LGI ) +endif() set_source_files_properties( WifiManager.cpp - impl/WifiManagerConnect.cpp - impl/WifiManagerScan.cpp + ${PLUGIN_WIFI_IMPL}/WifiManagerConnect.cpp + ${PLUGIN_WIFI_IMPL}/WifiManagerScan.cpp PROPERTIES COMPILE_FLAGS "-fexceptions") set_target_properties(${MODULE_NAME} PROPERTIES @@ -45,7 +63,8 @@ set_target_properties(${MODULE_NAME} PROPERTIES list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") -target_include_directories(${MODULE_NAME} PRIVATE ${IARMBUS_INCLUDE_DIRS} ../helpers) +target_include_directories(${MODULE_NAME} PRIVATE ${IARMBUS_INCLUDE_DIRS} ../helpers ${PLUGIN_WIFI_IMPL}) + target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${IARMBUS_LIBRARIES}) diff --git a/WifiManager/WifiManager.cpp b/WifiManager/WifiManager.cpp index a54c66c3e0..09e22c36b1 100644 --- a/WifiManager/WifiManager.cpp +++ b/WifiManager/WifiManager.cpp @@ -19,13 +19,11 @@ #include "WifiManager.h" #include "UtilsJsonRpc.h" -#include "UtilsIarm.h" +#include "WifiImplementation.h" #include #include -#include "libIBus.h" - // TODO: remove this #define registerMethod(...) for (uint8_t i = 1; GetHandler(i); i++) GetHandler(i)->Register(__VA_ARGS__) @@ -97,7 +95,7 @@ namespace WPEFramework const string WifiManager::Initialize(PluginHost::IShell* service) { - Utils::IARM::init(); + WifiImplementation::init(); if (instance != nullptr) { LOGERR("Expecting 'instance' to be initially unset; two instances of the plugin?"); @@ -108,6 +106,9 @@ namespace WPEFramework // Initialize other parts of the implementation string const scanMessage = wifiScan.Initialize(service); string const eventsMessage = wifiEvents.Initialize(service); +#ifdef IMPL_LGI + wifiState.Initialize(); +#endif // Combine their error messages (if any) return scanMessage + eventsMessage; @@ -116,7 +117,7 @@ namespace WPEFramework void WifiManager::Deinitialize(PluginHost::IShell* service) { wifiScan.Deinitialize(service); - wifiSignalThreshold.stopSignalThresholdThread(); + WifiImplementation::deinit(); instance = nullptr; } diff --git a/WifiManager/WifiManager.h b/WifiManager/WifiManager.h index 5cf20a4f4d..b1e2c0fcff 100644 --- a/WifiManager/WifiManager.h +++ b/WifiManager/WifiManager.h @@ -22,12 +22,12 @@ #include "Module.h" #include "WifiManagerDefines.h" #include "WifiManagerInterface.h" -#include "impl/WifiManagerWPS.h" -#include "impl/WifiManagerState.h" -#include "impl/WifiManagerConnect.h" -#include "impl/WifiManagerSignalThreshold.h" -#include "impl/WifiManagerScan.h" -#include "impl/WifiManagerEvents.h" +#include "WifiManagerWPS.h" +#include "WifiManagerState.h" +#include "WifiManagerConnect.h" +#include "WifiManagerSignalThreshold.h" +#include "WifiManagerScan.h" +#include "WifiManagerEvents.h" namespace WPEFramework { diff --git a/WifiManager/impl/WifiImplementation.h b/WifiManager/impl/WifiImplementation.h new file mode 100644 index 0000000000..4c460a3668 --- /dev/null +++ b/WifiManager/impl/WifiImplementation.h @@ -0,0 +1,13 @@ +#include "UtilsIarm.h" +#include "libIBus.h" + +namespace WPEFramework { + namespace Plugin { + namespace WifiImplementation { + void init() { + Utils::IARM::init(); + } + + void deinit() {} + } +}} \ No newline at end of file diff --git a/WifiManager/impl/WifiManagerSignalThreshold.cpp b/WifiManager/impl/WifiManagerSignalThreshold.cpp index c6995efc04..04f059ac75 100644 --- a/WifiManager/impl/WifiManagerSignalThreshold.cpp +++ b/WifiManager/impl/WifiManagerSignalThreshold.cpp @@ -98,12 +98,7 @@ WifiManagerSignalThreshold::WifiManagerSignalThreshold(): { } - WifiManagerSignalThreshold::~WifiManagerSignalThreshold() -{ -} - -void WifiManagerSignalThreshold::stopSignalThresholdThread() { stopThread(); } @@ -192,6 +187,7 @@ void WifiManagerSignalThreshold::loop(int interval) { getSignalData(signalStrength, strength); + if (strength != lastStrength) { LOGINFO("Triggering onWifiSignalThresholdChanged notification"); diff --git a/WifiManager/impl/WifiManagerSignalThreshold.h b/WifiManager/impl/WifiManagerSignalThreshold.h index 748276f012..ad1e3f888b 100644 --- a/WifiManager/impl/WifiManagerSignalThreshold.h +++ b/WifiManager/impl/WifiManagerSignalThreshold.h @@ -48,7 +48,6 @@ namespace WPEFramework { uint32_t setSignalThresholdChangeEnabled(const JsonObject& parameters, JsonObject& response); void setSignalThresholdChangeEnabled(bool enable); uint32_t isSignalThresholdChangeEnabled(const JsonObject& parameters, JsonObject& response) const; - void stopSignalThresholdThread(); private: void setSignalThresholdChangeEnabled(bool enabled, int interval); diff --git a/WifiManager/impl_lg/WifiImplementation.h b/WifiManager/impl_lg/WifiImplementation.h new file mode 100644 index 0000000000..885796d577 --- /dev/null +++ b/WifiManager/impl_lg/WifiImplementation.h @@ -0,0 +1,13 @@ +#include "dbus/DBusClient.h" + +namespace WPEFramework { + namespace Plugin { + namespace WifiImplementation { + void init() { + WifiManagerImpl::DBusClient::getInstance().run(); + } + void deinit() { + WifiManagerImpl::DBusClient::getInstance().stop(); + } + } +}} \ No newline at end of file diff --git a/WifiManager/impl_lg/WifiManagerConnect.cpp b/WifiManager/impl_lg/WifiManagerConnect.cpp new file mode 100644 index 0000000000..6483967b48 --- /dev/null +++ b/WifiManager/impl_lg/WifiManagerConnect.cpp @@ -0,0 +1,38 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "WifiManagerConnect.h" +// #include "UtilsLogging.h" +#include "UtilsJsonRpc.h" + +#include + +using namespace WPEFramework::Plugin; + +uint32_t WifiManagerConnect::connect(const JsonObject ¶meters, JsonObject &response) +{ + LOGINFOMETHOD(); + returnResponse(false); +} + +uint32_t WifiManagerConnect::disconnect(const JsonObject ¶meters, JsonObject &response) +{ + LOGINFOMETHOD(); + returnResponse(false); +} diff --git a/WifiManager/impl_lg/WifiManagerConnect.h b/WifiManager/impl_lg/WifiManagerConnect.h new file mode 100644 index 0000000000..45afe1cfa0 --- /dev/null +++ b/WifiManager/impl_lg/WifiManagerConnect.h @@ -0,0 +1,40 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once + +#include "../Module.h" +#include "../WifiManagerDefines.h" + +#include + +namespace WPEFramework { + namespace Plugin { + class WifiManagerConnect { + public: + WifiManagerConnect() = default; + virtual ~WifiManagerConnect() = default; + WifiManagerConnect(const WifiManagerConnect&) = delete; + WifiManagerConnect& operator=(const WifiManagerConnect&) = delete; + + uint32_t connect(const JsonObject& parameters, JsonObject& response); + uint32_t disconnect(const JsonObject& parameters, JsonObject& response); + }; + } +} diff --git a/WifiManager/impl_lg/WifiManagerEvents.cpp b/WifiManager/impl_lg/WifiManagerEvents.cpp new file mode 100644 index 0000000000..0376e9fca8 --- /dev/null +++ b/WifiManager/impl_lg/WifiManagerEvents.cpp @@ -0,0 +1,55 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +/** + * WifiManager implementation related to events. + * + */ + +#include "WifiManagerEvents.h" +#include "../WifiManager.h" // Need access to WifiManager::getInstance so can't use 'WifiManagerInterface.h' +#include "UtilsLogging.h" + + +using namespace WPEFramework; +using namespace WPEFramework::Plugin; + +/** + * \brief Register event handlers. + * + */ +std::string WifiManagerEvents::Initialize(PluginHost::IShell*) +{ + LOGINFO("initializing"); + /* + WifiManager::onWifiSignalThresholdChanged is already handled by WifiManagerSignalThreshold + WifiManager::onWIFIStateChanged is already handled by WifiManagerState + */ + // Successful + return string(); +} + +/** + * \brief Unregister event handlers. + * + */ +void WifiManagerEvents::Deinitialize(PluginHost::IShell*) +{ + LOGINFO("deinitializing"); +} diff --git a/WifiManager/impl_lg/WifiManagerEvents.h b/WifiManager/impl_lg/WifiManagerEvents.h new file mode 100644 index 0000000000..bafbc5a51e --- /dev/null +++ b/WifiManager/impl_lg/WifiManagerEvents.h @@ -0,0 +1,44 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once + +#include "../Module.h" + +// Forward declaration +typedef int IARM_EventId_t; + +namespace WPEFramework { + namespace Plugin { + /** + * The internal implementation of the functionality in WifiManager relating to event generation. + * + */ + class WifiManagerEvents { + public: + WifiManagerEvents() = default; + virtual ~WifiManagerEvents() = default; + WifiManagerEvents(WifiManagerEvents const&) = delete; + WifiManagerEvents& operator=(WifiManagerEvents const&) = delete; + + std::string Initialize(WPEFramework::PluginHost::IShell* service); + void Deinitialize(WPEFramework::PluginHost::IShell* service); + }; + } // namespace Plugin +} // namespace WPEFramework diff --git a/WifiManager/impl_lg/WifiManagerScan.cpp b/WifiManager/impl_lg/WifiManagerScan.cpp new file mode 100644 index 0000000000..10062572bf --- /dev/null +++ b/WifiManager/impl_lg/WifiManagerScan.cpp @@ -0,0 +1,85 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +/** + * WifiManager implementation related to performing scans of wireless networks. + * + */ + +#include "WifiManagerScan.h" +// #include "../WifiManager.h" // Need access to WifiManager::getInstance so can't use 'WifiManagerInterface.h' +#include "UtilsJsonRpc.h" + +// std +#include +#include + + +using namespace WPEFramework; +using namespace WPEFramework::Plugin; + +/** + * \brief Register event handlers. + * + */ +std::string WifiManagerScan::Initialize(PluginHost::IShell*) +{ + LOGINFO("initializing"); + // Successful + return string(); +} + +/** + * \brief Unregister event handlers. + * + */ +void WifiManagerScan::Deinitialize(PluginHost::IShell* service) +{ + LOGINFO("deinitializing"); +} + +/** + * \brief Get the available access points asynchronously. + * + * The results are published on via the "onAvailableSSIDs" event. + * + * \param parameters Must include 'incremental'. Optionally includes 'ssid' and/or 'frequency'. + * \param[out] response Always includes 'success' if successful. + * \return A code indicating success. + * + */ + +uint32_t WifiManagerScan::startScan(const JsonObject ¶meters, JsonObject &response) const +{ + LOGINFOMETHOD(); + returnResponse(false); +} + +/** + * \brief Cancel a current incremental asynchronous scan started by 'getAvailableSSIDsAsyncIncr'. + * + * \param parameters Ignored. + * \param[out] response Will contain 'success' key. + * \return Error code. + * + */ +uint32_t WifiManagerScan::stopScan(const JsonObject& parameters, JsonObject& response) +{ + returnResponse(false); +} diff --git a/WifiManager/impl_lg/WifiManagerScan.h b/WifiManager/impl_lg/WifiManagerScan.h new file mode 100644 index 0000000000..124bdffed7 --- /dev/null +++ b/WifiManager/impl_lg/WifiManagerScan.h @@ -0,0 +1,45 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once + +#include "../Module.h" +#include + +namespace WPEFramework { + namespace Plugin { + /** + * The internal implementation of the functionality in WifiManager relating to scanning for access points. + * + */ + class WifiManagerScan { + public: + WifiManagerScan() = default; + virtual ~WifiManagerScan() = default; + WifiManagerScan(WifiManagerScan const&) = delete; + WifiManagerScan& operator=(WifiManagerScan const&) = delete; + + std::string Initialize(PluginHost::IShell* service); + void Deinitialize(PluginHost::IShell* service); + + uint32_t startScan(const JsonObject& parameters, JsonObject& response) const; + uint32_t stopScan(const JsonObject& parameters, JsonObject& response); + }; + } // namespace Plugin +} // namespace WPEFramework diff --git a/WifiManager/impl_lg/WifiManagerSignalThreshold.cpp b/WifiManager/impl_lg/WifiManagerSignalThreshold.cpp new file mode 100644 index 0000000000..1b743de00f --- /dev/null +++ b/WifiManager/impl_lg/WifiManagerSignalThreshold.cpp @@ -0,0 +1,211 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "WifiManagerSignalThreshold.h" +#include "../WifiManager.h" // Need access to WifiManager::getInstance so can't use 'WifiManagerInterface.h' +#include "UtilsJsonRpc.h" +#include "dbus/DBusClient.h" +#include +#include "WifiManagerState.h" + +using namespace WPEFramework::Plugin; + +namespace { + + const float signalStrengthThresholdExcellent = -50.0f; + const float signalStrengthThresholdGood = -60.0f; + const float signalStrengthThresholdFair = -67.0f; + + std::string retrieveValue() { + const std::string& wifiInterface = WifiManagerState::getWifiInterfaceName(); + if (wifiInterface.empty()) { + return ""; + } + std::string result = ""; + if (DBusClient::getInstance().networkconfig1_GetParam(wifiInterface, "wifi.rssi", result)) { + return result; + } else { + LOGWARN("failed to retrieve wifi.rssi param"); + } + return ""; + } + + void getSignalData(float &signalStrengthOut, std::string &strengthOut) { + JsonObject response; + + string signalStrength = retrieveValue(); + + signalStrengthOut = 0.0f; + if (!signalStrength.empty()) + signalStrengthOut = std::stof(signalStrength.c_str()); + else { + LOGERR("signalStrength is empty\n"); + strengthOut = "Disconnected"; + return; + } + + if (signalStrengthOut >= signalStrengthThresholdExcellent && signalStrengthOut < 0) + { + strengthOut = "Excellent"; + } + else if (signalStrengthOut >= signalStrengthThresholdGood && signalStrengthOut < signalStrengthThresholdExcellent) + { + strengthOut = "Good"; + } + else if (signalStrengthOut >= signalStrengthThresholdFair && signalStrengthOut < signalStrengthThresholdGood) + { + strengthOut = "Fair"; + } + else + { + strengthOut = "Weak"; + }; + } +} + +WifiManagerSignalThreshold::WifiManagerSignalThreshold() : + changeEnabled(false), + running(false) +{ +} + +WifiManagerSignalThreshold::~WifiManagerSignalThreshold() +{ + stopThread(); +} + +uint32_t WifiManagerSignalThreshold::setSignalThresholdChangeEnabled(const JsonObject ¶meters, JsonObject &response) +{ + LOGINFOMETHOD(); + returnIfBooleanParamNotFound(parameters, "enabled"); + returnIfNumberParamNotFound(parameters, "interval"); + + bool enabled = parameters["enabled"].Boolean(); + int interval = parameters["interval"].Number(); + + setSignalThresholdChangeEnabled(enabled, interval); + + returnResponse(true); +} + +uint32_t WifiManagerSignalThreshold::isSignalThresholdChangeEnabled(const JsonObject ¶meters, JsonObject &response) const +{ + LOGINFOMETHOD(); + + if(isSignalThresholdChangeEnabled()) + { + response["result"] = 0; + } + else + { + response["result"] = 1; + } + + returnResponse(true); +} + +void WifiManagerSignalThreshold::setSignalThresholdChangeEnabled(bool enabled, int interval) +{ + LOGINFO("setSignalThresholdChangeEnabled: enabled %s, interval %d", enabled ? "true":"false", interval); + + stopThread(); + + changeEnabled = enabled; + JsonObject parameters, response; + WifiState state; + + uint32_t result = WifiManager::getInstance().getCurrentState(parameters, response); + if (result != 0) + { + LOGINFO("wifiManager.getCurrentState result = %d", result); + return; + } + else if (response.HasLabel("state")) + { + int64_t number = std::stoi(response["state"].String()); + state = (WifiState) number; + LOGINFO("wifi state = %d", static_cast(state)); + } + else + { + LOGINFO("no state attribute"); + return; + } + + if(changeEnabled) + { + if (state == WifiState::CONNECTED) + running = true; + startThread(interval); + } +} + +bool WifiManagerSignalThreshold::isSignalThresholdChangeEnabled() const +{ + return changeEnabled; +} + +void WifiManagerSignalThreshold::loop(int interval) +{ + std::unique_lock lk(cv_mutex); + std::string lastStrength = ""; + + while(changeEnabled) + { + float signalStrength; + std::string strength; + if (running) + { + getSignalData(signalStrength, strength); + + + if (strength != lastStrength) + { + LOGINFO("Triggering onWifiSignalThresholdChanged notification"); + WifiManager::getInstance().onWifiSignalThresholdChanged(signalStrength, strength); + lastStrength = strength; + } + } + cv.wait_for(lk, std::chrono::milliseconds(interval), [this](){ return changeEnabled == false; }); + } +} + +void WifiManagerSignalThreshold::stopThread() +{ + changeEnabled = false; + cv.notify_one(); + if(thread.joinable()) { + thread.join(); + } + running = false ; +} + + +void WifiManagerSignalThreshold::setSignalThresholdChangeEnabled(bool enable) +{ + LOGINFO("setSignalThresholdChangeEnabled: enable %s", enable ? "true":"false"); + running = enable; +} + +void WifiManagerSignalThreshold::startThread(int interval) +{ + thread = std::thread([interval, this](){ + loop(interval); + }); +} diff --git a/WifiManager/impl_lg/WifiManagerSignalThreshold.h b/WifiManager/impl_lg/WifiManagerSignalThreshold.h new file mode 100644 index 0000000000..70d144d9b9 --- /dev/null +++ b/WifiManager/impl_lg/WifiManagerSignalThreshold.h @@ -0,0 +1,68 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once + +#include "../Module.h" +#include "../WifiManagerDefines.h" + +#include +#include +#include +#include + +namespace WPEFramework { + namespace Plugin { + class WifiManagerSignalThreshold { + // This class realizes the following methods: + // - setSignalThresholdChangeEnabled + // - isSignalThresholdChangeEnabled + // And the following event: + // - onWifiSignalTresholdChanged + // From WifiManager module. + // + // As the onWifiSignalTresholdChanged event is signalled periodically, + // it has to be handled with an additional thread. + public: + WifiManagerSignalThreshold(); + virtual ~WifiManagerSignalThreshold(); + WifiManagerSignalThreshold(const WifiManagerSignalThreshold&) = delete; + WifiManagerSignalThreshold& operator=(const WifiManagerSignalThreshold&) = delete; + + uint32_t setSignalThresholdChangeEnabled(const JsonObject& parameters, JsonObject& response); + void setSignalThresholdChangeEnabled(bool enable); + uint32_t isSignalThresholdChangeEnabled(const JsonObject& parameters, JsonObject& response) const; + + private: + void setSignalThresholdChangeEnabled(bool enabled, int interval); + bool isSignalThresholdChangeEnabled() const; + + void loop(int interval); + void stopThread(); + void startThread(int interval); + + private: + std::thread thread; + std::atomic changeEnabled; + std::mutex cv_mutex; + std::condition_variable cv; + std::atomic_bool running; + }; + } +} diff --git a/WifiManager/impl_lg/WifiManagerState.cpp b/WifiManager/impl_lg/WifiManagerState.cpp new file mode 100644 index 0000000000..cd25979987 --- /dev/null +++ b/WifiManager/impl_lg/WifiManagerState.cpp @@ -0,0 +1,276 @@ +/** + * If not stated otherwise in this file or this component's LICENSE + * file the following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +#include "WifiManagerState.h" +#include "UtilsJsonRpc.h" +#include "../WifiManager.h" // Need access to WifiManager::getInstance so can't use 'WifiManagerInterface.h' + +using namespace WPEFramework::Plugin; +using namespace WifiManagerImpl; + +WifiManagerState::WifiManagerState() +{ +} + +void WifiManagerState::Initialize() +{ + DBusClient &dbus = DBusClient::getInstance(); + if (getWifiInterfaceName().empty()) + { + LOGWARN("No 'wifi' interface found"); + // TODO: throw an exception? + } + else + { + // register for status updates + dbus.registerStatusChanged(std::bind(&WifiManagerState::statusChanged, this, std::placeholders::_1, std::placeholders::_2)); + // get current wifi status + InterfaceStatus status; + const std::string& iname = getWifiInterfaceName(); + if (dbus.networkconfig1_GetStatus(iname, status)) + { + updateWifiStatus(status); + } + else + { + LOGWARN("failed to get interface '%s' status", iname.c_str()); + } + } +} + +WifiManagerState::~WifiManagerState() +{ +} + +namespace +{ + /* + `0`: UNINSTALLED - The device was in an installed state and was uninstalled; or, the device does not have a Wifi radio installed + `1`: DISABLED - The device is installed but not yet enabled + `2`: DISCONNECTED - The device is installed and enabled, but not yet connected to a network + `3`: PAIRING - The device is in the process of pairing, but not yet connected to a network + `4`: CONNECTING - The device is attempting to connect to a network + `5`: CONNECTED - The device is successfully connected to a network + */ + // for the moment, only state we need is 'CONNECTED' (the only state that Amazon app needs) + const std::map statusToState{ + {Disabled, WifiState::DISABLED}, + {Disconnected, WifiState::DISCONNECTED}, + {Associating, WifiState::CONNECTING}, + {Dormant, WifiState::DISCONNECTED}, + {Binding, WifiState::CONNECTING}, + {Assigned, WifiState::CONNECTED}, + {Scanning, WifiState::CONNECTING}}; +} + +uint32_t WifiManagerState::getCurrentState(const JsonObject ¶meters, JsonObject &response) +{ + // this is used by Amazon, but only 'state' is used by Amazon app and needs to be provided; the rest is not important + LOGINFOMETHOD(); + response["state"] = static_cast(m_wifi_state.load()); + returnResponse(true); +} + +static bool extractSsid(const std::string &netid, std::string &out_ssid) +{ + size_t pos = netid.find(":"); + if (pos != std::string::npos) + { + out_ssid = netid.substr(pos + 1); + return true; + } + else + { + return false; + } +} + +bool WifiManagerState::fetchSsid(std::string &out_ssid) +{ + const std::string &wifiInterface = getWifiInterfaceName(); + bool ret = false; + if (!wifiInterface.empty()) + { + std::string netid; + if (DBusClient::getInstance().networkconfig1_GetParam(wifiInterface, "netid", netid)) + { + std::string ssid; + if (extractSsid(netid, ssid)) + { + out_ssid = ssid; + if (ssid != m_latest_ssid) + { + WifiManager::getInstance().onSSIDsChanged(); + m_latest_ssid = ssid; + } + ret = true; + } + else + { + LOGWARN("failed to parse ssid from netid: %s", netid.c_str()); + } + } + } + return ret; +} + +std::string WifiManagerState::getWifiRssi() +{ + std::string result = ""; + const std::string& wifiInterface = WifiManagerState::getWifiInterfaceName(); + if (wifiInterface.empty()) { + return result; + } + if (!DBusClient::getInstance().networkconfig1_GetParam(wifiInterface, "wifi.rssi", result)) { + LOGWARN("failed to retrieve wifi.rssi for interface '%s' ", wifiInterface.c_str()); + } + return result; +} + +bool WifiManagerState::getWifiParams(std::map ¶ms) +{ + const std::string& wifiInterface = WifiManagerState::getWifiInterfaceName(); + if (wifiInterface.empty()) { + return false; + } + std::string netid; + if (DBusClient::getInstance().networkconfig1_GetParam(wifiInterface, "netid", netid)) { + if (DBusClient::getInstance().wifimanagement1_GetSSIDParams(wifiInterface, netid, params)) { + return true; + } else { + LOGWARN("failed to retrieve ssid '%s' params", netid.c_str()); + } + } else { + LOGWARN("failed to retrieve wifi netid param"); + } + return false; +} + + +uint32_t WifiManagerState::getConnectedSSID(const JsonObject ¶meters, JsonObject &response) +{ + LOGINFOMETHOD(); + std::string ssid; + if (fetchSsid(ssid)) + { + // only 'ssid' is used by Amazon app and needs to be returned; the rest can be empty for now + response["ssid"] = ssid; + std::map params; + if (getWifiParams(params)) + { + response["bssid"] = params["bssid"]; + response["rate"] = string(""); // no mapping + response["noise"] = string(""); // no mapping + response["security"] = params["security"]; + response["signalStrength"] = getWifiRssi(); + response["frequency"] = params["band"]; + } else { + response["bssid"] = string(""); + response["rate"] = string(""); + response["noise"] = string(""); + response["security"] = string(""); + response["signalStrength"] = string(""); + response["frequency"] = string(""); + } + returnResponse(true); + } + else + { + returnResponse(false); + } +} + +void WifiManagerState::statusChanged(const std::string &interface, InterfaceStatus status) +{ + if (interface == getWifiInterfaceName()) + { + updateWifiStatus(status); + } +} + +void WifiManagerState::updateWifiStatus(WifiManagerImpl::InterfaceStatus status) +{ + bool state_changed = false; + auto lookup = statusToState.find(status); + + if (lookup != statusToState.end()) + { + if (lookup->second != m_wifi_state) + { + m_wifi_state = lookup->second; + state_changed = true; + } + } + else + { + LOGWARN("unknown status: %d", status); + } + + if (state_changed) + { + if (m_wifi_state == WifiState::CONNECTED) + { + static std::string _; + // fetchSsid will raise onSSIDsChanged event in case ssid was changed + fetchSsid(_); + } + // Hardcode 'isLNF' for the moment (at the moment, the same is done in default rdk implementation) + WifiManager::getInstance().onWIFIStateChanged(m_wifi_state, false); + } +} + +uint32_t WifiManagerState::setEnabled(const JsonObject ¶meters, JsonObject &response) +{ + LOGINFOMETHOD(); + returnResponse(false); +} + +uint32_t WifiManagerState::getSupportedSecurityModes(const JsonObject ¶meters, JsonObject &response) +{ + LOGINFOMETHOD(); + returnResponse(false); +} + +std::string WifiManagerState::fetchWifiInterfaceName() +{ + DBusClient &dbus = DBusClient::getInstance(); + std::vector interfaces; + if (dbus.networkconfig1_GetInterfaces(interfaces)) + { + for (auto &intf : interfaces) + { + std::string type; + if (dbus.networkconfig1_GetParam(intf, "type", type) && type == "wifi") + { + return intf; + } + } + } + else + { + LOGWARN("failed to fetch interfaces via networkconfig1_GetInterfaces"); + } + return ""; +} + +const std::string &WifiManagerState::getWifiInterfaceName() +{ + static std::string name = WifiManagerState::fetchWifiInterfaceName(); + return name; +} diff --git a/WifiManager/impl_lg/WifiManagerState.h b/WifiManager/impl_lg/WifiManagerState.h new file mode 100644 index 0000000000..4bd8369a93 --- /dev/null +++ b/WifiManager/impl_lg/WifiManagerState.h @@ -0,0 +1,64 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once + +#include +#include + +#include "dbus/DBusClient.h" + +#include "../Module.h" +#include "../WifiManagerDefines.h" + +using namespace WifiManagerImpl; + +namespace WPEFramework { + namespace Plugin { + class WifiManagerState { + public: + WifiManagerState(); + virtual ~WifiManagerState(); + WifiManagerState(const WifiManagerState&) = delete; + WifiManagerState& operator=(const WifiManagerState&) = delete; + + void Initialize(); + + uint32_t getCurrentState(const JsonObject& parameters, JsonObject& response); + uint32_t getConnectedSSID(const JsonObject& parameters, JsonObject& response) /* dropping const, since this can update m_latest_ssid */; + uint32_t setEnabled(const JsonObject& parameters, JsonObject& response); + uint32_t getSupportedSecurityModes(const JsonObject& parameters, JsonObject& response); + void setWifiStateCache(bool value,WifiState state) {/* not used */} + static const std::string& getWifiInterfaceName(); + + private: + void statusChanged(const std::string& interface, InterfaceStatus status); + void updateWifiStatus(InterfaceStatus status); + bool fetchSsid(std::string& out_ssid); + bool getWifiParams(std::map ¶ms); + std::string getWifiRssi(); + + std::atomic m_wifi_state {WifiState::DISABLED}; + + static std::string fetchWifiInterfaceName(); + + std::string m_latest_ssid; + }; + } +} diff --git a/WifiManager/impl_lg/WifiManagerWPS.cpp b/WifiManager/impl_lg/WifiManagerWPS.cpp new file mode 100644 index 0000000000..60330f63a7 --- /dev/null +++ b/WifiManager/impl_lg/WifiManagerWPS.cpp @@ -0,0 +1,91 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "WifiManagerWPS.h" +#include "UtilsJsonRpc.h" + +namespace WPEFramework +{ + namespace Plugin + { + WifiManagerWPS::WifiManagerWPS() + { + m_useCachePairedSSID = false; + m_cachePairedSSID = ""; + m_cachePairedBSSID = ""; + } + + WifiManagerWPS::~WifiManagerWPS() + { + } + + void WifiManagerWPS::updateWifiWPSCache(bool value) + { + m_useCachePairedSSID = value; + } + + uint32_t WifiManagerWPS::initiateWPSPairing(const JsonObject ¶meters, JsonObject &response) + { + LOGINFOMETHOD(); + returnResponse(false); + } + + uint32_t WifiManagerWPS::initiateWPSPairing2(const JsonObject ¶meters, JsonObject &response) + { + LOGINFOMETHOD(); + returnResponse(false); + } + + uint32_t WifiManagerWPS::cancelWPSPairing(const JsonObject ¶meters, JsonObject &response) + { + LOGINFOMETHOD(); + returnResponse(false); + } + + uint32_t WifiManagerWPS::saveSSID(const JsonObject ¶meters, JsonObject &response) + { + LOGINFOMETHOD(); + returnResponse(false); + } + + uint32_t WifiManagerWPS::clearSSID(const JsonObject ¶meters, JsonObject &response) + { + LOGINFOMETHOD(); + returnResponse(false); + } + + uint32_t WifiManagerWPS::getPairedSSID(const JsonObject ¶meters, JsonObject &response) + { + LOGINFOMETHOD(); + returnResponse(false); + } + + uint32_t WifiManagerWPS::getPairedSSIDInfo(const JsonObject ¶meters, JsonObject &response) + { + LOGINFOMETHOD(); + returnResponse(false); + } + + uint32_t WifiManagerWPS::isPaired(const JsonObject ¶meters, JsonObject &response) + { + LOGINFOMETHOD(); + returnResponse(false); + } + } // namespace Plugin +} // namespace WPEFramework diff --git a/WifiManager/impl_lg/WifiManagerWPS.h b/WifiManager/impl_lg/WifiManagerWPS.h new file mode 100644 index 0000000000..28918537ee --- /dev/null +++ b/WifiManager/impl_lg/WifiManagerWPS.h @@ -0,0 +1,50 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once + +#include "../WifiManagerDefines.h" + +namespace WPEFramework { + + namespace Plugin { + // Implementation of wifi protected setup (WPS) WiFiManger API + class WifiManagerWPS { + public: + WifiManagerWPS(); + virtual ~WifiManagerWPS(); + WifiManagerWPS(const WifiManagerWPS&) = delete; + WifiManagerWPS& operator=(const WifiManagerWPS&) = delete; + + uint32_t initiateWPSPairing(const JsonObject& parameters, JsonObject& response); + uint32_t initiateWPSPairing2(const JsonObject& parameters, JsonObject& response); + uint32_t cancelWPSPairing(const JsonObject& parameters, JsonObject& response); + uint32_t saveSSID (const JsonObject& parameters, JsonObject& response); + uint32_t clearSSID(const JsonObject& parameters, JsonObject& response); + uint32_t getPairedSSID(const JsonObject& parameters, JsonObject& response); + uint32_t getPairedSSIDInfo(const JsonObject& parameters, JsonObject& response); + uint32_t isPaired(const JsonObject& parameters, JsonObject& response); + void updateWifiWPSCache(bool value); + + std::atomic m_useCachePairedSSID; + std::string m_cachePairedSSID; + std::string m_cachePairedBSSID; + }; + } // namespace Plugin +} // namespace WPEFramework diff --git a/WifiManager/impl_lg/dbus/DBusClient.cpp b/WifiManager/impl_lg/dbus/DBusClient.cpp new file mode 100644 index 0000000000..5d05048eb9 --- /dev/null +++ b/WifiManager/impl_lg/dbus/DBusClient.cpp @@ -0,0 +1,388 @@ +#include "DBusClient.h" +#include "Module.h" +#include +#include + +#define NETWORK_CONFIG_DBUS_INTERFACE_NAME "com.lgi.rdk.utils.networkconfig1" +#define NETWORK_CONFIG_DBUS_INTERFACE_OBJECT_PATH "/com/lgi/rdk/utils/networkconfig1" + +#define WIFI_MANAGEMENT_DBUS_INTERFACE_NAME "com.lgi.rdk.utils.wifimanagement1" +#define WIFI_MANAGEMENT_DBUS_INTERFACE_OBJECT_PATH "/com/lgi/rdk/utils/wifimanagement1" + +namespace WifiManagerImpl +{ + static const std::map statusFromString{ + {"Disabled", Disabled}, + {"Disconnected", Disconnected}, + {"Associating", Associating}, + {"Dormant", Dormant}, + {"Binding", Binding}, + {"Assigned", Assigned}, + {"Scanning", Scanning}}; + + DBusClient::DBusClient() + { + run(); + } + + static void handle_dbus_event(GDBusProxy *proxy, + char *sender_name, + char *_signal_name, + GVariant *parameters, + gpointer user_data) + { + std::string signal_name{_signal_name}; + + const gsize num_params = g_variant_n_children(parameters); + GVariantIter iter; + g_variant_iter_init(&iter, parameters); + + DBusClient *client = static_cast(user_data); + + if (signal_name == "StatusChanged" && num_params == 2) + { + GVariant *vId = g_variant_iter_next_value(&iter); + GVariant *vInterfaceStatus = g_variant_iter_next_value(&iter); + const gchar *aId = g_variant_get_string(vId, NULL); + const gchar *aIfaceStatus = g_variant_get_string(vInterfaceStatus, NULL); + client->handleStatusChangedDbusEvent(aId, aIfaceStatus); + g_variant_unref(vInterfaceStatus); + g_variant_unref(vId); + } + else + { + LOGINFO("handle_dbus_event: unsupported event; sender_name: %s signal_name: %s, num_params: %u", sender_name, _signal_name, num_params); + } + } + + void DBusClient::handleStatusChangedDbusEvent(const std::string &aId, const std::string &aIfaceStatus) + { + std::lock_guard lock(m_event_mutex); + if (m_statusChangedHandler) + { + auto status = statusFromString.find(aIfaceStatus); + if (status != statusFromString.end()) + { + m_statusChangedHandler(aId, status->second); + } + else + { + LOGERR("networkconfig1 StatusChanged event received with unknown status string: %s", aIfaceStatus.c_str()); + } + } + } + + void DBusClient::run() + { + if (!m_dbus_data) + { + m_loopThread = std::thread(&DBusClient::dbusWorker, this); + m_dbus_data = m_dbus_data_future.get_future().get(); + + if (m_dbus_data) + { + m_handle_networkconfig_gsignal = g_signal_connect(m_dbus_data->m_networkconfig1_interface->proxy, "g-signal", G_CALLBACK(handle_dbus_event), this); + if (!m_handle_networkconfig_gsignal) + { + LOGERR("Cannot connect to networkconfig1 g-signal"); + } + + /* seems we do not need any wifimanagement1 signals for now + m_handle_wifimanagement_gsignal = g_signal_connect(m_wifimanagement1_interface->proxy, "g-signal", G_CALLBACK(handle_dbus_event), this); + if (!m_handle_wifimanagement_gsignal) + { + LOGERR("Cannot connect to wifimanagement1 g-signal"); + } + */ + } + else + { + m_loopThread.join(); + throw std::runtime_error("failure creating dbus interfaces"); + } + } + } + + template + static void release_dbus_interface(T *interface) + { + if (interface->proxy) + { + g_object_unref(interface->proxy); + } + g_dbus_connection_flush_sync(interface->connection, NULL, NULL); + g_object_unref(interface->connection); + g_free(interface); + } + + void DBusClient::stop() + { + if (m_dbus_data) + { + if (m_handle_networkconfig_gsignal != 0) + { + g_signal_handler_disconnect(m_dbus_data->m_networkconfig1_interface->proxy, m_handle_networkconfig_gsignal); + } + + if (m_dbus_data->m_networkconfig1_interface) + release_dbus_interface(m_dbus_data->m_networkconfig1_interface); + if (m_dbus_data->m_wifimanagement1_interface) + release_dbus_interface(m_dbus_data->m_wifimanagement1_interface); + + g_main_context_invoke( + m_dbus_data->m_mainContext, +[](gpointer ptr) -> gboolean + { + LOGINFO("LgiNetworkClient::Stop() quit main loop TID: %u", gettid()); + g_main_loop_quit((GMainLoop*)ptr); + return FALSE; }, + (gpointer)m_dbus_data->m_mainLoop); + + if (m_loopThread.joinable()) + { + m_loopThread.join(); + } + else + { + LOGERR("Worker thread should be joinable"); + } + LOGINFO("signals disconnected"); + + delete m_dbus_data; + m_dbus_data = nullptr; + } + } + + bool DBusClient::networkconfig1_GetInterfaces(std::vector &out) + { + GError *error{nullptr}; + bool ret = false; + guint count = 0; + gchar **ids{nullptr}; + + if (com_lgi_rdk_utils_networkconfig1_call_get_interfaces_sync( + m_dbus_data->m_networkconfig1_interface, + &count, + &ids, + nullptr, + &error)) + { + for (guint i = 0; i < count; ++i) + { + out.push_back(ids[i]); + } + ret = true; + } + else + { + LOGERR("Failed to call networkconfig1_call_get_interfaces_sync - %s", error ? error->message : "(unknown)"); + } + if (error) + g_error_free(error); + if (ids) + g_free(ids); + return ret; + } + + bool DBusClient::networkconfig1_GetParam(const std::string &interface, const std::string ¶mName, std::string &res) + { + gint status = 0; + gchar *paramValue{nullptr}; + GError *error{nullptr}; + bool ret = false; + if ( + com_lgi_rdk_utils_networkconfig1_call_get_param_sync( + m_dbus_data->m_networkconfig1_interface, + interface.c_str(), + paramName.c_str(), + &status, + ¶mValue, + nullptr, + &error)) + { + if (status == 0) + { + res = paramValue; + ret = true; + } + else + { + LOGERR("Failed to call networkconfig1_call_get_param_sync - status: %d", status); + } + } + else + { + LOGERR("Failed to call networkconfig1_call_get_param_sync - %s", error ? error->message : "(unknown)"); + } + if (error) + g_error_free(error); + if (paramValue) + g_free(paramValue); + return ret; + } + + bool DBusClient::networkconfig1_GetStatus(const std::string &interface, InterfaceStatus &out) + { + gint status = 0; + gchar *ifaceStatus{nullptr}; + GError *error{nullptr}; + bool ret = false; + if ( + com_lgi_rdk_utils_networkconfig1_call_get_status_sync( + m_dbus_data->m_networkconfig1_interface, + interface.c_str(), + &status, + &ifaceStatus, + nullptr, + &error)) + { + if (status == 0) + { + auto it = statusFromString.find(ifaceStatus); + if (it != statusFromString.end()) + { + out = it->second; + ret = true; + } + else + { + LOGERR("networkconfig1_call_get_status_sync returned unknown status string: %s", ifaceStatus); + } + } + else + { + LOGERR("Failed to call networkconfig1_call_get_status_sync - status: %d", status); + } + } + else + { + LOGERR("Failed to call networkconfig1_call_get_status_sync - %s", error ? error->message : "(unknown)"); + } + if (error) + g_error_free(error); + if (ifaceStatus) + g_free(ifaceStatus); + return ret; + } + + bool DBusClient::wifimanagement1_GetSSIDParams(const std::string &ssid, const std::string &netid, std::map ¶ms) + { + bool ret = false; + gint status = 0; + guint count = 0; + GVariant *out_params{nullptr}; + GError *error{nullptr}; + + if (com_lgi_rdk_utils_wifimanagement1_call_get_ssidparams_sync( + m_dbus_data->m_wifimanagement1_interface, + ssid.c_str(), // const gchar *arg_id, + netid.c_str(), // const gchar *arg_netid, + &status, + &count, + &out_params, + NULL, + &error)) + { + if (status) + { + LOGERR("GetSSIDParams failed; status: %u error: '%s'", status, error ? error->message : "(unknown)"); + } + else + { + GVariantIter iter; + gchar *key; + gchar *value; + + if (out_params) + { + g_variant_iter_init(&iter, out_params); + while (g_variant_iter_loop(&iter, "{ss}", &key, &value)) + { + params[key] = value; + } + } + ret = true; + } + } + else + { + LOGERR("Failed to call wifimanagement1_call_get_ssidparams_sync - %s", error ? error->message : "(unknown)"); + } + if (error) + g_error_free(error); + if (out_params) + g_variant_unref(out_params); + return ret; + } + + void DBusClient::dbusWorker() + { + LOGINFO("LgiNetworkClient::Worker() TID: %u", gettid()); + + DbusData *dbusData = new DbusData(); + dbusData->m_mainContext = g_main_context_new(); + dbusData->m_mainLoop = g_main_loop_new(dbusData->m_mainContext, false); + + bool initialization_complete = false; + GError *error = nullptr; + + if (dbusData->m_mainLoop && dbusData->m_mainContext) + { + g_main_context_push_thread_default(dbusData->m_mainContext); + dbusData->m_networkconfig1_interface = networkconfig1_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NETWORK_CONFIG_DBUS_INTERFACE_NAME, + NETWORK_CONFIG_DBUS_INTERFACE_OBJECT_PATH, + NULL, /* GCancellable */ + &error); + if (error) + { + LOGERR("Failed to create networkconfig proxy: %s", error->message); + } + else + { + + dbusData->m_wifimanagement1_interface = com_lgi_rdk_utils_wifimanagement1_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + WIFI_MANAGEMENT_DBUS_INTERFACE_NAME, + WIFI_MANAGEMENT_DBUS_INTERFACE_OBJECT_PATH, + NULL, /* GCancellable */ + &error); + if (error) + { + LOGERR("Failed to create networkconfig proxy: %s", error->message); + } + else + { + m_dbus_data_future.set_value(dbusData); + initialization_complete = true; + LOGINFO("DBusClient::dbusWorker() start main loop TID: %u", gettid()); + g_main_loop_run(dbusData->m_mainLoop); // blocks + LOGINFO("DBusClient::dbusWorker() main loop finished TID: %u", gettid()); + g_main_context_pop_thread_default(dbusData->m_mainContext); + } + } + } + else + { + LOGERR("Failed to create glib main loop"); + m_dbus_data_future.set_value(nullptr); + } + + if (error) + { + g_error_free(error); + } + if (dbusData->m_mainLoop) + { + g_main_loop_unref(dbusData->m_mainLoop); + } + if (dbusData->m_mainContext) + { + g_main_context_unref(dbusData->m_mainContext); + } + if (!initialization_complete) + { + m_dbus_data_future.set_value(nullptr); + } + } +} diff --git a/WifiManager/impl_lg/dbus/DBusClient.h b/WifiManager/impl_lg/dbus/DBusClient.h new file mode 100644 index 0000000000..379a5927f4 --- /dev/null +++ b/WifiManager/impl_lg/dbus/DBusClient.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "networkconfig1_dbus_api.h" +#include "wifimanagement1_dbus_api.h" + +namespace WifiManagerImpl +{ + + enum InterfaceStatus + { + Disabled, + Disconnected, + Associating, + Dormant, + Binding, + Assigned, + Scanning + }; + + class DBusClient + { + public: + using StatusChangedHandler = std::function; + + static DBusClient &getInstance() + { + static DBusClient client; + return client; + } + + void run(); + void stop(); + + void registerStatusChanged(StatusChangedHandler handler) + { + std::lock_guard lock(m_event_mutex); + m_statusChangedHandler = handler; + } + + bool networkconfig1_GetInterfaces(std::vector &ret); + bool networkconfig1_GetParam(const std::string &interface, const std::string &name, std::string &res); + bool networkconfig1_GetStatus(const std::string &interface, InterfaceStatus &out); + bool wifimanagement1_GetSSIDParams(const std::string &ssid, const std::string &netid, std::map ¶ms); + + void handleStatusChangedDbusEvent(const std::string &aId, const std::string &aIfaceStatus); + + private: + DBusClient(); + ~DBusClient() = default; + + void dbusWorker(); + + std::thread m_loopThread; + + struct DbusData { + GMainContext *m_mainContext{nullptr}; + GMainLoop *m_mainLoop{nullptr}; + Networkconfig1 *m_networkconfig1_interface{nullptr}; + Wifimanagement1 *m_wifimanagement1_interface{nullptr}; + }; + + std::promise m_dbus_data_future; + DbusData* m_dbus_data{nullptr}; + + std::mutex m_event_mutex; + StatusChangedHandler m_statusChangedHandler {nullptr}; + + gulong m_handle_networkconfig_gsignal{0}; + /* seems we do not need any wifimanagement1 signals for now + gulong m_handle_wifimanagement_gsignal; + */ + }; +} \ No newline at end of file diff --git a/WifiManager/impl_lg/dbus/networkconfig1_dbus_api.c b/WifiManager/impl_lg/dbus/networkconfig1_dbus_api.c new file mode 100644 index 0000000000..e756692252 --- /dev/null +++ b/WifiManager/impl_lg/dbus/networkconfig1_dbus_api.c @@ -0,0 +1,180 @@ + +#include "networkconfig1_dbus_api.h" + +Networkconfig1 *networkconfig1_proxy_new_for_bus_sync( + GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GError **error) +{ + static const gchar *INTERFACE_NAME = "com.lgi.rdk.utils.networkconfig1"; + + GDBusConnection *connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, cancellable, error); + if (!connection) + { + printf("networkconfig1_proxy_new_for_bus_sync: failed g_bus_get_sync; error: '%s'\n", (*error) ? (*error)->message : "?"); + return NULL; + } + else + { + GDBusProxy *proxy = g_dbus_proxy_new_sync( + connection, + flags, + NULL, // A GDBusInterfaceInfo specifying the minimal interface that proxy conforms to or NULL + name, + object_path, + INTERFACE_NAME, + NULL, // cancellable + error); + if (!proxy) + { + printf("networkconfig1_proxy_new_for_bus_sync: failed g_dbus_proxy_new_sync; error: '%s'\n", (*error) ? (*error)->message : "?"); + g_object_unref(connection); + return NULL; + } + else + { + Networkconfig1 *ret = g_malloc(sizeof(Networkconfig1)); + ret->connection = connection; + ret->proxy = proxy; + return ret; + } + } +} + +/* copied from gdbus-codegen generated version; the only change is in first arg to g_dbus_proxy_call_sync */ +/** + * com_lgi_rdk_utils_networkconfig1_call_get_status_sync: + * @proxy: A #ComLgiRdkUtilsNetworkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @out_ifaceStatus: (out): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the GetStatus() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See com_lgi_rdk_utils_networkconfig1_call_get_status() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +com_lgi_rdk_utils_networkconfig1_call_get_status_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gint *out_status, + gchar **out_ifaceStatus, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (proxy->proxy, + "GetStatus", + g_variant_new ("(s)", + arg_id), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(is)", + out_status, + out_ifaceStatus); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/* copied from gdbus-codegen generated version; the only change is in first arg to g_dbus_proxy_call_sync */ +/** + * com_lgi_rdk_utils_networkconfig1_call_get_param_sync: + * @proxy: A #ComLgiRdkUtilsNetworkconfig1Proxy. + * @arg_id: Argument to pass with the method invocation. + * @arg_paramName: Argument to pass with the method invocation. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @out_paramValue: (out): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the GetParam() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See com_lgi_rdk_utils_networkconfig1_call_get_param() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +com_lgi_rdk_utils_networkconfig1_call_get_param_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + const gchar *arg_paramName, + gint *out_status, + gchar **out_paramValue, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (proxy->proxy, + "GetParam", + g_variant_new ("(ss)", + arg_id, + arg_paramName), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(is)", + out_status, + out_paramValue); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} + +/* copied from gdbus-codegen generated version; the only change is in first arg to g_dbus_proxy_call_sync */ +/** + * com_lgi_rdk_utils_networkconfig1_call_get_interfaces_sync: + * @proxy: A #ComLgiRdkUtilsNetworkconfig1Proxy. + * @out_count: (out): Return location for return parameter or %NULL to ignore. + * @out_ids: (out) (array zero-terminated=1): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the GetInterfaces() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See com_lgi_rdk_utils_networkconfig1_call_get_interfaces() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +com_lgi_rdk_utils_networkconfig1_call_get_interfaces_sync ( + Networkconfig1 *proxy, + guint *out_count, + gchar ***out_ids, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (proxy->proxy, + "GetInterfaces", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(u^as)", + out_count, + out_ids); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} diff --git a/WifiManager/impl_lg/dbus/networkconfig1_dbus_api.h b/WifiManager/impl_lg/dbus/networkconfig1_dbus_api.h new file mode 100644 index 0000000000..4e1fbb0fb5 --- /dev/null +++ b/WifiManager/impl_lg/dbus/networkconfig1_dbus_api.h @@ -0,0 +1,55 @@ +#ifndef __NETWORKCONFIG1_DBUS_API_H__ +#define __NETWORKCONFIG1_DBUS_API_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + GDBusConnection* connection; + GDBusProxy* proxy; +} Networkconfig1; + +Networkconfig1 *networkconfig1_proxy_new_for_bus_sync ( + GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GError **error); + +gboolean +com_lgi_rdk_utils_networkconfig1_call_get_status_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + gint *out_status, + gchar **out_ifaceStatus, + GCancellable *cancellable, + GError **error); + +gboolean +com_lgi_rdk_utils_networkconfig1_call_get_param_sync ( + Networkconfig1 *proxy, + const gchar *arg_id, + const gchar *arg_paramName, + gint *out_status, + gchar **out_paramValue, + GCancellable *cancellable, + GError **error); + +gboolean +com_lgi_rdk_utils_networkconfig1_call_get_interfaces_sync ( + Networkconfig1 *proxy, + guint *out_count, + gchar ***out_ids, + GCancellable *cancellable, + GError **error); + +#ifdef __cplusplus // extern "C" +} +#endif + +#endif diff --git a/WifiManager/impl_lg/dbus/wifimanagement1_dbus_api.c b/WifiManager/impl_lg/dbus/wifimanagement1_dbus_api.c new file mode 100644 index 0000000000..f494bc9e46 --- /dev/null +++ b/WifiManager/impl_lg/dbus/wifimanagement1_dbus_api.c @@ -0,0 +1,96 @@ +#include "wifimanagement1_dbus_api.h" + +Wifimanagement1 * +com_lgi_rdk_utils_wifimanagement1_proxy_new_for_bus_sync ( + GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GError **error) +{ + static const gchar *INTERFACE_NAME = "com.lgi.rdk.utils.wifimanagement1"; + + GDBusConnection *connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, cancellable, error); + if (!connection) + { + printf("com_lgi_rdk_utils_wifimanagement1_proxy_new_for_bus_sync: failed g_bus_get_sync; error: '%s'\n", (*error) ? (*error)->message : "?"); + return NULL; + } + else + { + GDBusProxy *proxy = g_dbus_proxy_new_sync( + connection, + flags, + NULL, // A GDBusInterfaceInfo specifying the minimal interface that proxy conforms to or NULL + name, + object_path, + INTERFACE_NAME, + NULL, // cancellable + error); + if (!proxy) + { + printf("com_lgi_rdk_utils_wifimanagement1_proxy_new_for_bus_sync: failed g_dbus_proxy_new_sync; error: '%s'\n", (*error) ? (*error)->message : "?"); + g_object_unref(connection); + return NULL; + } + else + { + Wifimanagement1 *ret = g_malloc(sizeof(Wifimanagement1)); + ret->connection = connection; + ret->proxy = proxy; + return ret; + } + } +} + +/* copied from gdbus-codegen generated version; the only change is in first arg to g_dbus_proxy_call_sync */ +/** + * com_lgi_rdk_utils_wifimanagement1_call_get_ssidparams_sync: + * @proxy: Wifimanagement1 proxy. + * @arg_id: Argument to pass with the method invocation. + * @arg_netid: Argument to pass with the method invocation. + * @out_status: (out): Return location for return parameter or %NULL to ignore. + * @out_count: (out): Return location for return parameter or %NULL to ignore. + * @out_params: (out): Return location for return parameter or %NULL to ignore. + * @cancellable: (nullable): A #GCancellable or %NULL. + * @error: Return location for error or %NULL. + * + * Synchronously invokes the GetSSIDParams() D-Bus method on @proxy. The calling thread is blocked until a reply is received. + * + * See com_lgi_rdk_utils_wifimanagement1_call_get_ssidparams() for the asynchronous version of this method. + * + * Returns: (skip): %TRUE if the call succeded, %FALSE if @error is set. + */ +gboolean +com_lgi_rdk_utils_wifimanagement1_call_get_ssidparams_sync ( + Wifimanagement1 *proxy, + const gchar *arg_id, + const gchar *arg_netid, + gint *out_status, + guint *out_count, + GVariant **out_params, + GCancellable *cancellable, + GError **error) +{ + GVariant *_ret; + _ret = g_dbus_proxy_call_sync (proxy->proxy, + "GetSSIDParams", + g_variant_new ("(ss)", + arg_id, + arg_netid), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + if (_ret == NULL) + goto _out; + g_variant_get (_ret, + "(iu@a{ss})", + out_status, + out_count, + out_params); + g_variant_unref (_ret); +_out: + return _ret != NULL; +} diff --git a/WifiManager/impl_lg/dbus/wifimanagement1_dbus_api.h b/WifiManager/impl_lg/dbus/wifimanagement1_dbus_api.h new file mode 100644 index 0000000000..6d9452c3ec --- /dev/null +++ b/WifiManager/impl_lg/dbus/wifimanagement1_dbus_api.h @@ -0,0 +1,40 @@ +#ifndef __WIFIMANAGEMENT1_DBUS_API_H__ +#define __WIFIMANAGEMENT1_DBUS_API_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + GDBusConnection* connection; + GDBusProxy* proxy; +} Wifimanagement1; + +Wifimanagement1 * +com_lgi_rdk_utils_wifimanagement1_proxy_new_for_bus_sync ( + GBusType bus_type, + GDBusProxyFlags flags, + const gchar *name, + const gchar *object_path, + GCancellable *cancellable, + GError **error); + +gboolean +com_lgi_rdk_utils_wifimanagement1_call_get_ssidparams_sync ( + Wifimanagement1 *proxy, + const gchar *arg_id, + const gchar *arg_netid, + gint *out_status, + guint *out_count, + GVariant **out_params, + GCancellable *cancellable, + GError **error); + +#ifdef __cplusplus // extern "C" +} +#endif + +#endif diff --git a/WifiManager/impl_lg/dbusgen/gencode.sh b/WifiManager/impl_lg/dbusgen/gencode.sh new file mode 100755 index 0000000000..b52f271b83 --- /dev/null +++ b/WifiManager/impl_lg/dbusgen/gencode.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# WE ARE NOT DIRECTLY USING THESE GENERATED FILES! +# dbus-codegen generated code creates static objects & this doesn't play well with wpeframework & dynamically +# loaded & unloaded modules (generally, not possible to disable and reenable the plugin then) +# these files are still useful - part of the implementation can be reused, with slight changes +gdbus-codegen --generate-c-code networkconfig1.autogen ../../../../../../../../../../onemw-src/onemw-src/networking/om-netconfig/api/dbus/com.lgi.rdk.utils.networkconfig1.xml +gdbus-codegen --generate-c-code wifimanagement1.autogen ../../../../../../../../../../onemw-src/onemw-src/networking/om-netconfig/api/dbus/com.lgi.rdk.utils.wifimanagement1.xml + + + diff --git a/XCast/CMakeLists.txt b/XCast/CMakeLists.txt index 43418eec25..c1430f871a 100644 --- a/XCast/CMakeLists.txt +++ b/XCast/CMakeLists.txt @@ -42,10 +42,11 @@ target_include_directories(${MODULE_NAME} PRIVATE ${RFC_INCLUDE_DIRS} ../helpers target_include_directories(${MODULE_NAME} PRIVATE $ENV{PKG_CONFIG_SYSROOT_DIR}/usr/include/pxcore) if(NOT RDK_SERVICES_TEST) - target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins rtRemote rtCore ${RFC_LIBRARIES} ${IARMBUS_LIBRARIES}) + target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins rtRemote rtCore cjson ${RFC_LIBRARIES} ${IARMBUS_LIBRARIES}) else(RDK_SERVICES_TEST) - target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${RFC_LIBRARIES} ${IARMBUS_LIBRARIES}) + target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins cjson ${RFC_LIBRARIES} ${IARMBUS_LIBRARIES}) endif() +# target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins rtRemote rtCore cjson rfcapi ${IARMBUS_LIBRARIES}) install(TARGETS ${MODULE_NAME} DESTINATION lib/${STORAGE_DIRECTORY}/plugins) diff --git a/XCast/LockedRtNotifier.hpp b/XCast/LockedRtNotifier.hpp new file mode 100644 index 0000000000..496503433a --- /dev/null +++ b/XCast/LockedRtNotifier.hpp @@ -0,0 +1,45 @@ +#pragma once + +/* +This wrapper can be used to synchronize rtremote callbacks in XCast +*/ + +#include "RtNotifier.h" +#include +#include "UtilsSynchro.hpp" + +class LockedRtNotifier : public RtNotifier { + RtNotifier *wrapped; + std::recursive_mutex& mtx; +public: + LockedRtNotifier(RtNotifier *wrapped, std::recursive_mutex& mtx) : wrapped(wrapped), mtx(mtx) { + } + void onRtServiceDisconnected() { + std::unique_lock lock(mtx); + wrapped->onRtServiceDisconnected(); + }; + void onXcastApplicationLaunchRequest(string appName, string parameter) { + std::unique_lock lock(mtx); + wrapped->onXcastApplicationLaunchRequest(appName, parameter); + }; + void onXcastApplicationLaunchRequestWithLaunchParam (string appName, string strPayLoad, string strQuery, string strAddDataUrl) { + std::unique_lock lock(mtx); + wrapped->onXcastApplicationLaunchRequestWithLaunchParam(appName, strPayLoad, strQuery, strAddDataUrl); + }; + void onXcastApplicationStopRequest(string appName, string appID) { + std::unique_lock lock(mtx); + wrapped->onXcastApplicationStopRequest(appName, appID); + }; + void onXcastApplicationHideRequest(string appName, string appID) { + std::unique_lock lock(mtx); + wrapped->onXcastApplicationHideRequest(appName, appID); + }; + void onXcastApplicationResumeRequest(string appName, string appID) { + std::unique_lock lock(mtx); + wrapped->onXcastApplicationResumeRequest(appName, appID); + }; + void onXcastApplicationStateRequest(string appName, string appID) { + std::unique_lock lock(mtx); + wrapped->onXcastApplicationStateRequest(appName, appID); + }; +}; \ No newline at end of file diff --git a/XCast/RtXcastConnector.h b/XCast/RtXcastConnector.h index 73606ce742..fa7b087964 100644 --- a/XCast/RtXcastConnector.h +++ b/XCast/RtXcastConnector.h @@ -35,7 +35,7 @@ using namespace std; */ class RtXcastConnector { protected: - RtXcastConnector():m_runEventThread(true){ + RtXcastConnector():m_observer(nullptr), m_runEventThread(true) { } public: virtual ~RtXcastConnector(); @@ -91,7 +91,6 @@ class RtXcastConnector { mutex m_threadlock; // Boolean event thread exit condition bool m_runEventThread; - bool m_IsDefaultDynamicAppListEnabled; // Member function to handle RT messages. void processRtMessages(); bool IsAppEnabled(char* strAppName); diff --git a/XCast/XCast.cpp b/XCast/XCast.cpp index 7e70861c03..f12f3ac3bb 100644 --- a/XCast/XCast.cpp +++ b/XCast/XCast.cpp @@ -27,6 +27,8 @@ #include #include #include "RtXcastConnector.h" +#include "UtilsSynchroIarm.hpp" + using namespace std; // Events @@ -91,7 +93,6 @@ namespace Plugin { SERVICE_REGISTRATION(XCast, API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH); static RtXcastConnector * _rtConnector = RtXcastConnector::getInstance(); -static int locateCastObjectRetryCount = 0; bool XCast::isCastEnabled = false; #ifdef XCAST_ENABLED_BY_DEFAULT bool XCast::m_xcastEnable = true; @@ -115,17 +116,17 @@ XCast::XCast() : PluginHost::JSONRPC() if(XCast::isCastEnabled) { LOGINFO("XcastService::Register methods and create onLocateCastTimer "); - Register(METHOD_GET_API_VERSION_NUMBER, &XCast::getApiVersionNumber, this); - Register(METHOD_ON_APPLICATION_STATE_CHANGED , &XCast::applicationStateChanged, this); - Register(METHOD_SET_ENABLED, &XCast::setEnabled, this); - Register(METHOD_GET_ENABLED, &XCast::getEnabled, this); - Register(METHOD_GET_STANDBY_BEHAVIOR, &XCast::getStandbyBehavior, this); - Register(METHOD_SET_STANDBY_BEHAVIOR, &XCast::setStandbyBehavior, this); - Register(METHOD_GET_FRIENDLYNAME, &XCast::getFriendlyName, this); - Register(METHOD_SET_FRIENDLYNAME, &XCast::setFriendlyName, this); - Register(METHOD_REG_APPLICATIONS, &XCast::registerApplications, this); - Register(METHOD_UNREG_APPLICATIONS, &XCast::unregisterApplications, this); - Register(METHOD_GET_PROTOCOLVERSION, &XCast::getProtocolVersion, this); + Utils::Synchro::RegisterLockedApi(METHOD_GET_API_VERSION_NUMBER, &XCast::getApiVersionNumber, this); + Utils::Synchro::RegisterLockedApi(METHOD_ON_APPLICATION_STATE_CHANGED , &XCast::applicationStateChanged, this); + Utils::Synchro::RegisterLockedApi(METHOD_SET_ENABLED, &XCast::setEnabled, this); + Utils::Synchro::RegisterLockedApi(METHOD_GET_ENABLED, &XCast::getEnabled, this); + Utils::Synchro::RegisterLockedApi(METHOD_GET_STANDBY_BEHAVIOR, &XCast::getStandbyBehavior, this); + Utils::Synchro::RegisterLockedApi(METHOD_SET_STANDBY_BEHAVIOR, &XCast::setStandbyBehavior, this); + Utils::Synchro::RegisterLockedApi(METHOD_GET_FRIENDLYNAME, &XCast::getFriendlyName, this); + Utils::Synchro::RegisterLockedApi(METHOD_SET_FRIENDLYNAME, &XCast::setFriendlyName, this); + Utils::Synchro::RegisterLockedApi(METHOD_REG_APPLICATIONS, &XCast::registerApplications, this); + Utils::Synchro::RegisterLockedApi(METHOD_UNREG_APPLICATIONS, &XCast::unregisterApplications, this); + Utils::Synchro::RegisterLockedApi(METHOD_GET_PROTOCOLVERSION, &XCast::getProtocolVersion, this); m_locateCastTimer.connect( bind( &XCast::onLocateCastTimer, this )); } @@ -146,7 +147,7 @@ const void XCast::InitializeIARM() if (Utils::IARM::init()) { IARM_Result_t res; - IARM_CHECK( IARM_Bus_RegisterEventHandler(IARM_BUS_PWRMGR_NAME,IARM_BUS_PWRMGR_EVENT_MODECHANGED, powerModeChange) ); + IARM_CHECK( Utils::Synchro::RegisterLockedIarmEventHandler(IARM_BUS_PWRMGR_NAME,IARM_BUS_PWRMGR_EVENT_MODECHANGED, powerModeChange) ); IARM_Bus_PWRMgr_GetPowerState_Param_t param; res = IARM_Bus_Call(IARM_BUS_PWRMGR_NAME, IARM_BUS_PWRMGR_API_GetPowerState, (void *)¶m, sizeof(param)); @@ -162,7 +163,7 @@ void XCast::DeinitializeIARM() if (Utils::IARM::isConnected()) { IARM_Result_t res; - IARM_CHECK( IARM_Bus_RemoveEventHandler(IARM_BUS_PWRMGR_NAME,IARM_BUS_PWRMGR_EVENT_MODECHANGED, powerModeChange) ); + IARM_CHECK( Utils::Synchro::RemoveLockedEventHandler(IARM_BUS_PWRMGR_NAME,IARM_BUS_PWRMGR_EVENT_MODECHANGED, powerModeChange) ); } Unregister(METHOD_GET_API_VERSION_NUMBER); Unregister(METHOD_ON_APPLICATION_STATE_CHANGED); @@ -182,9 +183,18 @@ void XCast::powerModeChange(const char *owner, IARM_EventId_t eventId, void *dat LOGINFO("Event IARM_BUS_PWRMGR_EVENT_MODECHANGED: State Changed %d -- > %d\r", param->data.state.curState, param->data.state.newState); m_powerState = param->data.state.newState; + LOGWARN("creating worker thread for threadPowerModeChangeEvent m_powerState :%d",m_powerState); std::thread powerModeChangeThread = std::thread(threadPowerModeChangeEvent); powerModeChangeThread.detach(); + + if(m_standbyBehavior == false) + { + if(m_xcastEnable && ( m_powerState == IARM_BUS_PWRMGR_POWERSTATE_ON)) + _rtConnector->enableCastService(m_friendlyName,true); + else + _rtConnector->enableCastService(m_friendlyName,false); + } } } } @@ -291,7 +301,7 @@ uint32_t XCast::setEnabled(const JsonObject& parameters, JsonObject& response) returnResponse(false); } m_xcastEnable= enabled; - if (m_xcastEnable && ( (m_standbyBehavior == true) || ((m_standbyBehavior == false)&&(m_powerState == IARM_BUS_PWRMGR_POWERSTATE_ON)) ) ) + if (m_xcastEnable && ( m_standbyBehavior || m_powerState == IARM_BUS_PWRMGR_POWERSTATE_ON )) _rtConnector->enableCastService(m_friendlyName,true); else _rtConnector->enableCastService(m_friendlyName,false); @@ -346,7 +356,7 @@ uint32_t XCast::setFriendlyName(const JsonObject& parameters, JsonObject& respon { m_friendlyName = paramStr; LOGINFO("XcastService::setFriendlyName :%s",m_friendlyName.c_str()); - if (m_xcastEnable && ( (m_standbyBehavior == true) || ((m_standbyBehavior == false)&&(m_powerState == IARM_BUS_PWRMGR_POWERSTATE_ON)) ) ) { + if (m_xcastEnable && ( m_standbyBehavior || m_powerState == IARM_BUS_PWRMGR_POWERSTATE_ON )) { _rtConnector->enableCastService(m_friendlyName,true); } else { @@ -459,14 +469,8 @@ bool XCast::getEntryFromAppLaunchParamList (const char* appName, DynamicAppConfi if (0 == strcmp (regAppLaunchParam->appName, appName)) { isEntryFound = true; strcpy (retAppConfig.appName, regAppLaunchParam->appName); - - if (regAppLaunchParam->query) { - strcpy (retAppConfig.query, regAppLaunchParam->query); - } - - if (regAppLaunchParam->payload) { - strcpy (retAppConfig.payload, regAppLaunchParam->payload); - } + strcpy (retAppConfig.query, regAppLaunchParam->query); + strcpy (retAppConfig.payload, regAppLaunchParam->payload); break; } } @@ -516,7 +520,7 @@ bool XCast::deleteFromDynamicAppCache(vector& appsToDelete) { //Delete the old unwanted item here. DynamicAppConfig* pDynamicAppConfigOld = m_appConfigCache[indexToDelete]; m_appConfigCache.erase (m_appConfigCache.begin()+indexToDelete); - free (pDynamicAppConfigOld); pDynamicAppConfigOld = NULL; + delete pDynamicAppConfigOld; pDynamicAppConfigOld = NULL; } entriesTodelete.clear(); @@ -592,14 +596,7 @@ void XCast::updateDynamicAppCache(JsonArray applications) for (int i = 0; i < jNames.Length(); i++) { itrName = jNames[i].String().c_str(); LOGINFO("%s, size:%d", itrName.c_str(), (int)strlen (itrName.c_str())); - DynamicAppConfig* pDynamicAppConfig = (DynamicAppConfig*) malloc (sizeof(DynamicAppConfig)); - memset ((void*)pDynamicAppConfig, '0', sizeof(DynamicAppConfig)); - memset (pDynamicAppConfig->appName, '\0', sizeof(pDynamicAppConfig->appName)); - strcpy (pDynamicAppConfig->appName, itrName.c_str()); - memset (pDynamicAppConfig->prefixes, '\0', sizeof(pDynamicAppConfig->prefixes)); - memset (pDynamicAppConfig->cors, '\0', sizeof(pDynamicAppConfig->cors)); - memset (pDynamicAppConfig->query, '\0', sizeof(pDynamicAppConfig->query)); - memset (pDynamicAppConfig->payload, '\0', sizeof(pDynamicAppConfig->payload)); + DynamicAppConfig *pDynamicAppConfig = new DynamicAppConfig(itrName.c_str()); appConfigListTemp.push_back (pDynamicAppConfig); } } @@ -734,7 +731,7 @@ uint32_t XCast::registerApplications(const JsonObject& parameters, JsonObject& r _rtConnector->registerApplications (m_appConfigCache); /*Reenabling cast service after registering Applications*/ - if (m_xcastEnable && ( (m_standbyBehavior == true) || ((m_standbyBehavior == false)&&(m_powerState == IARM_BUS_PWRMGR_POWERSTATE_ON)) ) ) { + if (m_xcastEnable && ( m_standbyBehavior || m_powerState == IARM_BUS_PWRMGR_POWERSTATE_ON )) { LOGINFO("Enable CastService m_xcastEnable: %d m_standbyBehavior: %d m_powerState:%d", m_xcastEnable, m_standbyBehavior, m_powerState); _rtConnector->enableCastService(m_friendlyName,true); } @@ -780,7 +777,7 @@ uint32_t XCast::unregisterApplications(const JsonObject& parameters, JsonObject& _rtConnector->registerApplications (appConfigList); /*Reenabling cast service after registering Applications*/ - if (m_xcastEnable && ( (m_standbyBehavior == true) || ((m_standbyBehavior == false)&&(m_powerState == IARM_BUS_PWRMGR_POWERSTATE_ON)) ) ) { + if (m_xcastEnable && ( m_standbyBehavior || m_powerState == IARM_BUS_PWRMGR_POWERSTATE_ON)) { LOGINFO("Enable CastService m_xcastEnable: %d m_standbyBehavior: %d m_powerState:%d", m_xcastEnable, m_standbyBehavior, m_powerState); _rtConnector->enableCastService(m_friendlyName,true); } @@ -804,57 +801,34 @@ uint32_t XCast::unregisterApplications(const JsonObject& parameters, JsonObject& //Timer Functions void XCast::onLocateCastTimer() { + if (_rtConnector == nullptr) { + LOGINFO("XCast::onLocateCastTimer :_rtConnector is NULL"); + return; + } int status = _rtConnector->connectToRemoteService(); if(status != 0) { - if(locateCastObjectRetryCount < 4) - { - locateCastObjectRetryCount++; - } - if(locateCastObjectRetryCount == 1) - { - LOGINFO("Retry after 5 sec..."); - m_locateCastTimer.setInterval(LOCATE_CAST_FIRST_TIMEOUT_IN_MILLIS); - } - if(locateCastObjectRetryCount == 2) - { - LOGINFO("Retry after 15 sec..."); - m_locateCastTimer.setInterval(LOCATE_CAST_SECOND_TIMEOUT_IN_MILLIS); - } - if(locateCastObjectRetryCount == 3) - { - LOGINFO("Retry after 30 sec..."); - m_locateCastTimer.setInterval(LOCATE_CAST_THIRD_TIMEOUT_IN_MILLIS); - } - if(locateCastObjectRetryCount == 4) - { - LOGINFO("Retry after 60 sec..."); - m_locateCastTimer.setInterval(LOCATE_CAST_FINAL_TIMEOUT_IN_MILLIS); - } + LOGINFO("Retry after 5 sec..."); + m_locateCastTimer.setInterval(LOCATE_CAST_FIRST_TIMEOUT_IN_MILLIS); return ; }// err != RT_OK - locateCastObjectRetryCount = 0; m_locateCastTimer.stop(); - if (NULL != _rtConnector) { - if (_rtConnector->IsDynamicAppListEnabled() && m_isDynamicRegistrationsRequired) { + if (_rtConnector->IsDynamicAppListEnabled() && m_isDynamicRegistrationsRequired) { - std::vector appConfigList; - {lock_guard lck(m_appConfigMutex); - appConfigList = m_appConfigCache; - } - dumpDynamicAppConfigCache(string("m_appConfigCache"), appConfigList); - LOGINFO("XCast::onLocateCastTimer : calling registerApplications"); - _rtConnector->registerApplications (appConfigList); - } - else { - LOGINFO("XCast::onLocateCastTimer : DynamicAppList not enabled"); + std::vector appConfigList; + {lock_guard lck(m_appConfigMutex); + appConfigList = m_appConfigCache; } + dumpDynamicAppConfigCache(string("m_appConfigCache"), appConfigList); + LOGINFO("XCast::onLocateCastTimer : calling registerApplications"); + _rtConnector->registerApplications (appConfigList); } else { - LOGINFO("XCast::onLocateCastTimer :_rtConnector: %p", _rtConnector); + LOGINFO("XCast::onLocateCastTimer : DynamicAppList not enabled"); } - if (m_xcastEnable && ( (m_standbyBehavior == true) || ((m_standbyBehavior == false)&&(m_powerState == IARM_BUS_PWRMGR_POWERSTATE_ON)) ) ) { + + if (m_xcastEnable && ( m_standbyBehavior || m_powerState == IARM_BUS_PWRMGR_POWERSTATE_ON )) { _rtConnector->enableCastService(m_friendlyName,true); } else { @@ -905,19 +879,20 @@ void XCast::getUrlFromAppLaunchParams (const char *app_name, const char *payload } else if(strcmp(app_name,"Netflix") == 0) { memset( url, 0, url_len ); - strcat( url, "source_type=12" ); - if(payload != NULL) + bool first = true; + if(payload != NULL && strlen(payload)) { - const char * pUrlEncodedParams; - pUrlEncodedParams = payload; - if( pUrlEncodedParams ){ - strcat( url, "&dial="); - strcat( url, pUrlEncodedParams ); - } + first = false; + strcat( url, "dial="); + strcat( url, payload ); } - if(additional_data_url != NULL){ - strcat(url, "&additionalDataUrl="); + if(additional_data_url != NULL && strlen(additional_data_url)){ + if (!first) { + strcat(url, "&"); + } + first = false; + strcat(url, "additionalDataUrl="); strcat(url, additional_data_url); } } @@ -962,12 +937,20 @@ void XCast::onXcastApplicationLaunchRequestWithLaunchParam (string appName, { //TODO LOGINFO ("XcastService::onXcastApplicationLaunchRequestWithLaunchParam "); + if(strAddDataUrl.size() > DIAL_MAX_ADDITIONALURL){ + LOGWARN ("%s - current additional data size (%d) exceeds maximum allowed size (%d) ", __PRETTY_FUNCTION__, strAddDataUrl.size(), DIAL_MAX_ADDITIONALURL); + return; + } + if(strPayLoad.size() > DIAL_MAX_PAYLOAD) { + LOGWARN ("%s - current payload size (%d) exceeds maximum allowed size (%d) ", __PRETTY_FUNCTION__, strPayLoad.size(), DIAL_MAX_PAYLOAD); + return; + } JsonObject params; JsonObject urlParam; char url[DIAL_MAX_PAYLOAD+DIAL_MAX_ADDITIONALURL+100] = {0,}; if(_rtConnector) { - DynamicAppConfig appConfig{}; + DynamicAppConfig appConfig {}; getEntryFromAppLaunchParamList (appName.c_str(), appConfig); /*Replacing with App requested payload and query*/ diff --git a/XCast/XCastCommon.h b/XCast/XCastCommon.h index 32b5dc994d..f7095b957f 100644 --- a/XCast/XCastCommon.h +++ b/XCast/XCastCommon.h @@ -24,8 +24,9 @@ #define __XCAST_COMMON_H__ /* * The maximum DIAL payload accepted per the DIAL 1.6.1 specification. + * payload from xdialserver is URI encoded */ -#define DIAL_MAX_PAYLOAD (4096) +#define DIAL_MAX_PAYLOAD (4096*3) typedef struct _DynamicAppConfig { @@ -37,8 +38,9 @@ typedef struct _DynamicAppConfig { char payload[DIAL_MAX_PAYLOAD+1]; public: - _DynamicAppConfig(){ + _DynamicAppConfig(const char *applicationName = nullptr){ memset (appName, '\0', sizeof(appName)); + if (applicationName) strncpy(appName, applicationName, sizeof(appName)-1); memset (prefixes, '\0', sizeof(prefixes)); memset (cors, '\0', sizeof(cors)); memset (query, '\0', sizeof(query)); diff --git a/cmake/FindDSInit.cmake b/cmake/FindDSInit.cmake new file mode 100644 index 0000000000..5700dcacba --- /dev/null +++ b/cmake/FindDSInit.cmake @@ -0,0 +1,35 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FIND_PACKAGE(PkgConfig) +PKG_CHECK_MODULES(PC_DSINIT QUIET dsinitmanager) + +FIND_LIBRARY(DSINIT_LIBRARIES + NAMES dsinitmanager +) + +message(${DSINIT_LIBRARIES}) + +FIND_PATH(DSINIT_INCLUDE_DIR + NAMES dsinitmanager/DSManagerPlugin.h +) +message(${DSINIT_INCLUDE_DIR}) + +SET(DSINIT_INCLUDE_DIRS ${DSINIT_INCLUDE_DIR}) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(DSINIT REQUIRED_VARS DSINIT_INCLUDE_DIRS DSINIT_LIBRARIES) diff --git a/cmake/FindLibGio.cmake b/cmake/FindLibGio.cmake new file mode 100644 index 0000000000..8ef7bd4ae3 --- /dev/null +++ b/cmake/FindLibGio.cmake @@ -0,0 +1,67 @@ +#============================================================================ +# Copyright (c) 2017 Liberty Global +#============================================================================ + +# - Try to find GIO +# +# Once done this will define +# LIBGIO_FOUND - System has the component +# LIBGIO_INCLUDE_DIRS - Component include directories +# LIBGIO_LIBRARIES - Libraries needed to use the component + +# Use the pkgconfig +find_package(PkgConfig REQUIRED) + +# Find the component information +pkg_check_modules(PC_LIBGIO QUIET gio-2.0) + +# _FOUND - set to 1 if module(s) exist +# _LIBRARIES - only the libraries (w/o the '-l') +# _LIBRARY_DIRS - the paths of the libraries (w/o the '-L') +# _LDFLAGS - all required linker flags +# _LDFLAGS_OTHER - all other linker flags +# _INCLUDE_DIRS - the '-I' preprocessor flags (w/o the '-I') +# _CFLAGS - all required cflags +# _CFLAGS_OTHER - the other compiler flags + +# _VERSION - version of the module +# _PREFIX - prefix-directory of the module +# _INCLUDEDIR - include-dir of the module +# _LIBDIR - lib-dir of the module + +message(STATUS "PC_LIBGIO_FOUND = ${PC_LIBGIO_FOUND}") +message(STATUS "PC_LIBGIO_LIBRARIES = ${PC_LIBGIO_LIBRARIES}") +message(STATUS "PC_LIBGIO_LIBRARY_DIRS = ${PC_LIBGIO_LIBRARY_DIRS}") +message(STATUS "PC_LIBGIO_LDFLAGS = ${PC_LIBGIO_LDFLAGS}") +message(STATUS "PC_LIBGIO_LDFLAGS_OTHER = ${PC_LIBGIO_LDFLAGS_OTHER}") +message(STATUS "PC_LIBGIO_INCLUDE_DIRS = ${PC_LIBGIO_INCLUDE_DIRS}") +message(STATUS "PC_LIBGIO_CFLAGS = ${PC_LIBGIO_CFLAGS}") +message(STATUS "PC_LIBGIO_CFLAGS_OTHER = ${PC_LIBGIO_CFLAGS_OTHER}") +message(STATUS "PC_LIBGIO_VERSION = ${PC_LIBGIO_VERSION}") +message(STATUS "PC_LIBGIO_PREFIX = ${PC_LIBGIO_PREFIX}") +message(STATUS "PC_LIBGIO_INCLUDEDIR = ${PC_LIBGIO_INCLUDEDIR}") +message(STATUS "PC_LIBGIO_LIBDIR = ${PC_LIBGIO_LIBDIR}") + +find_path(LIBGIO_INCLUDE_DIR + NAMES gio/gio.h + HINTS ${PC_LIBGIO_INCLUDEDIR} ${PC_LIBGIO_INCLUDE_DIRS} + PATH_SUFFIXES glib-2.0 ) + +find_library(LIBGIO_LIBRARY + NAMES gio-2.0 + HINTS ${PC_LIBGIO_LIBDIR} ${PC_LIBGIO_LIBRARY_DIRS} ) + +message(STATUS "LIBGIO_INCLUDE_DIR = ${LIBGIO_INCLUDE_DIR}") +message(STATUS "LIBGIO_LIBRARY = ${LIBGIO_LIBRARY}") + +# handle the QUIETLY and REQUIRED arguments and set component to TRUE +# if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LibGio DEFAULT_MSG + LIBGIO_LIBRARY LIBGIO_INCLUDE_DIR) + +mark_as_advanced(LIBGIO_INCLUDE_DIR LIBGIO_LIBRARY) + +set(LIBGIO_INCLUDE_DIRS ${LIBGIO_INCLUDE_DIR}) +set(LIBGIO_LIBRARIES ${LIBGIO_LIBRARY}) + diff --git a/cmake/FindLibGlib.cmake b/cmake/FindLibGlib.cmake new file mode 100644 index 0000000000..839f2a134d --- /dev/null +++ b/cmake/FindLibGlib.cmake @@ -0,0 +1,72 @@ +#============================================================================ +# Copyright (c) 2017 Liberty Global +#============================================================================ + +# - Try to find Glib +# +# Once done this will define +# LIBGLIB_FOUND - System has the component +# LIBGLIB_INCLUDE_DIRS - Component include directories +# LIBGLIB_LIBRARIES - Libraries needed to use the component + +# Use the pkgconfig +find_package(PkgConfig REQUIRED) + +# Find the component information +pkg_check_modules(PC_LIBGLIB QUIET glib-2.0) + +# _FOUND - set to 1 if module(s) exist +# _LIBRARIES - only the libraries (w/o the '-l') +# _LIBRARY_DIRS - the paths of the libraries (w/o the '-L') +# _LDFLAGS - all required linker flags +# _LDFLAGS_OTHER - all other linker flags +# _INCLUDE_DIRS - the '-I' preprocessor flags (w/o the '-I') +# _CFLAGS - all required cflags +# _CFLAGS_OTHER - the other compiler flags + +# _VERSION - version of the module +# _PREFIX - prefix-directory of the module +# _INCLUDEDIR - include-dir of the module +# _LIBDIR - lib-dir of the module + +message(STATUS "PC_LIBGLIB_FOUND = ${PC_LIBGLIB_FOUND}") +message(STATUS "PC_LIBGLIB_LIBRARIES = ${PC_LIBGLIB_LIBRARIES}") +message(STATUS "PC_LIBGLIB_LIBRARY_DIRS = ${PC_LIBGLIB_LIBRARY_DIRS}") +message(STATUS "PC_LIBGLIB_LDFLAGS = ${PC_LIBGLIB_LDFLAGS}") +message(STATUS "PC_LIBGLIB_LDFLAGS_OTHER = ${PC_LIBGLIB_LDFLAGS_OTHER}") +message(STATUS "PC_LIBGLIB_INCLUDE_DIRS = ${PC_LIBGLIB_INCLUDE_DIRS}") +message(STATUS "PC_LIBGLIB_CFLAGS = ${PC_LIBGLIB_CFLAGS}") +message(STATUS "PC_LIBGLIB_CFLAGS_OTHER = ${PC_LIBGLIB_CFLAGS_OTHER}") +message(STATUS "PC_LIBGLIB_VERSION = ${PC_LIBGLIB_VERSION}") +message(STATUS "PC_LIBGLIB_PREFIX = ${PC_LIBGLIB_PREFIX}") +message(STATUS "PC_LIBGLIB_INCLUDEDIR = ${PC_LIBGLIB_INCLUDEDIR}") +message(STATUS "PC_LIBGLIB_LIBDIR = ${PC_LIBGLIB_LIBDIR}") + +find_path(LIBGLIB_INCLUDE_DIR + NAMES glib.h + HINTS ${PC_LIBGLIB_INCLUDEDIR} ${PC_LIBGLIB_INCLUDE_DIRS} + PATH_SUFFIXES glib-2.0 ) + +find_path(LIBGLIB_CONFIG_INCLUDE_DIR + NAMES glibconfig.h + HINTS ${PC_LIBGLIB_INCLUDEDIR} ${PC_LIBGLIB_INCLUDE_DIRS} + PATH_SUFFIXES lib/glib-2.0/include ../lib/glib-2.0/include ) + +find_library(LIBGLIB_LIBRARY + NAMES glib-2.0 + HINTS ${PC_LIBGLIB_LIBDIR} ${PC_LIBGLIB_LIBRARY_DIRS} ) + +message(STATUS "LIBGLIB_INCLUDE_DIR = ${LIBGLIB_INCLUDE_DIR}") +message(STATUS "LIBGLIB_CONFIG_INCLUDE_DIR = ${LIBGLIB_CONFIG_INCLUDE_DIR}") +message(STATUS "LIBGLIB_LIBRARY = ${LIBGLIB_LIBRARY}") + +# handle the QUIETLY and REQUIRED arguments and set component to TRUE +# if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LibGlib DEFAULT_MSG + LIBGLIB_LIBRARY LIBGLIB_INCLUDE_DIR LIBGLIB_CONFIG_INCLUDE_DIR) + +mark_as_advanced(LIBGLIB_INCLUDE_DIR LIBGLIB_CONFIG_INCLUDE_DIR LIBGLIB_LIBRARY) + +set(LIBGLIB_INCLUDE_DIRS ${LIBGLIB_INCLUDE_DIR} ${LIBGLIB_CONFIG_INCLUDE_DIR}) +set(LIBGLIB_LIBRARIES ${LIBGLIB_LIBRARY}) diff --git a/helpers/ReportErrors.h b/helpers/ReportErrors.h new file mode 100644 index 0000000000..4e5f68073e --- /dev/null +++ b/helpers/ReportErrors.h @@ -0,0 +1,17 @@ +#pragma once + +#ifdef HAVE_LIBODHERR_ODHERR_H + +#include + +#else + +#define ODH_ERROR_REPORT_CTX_ERROR(e, msg, ...) +#define ODH_ERROR_REPORT_CTX_WARN(e, msg, ...) +#define ODH_ERROR_REPORT_CTX_CRITICAL(e, msg, ...) +#define ODH_ERROR_REPORT_ERROR_FORMAT_MSG(e, format, ...) +#define ODH_ERROR_REPORT_WARN_FORMAT_MSG(e, format, ...) +#define ODH_ERROR_REPORT_CRITICAL_FORMAT_MSG(e, format, ...) +#define ODH_ERROR_REPORT_DEINIT() + +#endif diff --git a/helpers/UtilsJsonRpc.h b/helpers/UtilsJsonRpc.h index fb33b50e5c..d1456dc137 100644 --- a/helpers/UtilsJsonRpc.h +++ b/helpers/UtilsJsonRpc.h @@ -44,23 +44,17 @@ returnResponse(false); \ } -/** - * DO NOT USE THIS. - * - * You should be capable of just using "Notify". - */ - #define sendNotify(event,params) { \ std::string json; \ params.ToString(json); \ LOGINFO("Notify %s %s", event, json.c_str()); \ - for (uint8_t i = 1; GetHandler(i); i++) GetHandler(i)->Notify(event,params); \ + Notify(event, params); \ } #define sendNotifyMaskParameters(event,params) { \ std::string json; \ params.ToString(json); \ LOGINFO("Notify %s <***>", event); \ - for (uint8_t i = 1; GetHandler(i); i++) GetHandler(i)->Notify(event,params); \ + Notify(event, params); \ } /** diff --git a/helpers/UtilsSynchro.hpp b/helpers/UtilsSynchro.hpp index 08667930e7..2a6950895f 100644 --- a/helpers/UtilsSynchro.hpp +++ b/helpers/UtilsSynchro.hpp @@ -10,10 +10,8 @@ using namespace WPEFramework; namespace Utils { namespace Synchro { - namespace { - // set when inside of getFunctionToCall wrapper (or locked IARM handler - see UtilsSynchroIarm.hpp) - thread_local bool isThreadUsingLockedApi = false; - } + // set when inside of getFunctionToCall wrapper (or locked IARM handler - see UtilsSynchroIarm.hpp) + thread_local bool isThreadUsingLockedApi = false; // keeps API locks, one per specific class template @@ -95,4 +93,4 @@ namespace Utils { } // Utils -} // Synchro \ No newline at end of file +} // Synchro