From 48c61c1646e44bb7625eb14b2e5711896c24a174 Mon Sep 17 00:00:00 2001 From: Adrian Muzyka Date: Tue, 3 Dec 2024 14:13:10 +0100 Subject: [PATCH 1/3] RDK-54875: Analytics plugin documentation --- Analytics/Analytics.json | 44 +++-------- Analytics/AnalyticsPlugin.json | 131 ++++++++++++++++++++++++++++--- docs/_sidebar.md | 1 + docs/api/AnalyticsPlugin.md | 137 +++++++++++++++++++++++++++++++++ docs/api/_sidebar.md | 1 + 5 files changed, 270 insertions(+), 44 deletions(-) create mode 100644 docs/api/AnalyticsPlugin.md diff --git a/Analytics/Analytics.json b/Analytics/Analytics.json index 4afc8ea17b..24ed3a03fa 100644 --- a/Analytics/Analytics.json +++ b/Analytics/Analytics.json @@ -4,7 +4,7 @@ "info": { "title": "Analytics API", "class": "Analytics", - "description": "The `Analytics` plugin allows sending analytics events to dedicated backends." + "description": "The `Analytics` plugin allows sending analytics events to dedicated backends. Currently the SIFT backend is supported." }, "common": { "$ref": "../common/common.json" @@ -49,17 +49,20 @@ "example": 35000 }, "eventPayload":{ - "summary": "The payload of the event", + "summary": "Custom payload of the event in JSON format. User defined colection of objects and keys. May be an empty object", "type": "object", - "example": { - "key1": "value1", - "key2": "value2" + "properties": { + "keyOrObject": { + "summary": "User defined custom key or object", + "type": "string", + "example": "value1" + } } } }, "methods": { "sendEvent":{ - "summary": "Send event", + "summary": "Enqueue an event to be sent to the SIFT analytics backend", "params": { "type":"object", "properties": { @@ -97,32 +100,9 @@ ] }, "result": { - "$ref": "#/common/result" - } - }, - "setSessionId" : { - "summary": "Set the session ID for the analytics events", - "params": { - "type":"object", - "properties": { - "sessionId":{ - "summary": "Session ID", - "type": "string", - "example": "1234567890" - } - }, - "required": [ - "sessionId" - ] - }, - "result": { - "$ref": "#/common/result" - } - }, - "setTimeReady" : { - "summary": "Let the analytics plugin know that the system time is ready and valid", - "result": { - "$ref": "#/common/result" + "summary": "On success null will be returned", + "type": "string", + "example": "null" } } } diff --git a/Analytics/AnalyticsPlugin.json b/Analytics/AnalyticsPlugin.json index 5e36a8ec32..27383a2bb2 100644 --- a/Analytics/AnalyticsPlugin.json +++ b/Analytics/AnalyticsPlugin.json @@ -1,13 +1,120 @@ { - "$schema": "https://raw.githubusercontent.com/rdkcentral/rdkservices/main/Tools/json_generator/schemas/plugin.schema.json", - "info": { - "title": "Analytics Plugin", - "callsign": "org.rdk.Analytics", - "locator": "libWPEFrameworkAnalytics.so", - "status": "development", - "description": "The `Analytics` plugin allows to send analytics events to dedicated backends." - }, - "interface": { - "$ref": "Analytics.json#" - } -} + "$schema":"https://raw.githubusercontent.com/rdkcentral/rdkservices/main/Tools/json_generator/schemas/plugin.schema.json", + "info":{ + "title":"Analytics Plugin", + "callsign":"org.rdk.Analytics", + "locator":"libWPEFrameworkAnalytics.so", + "status":"development", + "description":"The `Analytics` plugin allows to send analytics events to dedicated backends. Currently the SIFT backend is supported." + }, + "configuration":{ + "type":"object", + "properties":{ + "configuration":{ + "type":"object", + "properties":{ + "deviceosname":{ + "description":"Device OS name", + "type":"string" + }, + "sift":{ + "type":"object", + "properties":{ + "schema2":{ + "description":"If true, enables Sift 2.0 schema, otherwise uses Sift 1.0 schema", + "type":"boolean" + }, + "commonschema":{ + "description":"Sift schema common schema", + "type":"string" + }, + "env":{ + "description":"Sift schema environment", + "type":"string" + }, + "productname":{ + "description":"Sift schema product name", + "type":"string" + }, + "loggername":{ + "description":"Sift schema logger name", + "type":"string" + }, + "loggerversion":{ + "description":"Sift schema logger version", + "type":"string" + }, + "platformdefault":{ + "description":"Sift schema platform default value", + "type":"string" + }, + "maxrandomisationwindowtime":{ + "description":"Sift uploader max randomisation window time of posting queued events in seconds", + "type":"number" + }, + "maxeventsinpost":{ + "description":"Sift uploader max events in single post", + "type":"number" + }, + "maxretries":{ + "description":"Sift uploader max retries posting events", + "type":"number" + }, + "minretryperiod":{ + "description":"Sift uploader min retry period seconds", + "type":"number" + }, + "maxretryperiod":{ + "description":"Sift uploader max retry period seconds", + "type":"number" + }, + "exponentialperiodicfactor":{ + "description":"Sift uploader exponential periodic factor for retry delay", + "type":"number" + }, + "storepath":{ + "description":"Sift store path to persistent queue with events", + "type":"number" + }, + "eventslimit":{ + "description":"Sift store events limit", + "type":"number" + }, + "url":{ + "description":"URL to Sift server endpoint", + "type":"string" + } + }, + "required":[ + "schema2", + "commonschema", + "productname", + "loggername", + "loggerversion", + "platformdefault", + "maxrandomisationwindowtime", + "maxeventsinpost", + "maxretries", + "minretryperiod", + "maxretryperiod", + "exponentialperiodicfactor", + "storepath", + "eventslimit", + "url" + ] + } + }, + "required":[ + "deviceosname", + "sift" + ] + } + }, + "required":[ + "configuration" + ] + }, + "interface":{ + "$ref":"Analytics.json#" + } +} \ No newline at end of file diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 31d419daf0..aa8cf9aa1d 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -4,6 +4,7 @@ - [**Developer Guide**](developerguide/develperguidefornewplugin.md) - [**API Reference**](/README) - [ActivityMonitor](api/ActivityMonitorPlugin.md) + - [Analytics](api/AnalyticsPlugin.md) - [AVInput](api/AVInputPlugin.md) - [Bluetooth](api/BluetoothPlugin.md) - [CompositeInput](api/CompositeInputPlugin.md) diff --git a/docs/api/AnalyticsPlugin.md b/docs/api/AnalyticsPlugin.md new file mode 100644 index 0000000000..d581c49fbe --- /dev/null +++ b/docs/api/AnalyticsPlugin.md @@ -0,0 +1,137 @@ + + +# Analytics Plugin + +**Version: [1.0.1](https://github.com/rdkcentral/rdkservices/blob/main/Analytics/CHANGELOG.md)** + +A org.rdk.Analytics plugin for Thunder framework. + +### Table of Contents + +- [Abbreviation, Acronyms and Terms](#head.Abbreviation,_Acronyms_and_Terms) +- [Description](#head.Description) +- [Configuration](#head.Configuration) +- [Methods](#head.Methods) + + +# Abbreviation, Acronyms and Terms + +[[Refer to this link](userguide/aat.md)] + + +# Description + +The `Analytics` plugin allows to send analytics events to dedicated backends. Currently the SIFT backend is supported. + +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.Analytics*) | +| classname | string | Class name: *org.rdk.Analytics* | +| locator | string | Library name: *libWPEFrameworkAnalytics.so* | +| autostart | boolean | Determines if the plugin shall be started automatically along with the framework | +| configuration | object | | +| configuration.deviceosname | string | Device OS name | +| configuration.sift | object | | +| configuration.sift.schema2 | boolean | If true, enables Sift 2.0 schema, otherwise uses Sift 1.0 schema | +| configuration.sift.commonschema | string | Sift schema common schema | +| configuration.sift?.env | string | *(optional)* Sift schema environment | +| configuration.sift.productname | string | Sift schema product name | +| configuration.sift.loggername | string | Sift schema logger name | +| configuration.sift.loggerversion | string | Sift schema logger version | +| configuration.sift.platformdefault | string | Sift schema platform default value | +| configuration.sift.maxrandomisationwindowtime | number | Sift uploader max randomisation window time of posting queued events in seconds | +| configuration.sift.maxeventsinpost | number | Sift uploader max events in single post | +| configuration.sift.maxretries | number | Sift uploader max retries posting events | +| configuration.sift.minretryperiod | number | Sift uploader min retry period seconds | +| configuration.sift.maxretryperiod | number | Sift uploader max retry period seconds | +| configuration.sift.exponentialperiodicfactor | number | Sift uploader exponential periodic factor for retry delay | +| configuration.sift.storepath | number | Sift store path to persistent queue with events | +| configuration.sift.eventslimit | number | Sift store events limit | +| configuration.sift.url | string | URL to Sift server endpoint | + + +# Methods + +The following methods are provided by the org.rdk.Analytics plugin: + +Analytics interface methods: + +| Method | Description | +| :-------- | :-------- | +| [sendEvent](#method.sendEvent) | Enqueue an event to be sent to the SIFT analytics backend | + + + +## *sendEvent [method](#head.Methods)* + +Enqueue an event to be sent to the SIFT analytics backend. + +### Events + +No Events + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.eventName | string | Name of the event | +| params?.eventVersion | string | *(optional)* Version number of event schema | +| params.eventSource | string | Name of the component that originates the event (Durable App ID if an App) | +| params.eventSourceVersion | string | Version number for the component that originates the event | +| params.cetList | array | An array of Capability Exclusion Tags to be included on the report. Each CET will exclude the event from being processed for the specified process, any may result in the event being dropped. May be an array of length zero | +| params.cetList[#] | string | | +| params?.epochTimestamp | integer | *(optional)* Timestamp for the START of this event, epoch time, in ms UTC | +| params?.uptimeTimestamp | integer | *(optional)* Timestamp for the START of this event, uptime of the device, in ms. ONLY to be used when Time quality is not good | +| params.eventPayload | object | Custom payload of the event in JSON format. User defined colection of objects and keys. May be an empty object | +| params.eventPayload.keyOrObject | string | User defined custom key or object | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | string | On success null will be returned | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "org.rdk.Analytics.sendEvent", + "params": { + "eventName": "app_summary", + "eventVersion": "1.0.0", + "eventSource": "epg", + "eventSourceVersion": "1.0.0", + "cetList": [ + "cet1" + ], + "epochTimestamp": 1721906631000, + "uptimeTimestamp": 35000, + "eventPayload": { + "keyOrObject": "value1" + } + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "result": "null" +} +``` + diff --git a/docs/api/_sidebar.md b/docs/api/_sidebar.md index bbaf885354..15e8de8485 100644 --- a/docs/api/_sidebar.md +++ b/docs/api/_sidebar.md @@ -4,6 +4,7 @@ - [**Developer Guide**](developerguide/develperguidefornewplugin.md) - [**API Reference**](/README) - [ActivityMonitor](api/ActivityMonitorPlugin.md) + - [Analytics](api/AnalyticsPlugin.md) - [AVInput](api/AVInputPlugin.md) - [Bluetooth](api/BluetoothPlugin.md) - [CompositeInput](api/CompositeInputPlugin.md) From 603c54cc63c1e37fcde5a6058a8c05d8a8a28645 Mon Sep 17 00:00:00 2001 From: rpadin319 Date: Wed, 4 Dec 2024 21:02:33 +0000 Subject: [PATCH 2/3] RDKTV-34551 - Added DeviceInfo brandname API --- DeviceInfo/CHANGELOG.md | 4 ++++ DeviceInfo/DeviceInfo.cpp | 4 ++-- DeviceInfo/DeviceInfo.h | 1 + DeviceInfo/DeviceInfo.json | 24 +++++++++++++++++++++++- DeviceInfo/DeviceInfoJsonRpc.cpp | 18 ++++++++++++++++++ DeviceInfo/Implementation/DeviceInfo.cpp | 14 ++++++++++++++ DeviceInfo/Implementation/DeviceInfo.h | 1 + 7 files changed, 63 insertions(+), 3 deletions(-) diff --git a/DeviceInfo/CHANGELOG.md b/DeviceInfo/CHANGELOG.md index d5809acb97..e6f8c0b7d7 100644 --- a/DeviceInfo/CHANGELOG.md +++ b/DeviceInfo/CHANGELOG.md @@ -15,6 +15,10 @@ All notable changes to this RDK Service will be documented in this file. * Changes in CHANGELOG should be updated when commits are added to the main or release branches. There should be one CHANGELOG entry per JIRA Ticket. This is not enforced on sprint branches since there could be multiple changes for the same JIRA ticket during development. * For more details, refer to [versioning](https://github.com/rdkcentral/rdkservices#versioning) section under Main README. +## [1.1.0] - 2024-11-26 +### Added +- Added API to get Device brand + ## [1.0.15] - 2024-09-16 ### Changed - DELIA-65827: Return Empty SerialNumber in curl for "method": "DeviceInfo.1.systeminfo" diff --git a/DeviceInfo/DeviceInfo.cpp b/DeviceInfo/DeviceInfo.cpp index 566c3645af..86a0a551a1 100644 --- a/DeviceInfo/DeviceInfo.cpp +++ b/DeviceInfo/DeviceInfo.cpp @@ -20,8 +20,8 @@ #include "DeviceInfo.h" #define API_VERSION_NUMBER_MAJOR 1 -#define API_VERSION_NUMBER_MINOR 0 -#define API_VERSION_NUMBER_PATCH 14 +#define API_VERSION_NUMBER_MINOR 1 +#define API_VERSION_NUMBER_PATCH 0 namespace WPEFramework { namespace { diff --git a/DeviceInfo/DeviceInfo.h b/DeviceInfo/DeviceInfo.h index 4714b6c0aa..191e250b3d 100644 --- a/DeviceInfo/DeviceInfo.h +++ b/DeviceInfo/DeviceInfo.h @@ -117,6 +117,7 @@ namespace Plugin { uint32_t get_modelid(JsonData::DeviceInfo::ModelidData& response) const; uint32_t get_make(JsonData::DeviceInfo::MakeData& response) const; uint32_t get_modelname(JsonData::DeviceInfo::ModelnameData& response) const; + uint32_t get_brandname(JsonData::DeviceInfo::BrandnameData& response) const; uint32_t get_devicetype(JsonData::DeviceInfo::DevicetypeData& response) const; uint32_t get_distributorid(JsonData::DeviceInfo::DistributoridData& response) const; uint32_t get_supportedaudioports(JsonData::DeviceInfo::SupportedaudioportsData& response) const; diff --git a/DeviceInfo/DeviceInfo.json b/DeviceInfo/DeviceInfo.json index 463e051f76..b7918adb44 100644 --- a/DeviceInfo/DeviceInfo.json +++ b/DeviceInfo/DeviceInfo.json @@ -509,6 +509,28 @@ } ] }, + "brandname": { + "summary": "Device brand name", + "readonly": true, + "params": { + "type": "object", + "properties": { + "brandname": { + "type": "string", + "example": "Comcast" + } + }, + "required": [ + "brand" + ] + }, + "errors": [ + { + "description": "General error", + "$ref": "#/common/errors/general" + } + ] + }, "make": { "summary": "Device manufacturer", "readonly": true, @@ -867,4 +889,4 @@ ] } } -} \ No newline at end of file +} diff --git a/DeviceInfo/DeviceInfoJsonRpc.cpp b/DeviceInfo/DeviceInfoJsonRpc.cpp index 166050f286..e777f2138b 100644 --- a/DeviceInfo/DeviceInfoJsonRpc.cpp +++ b/DeviceInfo/DeviceInfoJsonRpc.cpp @@ -39,6 +39,7 @@ namespace Plugin { Property(_T("modelid"), &DeviceInfo::get_modelid, nullptr, this); Property(_T("make"), &DeviceInfo::get_make, nullptr, this); Property(_T("modelname"), &DeviceInfo::get_modelname, nullptr, this); + Property(_T("brandname"), &DeviceInfo::get_brandname, nullptr, this); Property(_T("devicetype"), &DeviceInfo::get_devicetype, nullptr, this); Property(_T("distributorid"), &DeviceInfo::get_distributorid, nullptr, this); Property(_T("supportedaudioports"), &DeviceInfo::get_supportedaudioports, nullptr, this); @@ -62,6 +63,7 @@ namespace Plugin { Unregister(_T("modelid")); Unregister(_T("make")); Unregister(_T("modelname")); + Unregister(_T("brandname")); Unregister(_T("devicetype")); Unregister(_T("distributorid")); Unregister(_T("supportedaudioports")); @@ -221,6 +223,22 @@ namespace Plugin { return result; } + // Property: brandname - device brand name + // Return codes: + // - ERROR_NONE: Success + // - ERROR_GENERAL: General error + uint32_t DeviceInfo::get_brandname(BrandnameData& response) const + { + string brand; + + auto result = _deviceInfo->Brand(brand); + if (result == Core::ERROR_NONE) { + response.Brand = brand; + } + + return result; + } + // Property: devicetype - Device type // Return codes: // - ERROR_NONE: Success diff --git a/DeviceInfo/Implementation/DeviceInfo.cpp b/DeviceInfo/Implementation/DeviceInfo.cpp index 9707f52c7a..53315657c9 100644 --- a/DeviceInfo/Implementation/DeviceInfo.cpp +++ b/DeviceInfo/Implementation/DeviceInfo.cpp @@ -116,6 +116,20 @@ namespace Plugin { std::regex("^FRIENDLY_ID(?:\\s*)=(?:\\s*)(?:\"{0,1})([^\"\\n]+)(?:\"{0,1})(?:\\s*)$"), model); } + uint32_t DeviceInfoImplementation::Brand(string& brand) const + { + brand = "Unknown"; + return +#ifdef USE_SERIALIZED_MANUFACTURER_NAME + ((Core::ERROR_NONE == GetFileRegex(_T("/tmp/.manufacturer"), std::regex("^([^\\n]+)$"), brand)) || + (GetMFRData(mfrSERIALIZED_TYPE_MANUFACTURER, brand) == Core::ERROR_NONE)) + ? Core::ERROR_NONE + : +#endif + GetFileRegex(_T("/etc/device.properties"), + std::regex("^BRAND_NAME(?:\\s*)=(?:\\s*)(?:\"{0,1})([^\"\\n]+)(?:\"{0,1})(?:\\s*)$"), brand); + } + uint32_t DeviceInfoImplementation::DeviceType(string& deviceType) const { #ifndef ENABLE_COMMUNITY_DEVICE_TYPE diff --git a/DeviceInfo/Implementation/DeviceInfo.h b/DeviceInfo/Implementation/DeviceInfo.h index 66d50dc003..cf321ea3d4 100644 --- a/DeviceInfo/Implementation/DeviceInfo.h +++ b/DeviceInfo/Implementation/DeviceInfo.h @@ -29,6 +29,7 @@ namespace Plugin { uint32_t Model(string& model) const override; uint32_t DeviceType(string& deviceType) const override; uint32_t DistributorId(string& distributorId) const override; + uint32_t Brand(string& brand) const override; }; } } From 8a4fb5d35e9a3ecedbe37fb131eabe3418adb534 Mon Sep 17 00:00:00 2001 From: Nikita Poltorapavlo Date: Thu, 12 Sep 2024 18:19:01 +0300 Subject: [PATCH 3/3] RDK-52316 : Content Protection service Reason for change: new service to proxy content security capabilities of the existing DRM plugins like SecManager & Watermark and provide a single unified interface. Test Procedure: None Risks: None Signed-off-by: Nikita Poltorapavlo --- .github/workflows/L2-ContentProtection.yml | 35 + CMakeLists.txt | 4 + ContentProtection/CHANGELOG.md | 25 + ContentProtection/CMakeLists.txt | 44 + ContentProtection/ContentProtection.conf.in | 4 + ContentProtection/ContentProtection.config | 7 + ContentProtection/ContentProtection.cpp | 95 +++ ContentProtection/ContentProtection.h | 757 ++++++++++++++++++ .../ContentProtectionPlugin.json | 10 + ContentProtection/Module.cpp | 22 + ContentProtection/Module.h | 33 + 11 files changed, 1036 insertions(+) create mode 100644 .github/workflows/L2-ContentProtection.yml create mode 100644 ContentProtection/CHANGELOG.md create mode 100644 ContentProtection/CMakeLists.txt create mode 100644 ContentProtection/ContentProtection.conf.in create mode 100644 ContentProtection/ContentProtection.config create mode 100644 ContentProtection/ContentProtection.cpp create mode 100644 ContentProtection/ContentProtection.h create mode 100644 ContentProtection/ContentProtectionPlugin.json create mode 100644 ContentProtection/Module.cpp create mode 100644 ContentProtection/Module.h diff --git a/.github/workflows/L2-ContentProtection.yml b/.github/workflows/L2-ContentProtection.yml new file mode 100644 index 0000000000..34c91be58f --- /dev/null +++ b/.github/workflows/L2-ContentProtection.yml @@ -0,0 +1,35 @@ +name: L2-ContentProtection + +on: + push: + paths: + - ContentProtection/** + - .github/workflows/*ContentProtection*.yml + pull_request: + paths: + - ContentProtection/** + - .github/workflows/*ContentProtection*.yml + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + path: ${{github.repository}} + + - name: Install cmake + run: | + sudo apt update + sudo apt install -y cmake + + - name: Build Thunder + working-directory: ${{github.workspace}} + run: sh +x ${GITHUB_REPOSITORY}/.github/workflows/BuildThunder.sh + + - name: Build + working-directory: ${{github.workspace}} + run: | + cmake -S ${GITHUB_REPOSITORY}/ContentProtection -B build/ContentProtection -DCMAKE_INSTALL_PREFIX="install" -DCMAKE_CXX_FLAGS="-Wall -Werror" + cmake --build build/ContentProtection --target install diff --git a/CMakeLists.txt b/CMakeLists.txt index 383ebee249..cc398a1c47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -417,6 +417,10 @@ if(PLUGIN_MIGRATIONPREPARER) add_subdirectory(MigrationPreparer) endif() +if(PLUGIN_CONTENTPROTECTION) + add_subdirectory(ContentProtection) +endif() + if(WPEFRAMEWORK_CREATE_IPKG_TARGETS) set(CPACK_GENERATOR "DEB") set(CPACK_DEB_COMPONENT_INSTALL ON) diff --git a/ContentProtection/CHANGELOG.md b/ContentProtection/CHANGELOG.md new file mode 100644 index 0000000000..7864154892 --- /dev/null +++ b/ContentProtection/CHANGELOG.md @@ -0,0 +1,25 @@ +# 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.0] - 2024-07-15 +### Added +- Add CHANGELOG + +### Change +- Reset API version to 1.0.0 +- Change README to inform how to update changelog and API version diff --git a/ContentProtection/CMakeLists.txt b/ContentProtection/CMakeLists.txt new file mode 100644 index 0000000000..c99f8a9aef --- /dev/null +++ b/ContentProtection/CMakeLists.txt @@ -0,0 +1,44 @@ +# 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. + +cmake_minimum_required(VERSION 3.14) + +set(PLUGIN_NAME ContentProtection) +find_package(WPEFramework NAMES WPEFramework Thunder) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) + +set(CMAKE_CXX_STANDARD 11) + +set(PLUGIN_CONTENTPROTECTION_STARTUPORDER "" CACHE STRING "To configure startup order of the plugin") +set(PLUGIN_CONTENTPROTECTION_AUTOSTART false CACHE STRING "To automatically start the plugin") + +add_library(${MODULE_NAME} SHARED + ContentProtection.cpp + Module.cpp +) + +find_package(${NAMESPACE}Plugins REQUIRED) +find_package(${NAMESPACE}Definitions REQUIRED) +target_link_libraries(${MODULE_NAME} PRIVATE + ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${NAMESPACE}Definitions::${NAMESPACE}Definitions +) + +install(TARGETS ${MODULE_NAME} + DESTINATION lib/${STORAGE_DIRECTORY}/plugins) + +write_config(${PLUGIN_NAME}) diff --git a/ContentProtection/ContentProtection.conf.in b/ContentProtection/ContentProtection.conf.in new file mode 100644 index 0000000000..d9a71b3016 --- /dev/null +++ b/ContentProtection/ContentProtection.conf.in @@ -0,0 +1,4 @@ +precondition = ["Platform"] +callsign = "org.rdk.ContentProtection" +autostart = "@PLUGIN_CONTENTPROTECTION_AUTOSTART@" +startuporder = "@PLUGIN_CONTENTPROTECTION_STARTUPORDER@" diff --git a/ContentProtection/ContentProtection.config b/ContentProtection/ContentProtection.config new file mode 100644 index 0000000000..1ad44748b5 --- /dev/null +++ b/ContentProtection/ContentProtection.config @@ -0,0 +1,7 @@ +set(autostart ${PLUGIN_CONTENTPROTECTION_AUTOSTART}) +set(preconditions Platform) +set(callsign "org.rdk.ContentProtection") + +if(PLUGIN_CONTENTPROTECTION_STARTUPORDER) +set (startuporder ${PLUGIN_CONTENTPROTECTION_STARTUPORDER}) +endif() diff --git a/ContentProtection/ContentProtection.cpp b/ContentProtection/ContentProtection.cpp new file mode 100644 index 0000000000..82c58013aa --- /dev/null +++ b/ContentProtection/ContentProtection.cpp @@ -0,0 +1,95 @@ +/* + * 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. + */ + +#include "ContentProtection.h" + +#define API_VERSION_NUMBER_MAJOR 1 +#define API_VERSION_NUMBER_MINOR 0 +#define API_VERSION_NUMBER_PATCH 0 + +namespace WPEFramework { +namespace Plugin { + + namespace { + static Metadata metadata( + // Version (Major, Minor, Patch) + API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH, + // Preconditions + {}, + // Terminations + {}, + // Controls + {}); + } + + SERVICE_REGISTRATION(ContentProtection, API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH); + + const string ContentProtection::Initialize(PluginHost::IShell* service) + { + string result; + + ASSERT(service != nullptr); + + _implementation = Core::Service::Create< + Exchange::IContentProtection>(*this); + Exchange::JContentProtection::Register(*this, _implementation); + + string token; + auto security = service->QueryInterfaceByCallsign< + PluginHost::IAuthenticate>("SecurityAgent"); + if (security != nullptr) { + string payload = "http://localhost"; + auto ret = security->CreateToken( + static_cast(payload.length()), + reinterpret_cast(payload.c_str()), + token); + if (ret != Core::ERROR_NONE) { + SYSLOG(Logging::Startup, + (_T("Couldn't create token: %d"), ret)); + } + security->Release(); + } + + Core::SystemInfo::SetEnvironment(_T("THUNDER_ACCESS"), + (_T("127.0.0.1:9998"))); + _secManager = Core::ProxyType::Create( + _T("SecManager"), _T(""), "token=" + token); + _watermark = Core::ProxyType::Create( + _T("Watermark"), _T(""), "token=" + token); + Subscribe(); + + return result; + } + + void ContentProtection::Deinitialize(PluginHost::IShell*) + { + Unsubscribe(); + Exchange::JContentProtection::Unregister(*this); + if (_implementation != nullptr) { + _implementation->Release(); + } + } + + string ContentProtection::Information() const + { + return (string()); + } + +} // namespace Plugin +} // namespace WPEFramework diff --git a/ContentProtection/ContentProtection.h b/ContentProtection/ContentProtection.h new file mode 100644 index 0000000000..93c877aba5 --- /dev/null +++ b/ContentProtection/ContentProtection.h @@ -0,0 +1,757 @@ +/* + * 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" +#include +#include + +namespace WPEFramework { +namespace Plugin { + + class ContentProtection + : public PluginHost::IPlugin, + public PluginHost::JSONRPC { + private: + struct Session { + string ClientId; + Exchange::IContentProtection::KeySystem KeySystem; + }; + + private: + struct Watermark { + uint32_t SessionId; + bool AdjustVisibilityRequired; + uint32_t GraphicImageBufferKey; + uint32_t GraphicImageSize; + }; + + private: + template + class Storage { + public: + void Set(uint32_t id, const OBJECT& item) + { + Core::SafeSyncType lock(_lock); + auto it = _items.find(id); + if (it != _items.end()) { + it->second = item; + } else { + _items.emplace(id, item); + } + } + Core::OptionalType Get(uint32_t id) + { + Core::SafeSyncType lock(_lock); + Core::OptionalType result; + auto it = _items.find(id); + if (it != _items.end()) { + result = it->second; + } + return result; + } + void Delete(uint32_t id) + { + Core::SafeSyncType lock(_lock); + _items.erase(id); + } + + private: + Core::CriticalSection _lock; + std::map _items; + }; + + private: + struct OnAddWatermarkParams : public Core::JSON::Container { + OnAddWatermarkParams() + { + Add(_T("sessionId"), &SessionId); + Add(_T("graphicId"), &GraphicId); + Add(_T("adjustVisibilityRequired"), + &AdjustVisibilityRequired); + Add(_T("zIndex"), &ZIndex); + Add(_T("imageType"), &ImageType); + Add(_T("graphicImageBufferKey"), &GraphicImageBufferKey); + Add(_T("graphicImageSize"), &GraphicImageSize); + } + Core::JSON::DecUInt32 SessionId; + Core::JSON::DecUInt32 GraphicId; + Core::JSON::Boolean AdjustVisibilityRequired; + Core::JSON::DecUInt32 ZIndex; + Core::JSON::String ImageType; + Core::JSON::DecUInt32 GraphicImageBufferKey; + Core::JSON::DecUInt32 GraphicImageSize; + }; + + struct OnRemoveWatermarkParams : public Core::JSON::Container { + OnRemoveWatermarkParams() + { + Add(_T("graphicId"), &GraphicId); + } + Core::JSON::DecUInt32 GraphicId; + }; + + struct OnDisplayWatermarkParams : public Core::JSON::Container { + OnDisplayWatermarkParams() + { + Add(_T("hideWatermark"), &HideWatermark); + } + Core::JSON::Boolean HideWatermark; + }; + + struct OnWatermarkSessionParams : public Core::JSON::Container { + OnWatermarkSessionParams() + { + Add(_T("sessionId"), &SessionId); + Add(_T("conditionContext"), &ConditionContext); + } + Core::JSON::DecUInt32 SessionId; + Core::JSON::DecSInt32 ConditionContext; + }; + + struct OnUpdateWatermarkParams : public Core::JSON::Container { + OnUpdateWatermarkParams() + { + Add(_T("graphicId"), &GraphicId); + Add(_T("watermarkClutBufferKey"), &WatermarkClutBufferKey); + Add(_T("watermarkImageBufferKey"), + &WatermarkImageBufferKey); + } + Core::JSON::DecUInt32 GraphicId; + Core::JSON::DecUInt32 WatermarkClutBufferKey; + Core::JSON::DecUInt32 WatermarkImageBufferKey; + }; + + private: + struct OnWatermarkRequestStatusParams : public Core::JSON::Container { + OnWatermarkRequestStatusParams() + { + Add(_T("id"), &Id); + Add(_T("type"), &Type); + Add(_T("success"), &Success); + } + Core::JSON::DecUInt32 Id; + Core::JSON::String Type; + Core::JSON::Boolean Success; + }; + + struct OnWatermarkRenderFailedParams : public Core::JSON::Container { + OnWatermarkRenderFailedParams() + { + Add(_T("image"), &Image); + } + Core::JSON::DecUInt32 Image; + }; + + private: + struct SetPlaybackSessionStateParams : public Core::JSON::Container { + SetPlaybackSessionStateParams() + { + Add(_T("clientId"), &ClientId); + Add(_T("sessionid"), &SessionId); + Add(_T("sessionstate"), &SessionState); + } + Core::JSON::String ClientId; + Core::JSON::DecUInt32 SessionId; + Core::JSON::EnumType< + Exchange::IContentProtection::State> + SessionState; + }; + + struct ClosePlaybackSessionParams : public Core::JSON::Container { + ClosePlaybackSessionParams() + { + Add(_T("clientId"), &ClientId); + Add(_T("sessionid"), &SessionId); + } + Core::JSON::String ClientId; + Core::JSON::DecUInt32 SessionId; + }; + + struct SetPlaybackSpeedStateParams : public Core::JSON::Container { + SetPlaybackSpeedStateParams() + { + Add(_T("sessionid"), &SessionId); + Add(_T("playbackSpeed"), &PlaybackSpeed); + Add(_T("playbackPosition"), &PlaybackPosition); + } + Core::JSON::DecUInt32 SessionId; + Core::JSON::DecSInt32 PlaybackSpeed; + Core::JSON::DecSInt32 PlaybackPosition; + }; + + struct LoadClutWatermarkParams : public Core::JSON::Container { + LoadClutWatermarkParams() + { + Add(_T("sessionId"), &SessionId); + Add(_T("graphicId"), &GraphicId); + Add(_T("watermarkClutBufferKey"), &WatermarkClutBufferKey); + Add(_T("watermarkImageBufferKey"), &WatermarkImageBufferKey); + Add(_T("clutPaletteSize"), &ClutPaletteSize); + Add(_T("clutPaletteFormat"), &ClutPaletteFormat); + Add(_T("watermarkWidth"), &WatermarkWidth); + Add(_T("watermarkHeight"), &WatermarkHeight); + Add(_T("aspectRatio"), &AspectRatio); + } + Core::JSON::DecUInt32 SessionId; + Core::JSON::DecUInt32 GraphicId; + Core::JSON::DecUInt32 WatermarkClutBufferKey; + Core::JSON::DecUInt32 WatermarkImageBufferKey; + Core::JSON::DecUInt32 ClutPaletteSize; + Core::JSON::String ClutPaletteFormat; + Core::JSON::DecUInt32 WatermarkWidth; + Core::JSON::DecUInt32 WatermarkHeight; + Core::JSON::Float AspectRatio; + }; + + private: + struct ShowWatermarkParams : public Core::JSON::Container { + ShowWatermarkParams() + { + Add(_T("show"), &Show); + } + Core::JSON::Boolean Show; + }; + + struct CreateWatermarkParams : public Core::JSON::Container { + CreateWatermarkParams() + { + Add(_T("id"), &Id); + Add(_T("zorder"), &Zorder); + } + Core::JSON::DecUInt32 Id; + Core::JSON::DecUInt32 Zorder; + }; + + struct DeleteWatermarkParams : public Core::JSON::Container { + DeleteWatermarkParams() + { + Add(_T("id"), &Id); + } + Core::JSON::DecUInt32 Id; + }; + + struct PaletteWatermarkParams : public Core::JSON::Container { + PaletteWatermarkParams() + { + Add(_T("id"), &Id); + Add(_T("imageKey"), &ImageKey); + Add(_T("imageWidth"), &ImageWidth); + Add(_T("imageHeight"), &ImageHeight); + Add(_T("clutKey"), &ClutKey); + Add(_T("clutSize"), &ClutSize); + Add(_T("clutType"), &ClutType); + } + Core::JSON::DecUInt32 Id; + Core::JSON::DecUInt32 ImageKey; + Core::JSON::DecUInt32 ImageWidth; + Core::JSON::DecUInt32 ImageHeight; + Core::JSON::DecUInt32 ClutKey; + Core::JSON::DecUInt32 ClutSize; + Core::JSON::String ClutType; + }; + + struct UpdateWatermarkParams : public Core::JSON::Container { + UpdateWatermarkParams() + { + Add(_T("id"), &Id); + Add(_T("key"), &Key); + Add(_T("size"), &Size); + } + Core::JSON::DecUInt32 Id; + Core::JSON::DecUInt32 Key; + Core::JSON::DecUInt32 Size; + }; + + private: + enum { Timeout = 1000 }; + + private: + using JSONRPCLink = WPEFramework::JSONRPC::SmartLinkType< + Core::JSON::IElement>; + using State + = Exchange::IContentProtection::INotification::Status::State; + + private: + class Implementation : public Exchange::IContentProtection { + public: + Implementation(const Implementation&) = delete; + Implementation(Implementation&&) = delete; + Implementation& operator=(const Implementation&) = delete; + Implementation& operator=(Implementation&&) = delete; + + explicit Implementation(ContentProtection& parent) + : _parent(parent) + { + } + + BEGIN_INTERFACE_MAP(Implementation) + INTERFACE_ENTRY(IContentProtection) + END_INTERFACE_MAP + + private: + uint32_t Register( + IContentProtection::INotification* notification) override + { + Core::SafeSyncType lock( + _parent._clientLock); + ASSERT(std::find( + _parent._clients.begin(), _parent._clients.end(), + notification) + == _parent._clients.end()); + notification->AddRef(); + _parent._clients.push_back(notification); + return Core::ERROR_NONE; + } + + uint32_t Unregister( + IContentProtection::INotification* notification) override + { + Core::SafeSyncType lock( + _parent._clientLock); + auto index = std::find( + _parent._clients.begin(), _parent._clients.end(), + notification); + ASSERT(index != _parent._clients.end()); + if (index != _parent._clients.end()) { + notification->Release(); + _parent._clients.erase(index); + } + return Core::ERROR_NONE; + } + + uint32_t OpenDrmSession(const string& clientId, + KeySystem keySystem, const string& licenseRequest, + const string& initData, uint32_t& sessionId, string& response) + override + { + uint32_t result; + JsonObject out; + out.FromString(initData); + out["clientId"] = clientId; + out["keySystem"] = Core::JSON::EnumType(keySystem) + .Data(); + out["licenseRequest"] = licenseRequest; + JsonObject in; + result = _parent._secManager->Invoke( + Timeout, _T("openPlaybackSession"), out, in); + if (result == Core::ERROR_NONE) { + if (!in["success"].Boolean()) { + result = Core::ERROR_GENERAL; + } else { + sessionId = in["sessionId"].Number(); + in.ToString(response); + + _parent._sessionStorage.Set(sessionId, + { clientId, keySystem }); + } + } + return result; + } + + uint32_t SetDrmSessionState(uint32_t sessionId, + State sessionState) override + { + auto session = _parent._sessionStorage.Get(sessionId); + if (!session.IsSet()) { + return Core::ERROR_ILLEGAL_STATE; // No such session + } + + uint32_t result; + SetPlaybackSessionStateParams out; + out.ClientId = session.Value().ClientId; + out.SessionId = sessionId; + out.SessionState = sessionState; + JsonObject in; + result = _parent._secManager->Invoke< + SetPlaybackSessionStateParams, JsonObject>( + Timeout, _T("setPlaybackSessionState"), out, in); + if (result == Core::ERROR_NONE) { + if (!in["success"].Boolean()) { + result = Core::ERROR_GENERAL; + } + } + return result; + } + + uint32_t UpdateDrmSession(uint32_t sessionId, + const string& licenseRequest, + const string& initData, string& response) override + { + auto session = _parent._sessionStorage.Get(sessionId); + if (!session.IsSet()) { + return Core::ERROR_ILLEGAL_STATE; // No such session + } + + uint32_t result; + JsonObject out; + out.FromString(initData); + out["clientId"] = session.Value().ClientId; + out["sessionId"] = sessionId; + out["keySystem"] = Core::JSON::EnumType( + session.Value().KeySystem) + .Data(); + out["licenseRequest"] = licenseRequest; + JsonObject in; + result = _parent._secManager->Invoke< + JsonObject, JsonObject>( + Timeout, _T("updatePlaybackSession"), out, in); + if (result == Core::ERROR_NONE) { + if (!in["success"].Boolean()) { + result = Core::ERROR_GENERAL; + } else { + in.ToString(response); + } + } + return result; + } + + uint32_t CloseDrmSession(uint32_t sessionId) override + { + auto session = _parent._sessionStorage.Get(sessionId); + if (!session.IsSet()) { + return Core::ERROR_ILLEGAL_STATE; // No such session + } + + uint32_t result; + ClosePlaybackSessionParams out; + out.ClientId = session.Value().ClientId; + out.SessionId = sessionId; + JsonObject in; + result = _parent._secManager->Invoke< + ClosePlaybackSessionParams, JsonObject>( + Timeout, _T("closePlaybackSession"), out, in); + if ((result == Core::ERROR_NONE) && !in["success"].Boolean()) { + result = Core::ERROR_GENERAL; + } + return result; + } + + uint32_t ShowWatermark(uint32_t /*sessionId*/, + bool show, bool /*localOverlay*/) override + { + uint32_t result; + ShowWatermarkParams out; + out.Show = show; + JsonObject in; + result = _parent._watermark->Invoke< + ShowWatermarkParams, JsonObject>( + Timeout, _T("showWatermark"), out, in); + if ((result == Core::ERROR_NONE) && !in["success"].Boolean()) { + result = Core::ERROR_GENERAL; + } + return result; + } + + uint32_t SetPlaybackPosition(uint32_t sessionId, + int32_t speed, long position) override + { + uint32_t result; + SetPlaybackSpeedStateParams out; + out.SessionId = sessionId; + out.PlaybackSpeed = speed; + out.PlaybackPosition = position; + JsonObject in; + result = _parent._secManager->Invoke< + SetPlaybackSpeedStateParams, JsonObject>( + Timeout, _T("setPlaybackSpeedState"), out, in); + if ((result == Core::ERROR_NONE) && !in["success"].Boolean()) { + result = Core::ERROR_GENERAL; + } + return result; + } + + private: + ContentProtection& _parent; + }; + + public: + ContentProtection(const ContentProtection&) = delete; + ContentProtection(ContentProtection&&) = delete; + ContentProtection& operator=(const ContentProtection&) = delete; + ContentProtection& operator=(ContentProtection&&) = delete; + ContentProtection() = default; + + BEGIN_INTERFACE_MAP(ContentProtection) + INTERFACE_ENTRY(PluginHost::IPlugin) + INTERFACE_ENTRY(PluginHost::IDispatcher) + INTERFACE_AGGREGATE(Exchange::IContentProtection, _implementation) + END_INTERFACE_MAP + + private: + const string Initialize(PluginHost::IShell* service) override; + void Deinitialize(PluginHost::IShell* service) override; + string Information() const override; + + private: + void Subscribe() + { + ASSERT(_secManager->Subscribe( + Timeout, _T("onAddWatermark"), + [&](const OnAddWatermarkParams& params) { + _watermarkStorage.Set( + params.GraphicId, + { params.SessionId, + params.AdjustVisibilityRequired, + params.GraphicImageBufferKey, + params.GraphicImageSize }); + + CreateWatermarkParams out; + out.Id = params.GraphicId; + out.Zorder = params.ZIndex; + JsonObject in; + if ((_watermark->Invoke< + CreateWatermarkParams, JsonObject>( + Timeout, _T("createWatermark"), out, in) + != Core::ERROR_NONE) + || !in["success"].Boolean()) { + TRACE(Trace::Error, + (_T("create %d failed"), params.GraphicId)); + } + }) + == Core::ERROR_NONE); + ASSERT(_secManager->Subscribe( + Timeout, _T("onRemoveWatermark"), + [&](const OnRemoveWatermarkParams& params) { + DeleteWatermarkParams out; + out.Id = params.GraphicId; + JsonObject in; + if ((_watermark->Invoke< + DeleteWatermarkParams, JsonObject>( + Timeout, _T("deleteWatermark"), out, in) + != Core::ERROR_NONE) + || !in["success"].Boolean()) { + TRACE(Trace::Error, + (_T("delete %d failed"), params.GraphicId)); + } + }) + == Core::ERROR_NONE); + ASSERT(_secManager->Subscribe( + Timeout, _T("onDisplayWatermark"), + [&](const OnDisplayWatermarkParams& params) { + uint32_t result; + ShowWatermarkParams out; + out.Show = !params.HideWatermark; + JsonObject in; + result = _watermark->Invoke< + ShowWatermarkParams, JsonObject>( + Timeout, _T("showWatermark"), out, in); + if ((result != Core::ERROR_NONE) + || !in["success"].Boolean()) { + TRACE(Trace::Error, (_T("show failed"))); + } + }) + == Core::ERROR_NONE); + ASSERT(_secManager->Subscribe( + Timeout, _T("onWatermarkSession"), + [&](const OnWatermarkSessionParams& params) { + WatermarkStatusChanged( + params.SessionId, + { ((params.ConditionContext == 1) + ? State::GRANTED + : ((params.ConditionContext == 2) + ? State::NOT_REQUIRED + : ((params.ConditionContext == 3) + ? State::DENIED + : State::FAILED))), + params.ConditionContext }); + }) + == Core::ERROR_NONE); + ASSERT(_secManager->Subscribe( + Timeout, _T("onUpdateWatermark"), + [&](const OnUpdateWatermarkParams& params) { + auto palette = _palettedImageDataStorage.Get( + params.GraphicId); + if (!palette.IsSet()) { + TRACE(Trace::Error, + (_T("no palette %d"), params.GraphicId)); + } else { + PaletteWatermarkParams out; + out.Id = params.GraphicId; + out.ImageKey = params.WatermarkImageBufferKey; + out.ImageWidth = palette.Value().imageWidth; + out.ImageHeight = palette.Value().imageHeight; + out.ClutKey = params.WatermarkClutBufferKey; + out.ClutSize = palette.Value().clutSize; + out.ClutType = palette.Value().clutType; + JsonObject in; + if ((_watermark->Invoke< + PaletteWatermarkParams, JsonObject>( + Timeout, _T("modifyPalettedWatermark"), + out, in) + != Core::ERROR_NONE) + || !in["success"].Boolean()) { + TRACE(Trace::Error, + (_T("modify %d failed"), + params.GraphicId)); + } + } + }) + == Core::ERROR_NONE); + ASSERT(_watermark->Subscribe( + Timeout, _T("onWatermarkRequestStatus"), + [&](const OnWatermarkRequestStatusParams& params) { + if (params.Type == "show") { + if (!params.Success) { + TRACE(Trace::Error, (_T("show failed"))); + } + // Watermark plugin does not tell + // which call ended, can be any. Can't take this + // information as a response + } else if (!params.Success) { + auto watermark = _watermarkStorage + .Get(params.Id); + if (watermark.IsSet()) { + TRACE(Trace::Error, + (_T("%s %d failed"), + params.Type.Value().c_str(), + params.Id)); + } + } else if (params.Type == "create") { + auto watermark = _watermarkStorage + .Get(params.Id); + if (watermark.IsSet()) { + UpdateWatermarkParams out; + out.Id = params.Id; + out.Key = watermark.Value() + .GraphicImageBufferKey; + out.Size = watermark.Value() + .GraphicImageSize; + JsonObject in; + if ((_watermark->Invoke< + UpdateWatermarkParams, JsonObject>( + Timeout, _T("updateWatermark"), + out, in) + != Core::ERROR_NONE) + || !in["success"].Boolean()) { + TRACE(Trace::Error, + (_T("update %d failed"), + params.Id)); + } + } + } else if (params.Type == "update") { + auto watermark = _watermarkStorage + .Get(params.Id); + if (watermark.IsSet() + && watermark.Value() + .AdjustVisibilityRequired) { + DeleteWatermarkParams out; + out.Id = params.Id; + PaletteWatermarkParams in; + if ((_watermark->Invoke< + DeleteWatermarkParams, + PaletteWatermarkParams>( + Timeout, + _T("getPalettedWatermark"), + out, in) + != Core::ERROR_NONE) + || !in.ImageWidth || !in.ImageHeight) { + TRACE(Trace::Error, + (_T("get %d failed"), params.Id)); + } else { + _palettedImageDataStorage.Set( + params.Id, + { in.ImageKey, + in.ImageWidth, + in.ImageHeight, + in.ClutKey, + in.ClutSize, + in.ClutType }); + + LoadClutWatermarkParams out; + out.SessionId = watermark.Value() + .SessionId; + out.GraphicId = params.Id; + out.WatermarkClutBufferKey = in.ClutKey; + out.WatermarkImageBufferKey + = in.ImageKey; + out.ClutPaletteSize = in.ClutSize; + out.ClutPaletteFormat = in.ClutType; + out.WatermarkWidth = in.ImageWidth; + out.WatermarkHeight = in.ImageHeight; + out.AspectRatio + = ((float)in.ImageWidth + / (float)in.ImageHeight); + JsonObject in2; + if ((_secManager->Invoke< + LoadClutWatermarkParams, + JsonObject>( + Timeout, + _T("loadClutWatermark"), + out, in2) + != Core::ERROR_NONE) + || !in2["success"].Boolean()) { + TRACE(Trace::Error, + (_T("load %d failed"), + params.Id)); + } + } + } + } + }) + == Core::ERROR_NONE); + ASSERT(_watermark->Subscribe( + Timeout, _T("onWatermarkRenderFailed"), + [&](const OnWatermarkRenderFailedParams& params) { + auto watermark = _watermarkStorage + .Get(params.Image); + if (watermark.IsSet()) { + WatermarkStatusChanged( + watermark.Value().SessionId, + { State::FAILED, 20001 }); + } + }) + == Core::ERROR_NONE); + } + void Unsubscribe() + { + _secManager->Unsubscribe(Timeout, _T("onAddWatermark")); + _secManager->Unsubscribe(Timeout, _T("onRemoveWatermark")); + _secManager->Unsubscribe(Timeout, _T("onDisplayWatermark")); + _secManager->Unsubscribe(Timeout, _T("onWatermarkSession")); + _secManager->Unsubscribe(Timeout, _T("onUpdateWatermark")); + _watermark->Unsubscribe(Timeout, _T("onWatermarkRequestStatus")); + _watermark->Unsubscribe(Timeout, _T("onWatermarkRenderFailed")); + } + void WatermarkStatusChanged(uint32_t sessionId, + const Exchange::IContentProtection::INotification::Status& status) + { + Exchange::JContentProtection::Event::WatermarkStatusChanged( + *this, sessionId, status); + + Core::SafeSyncType lock(_clientLock); + for (auto& i : _clients) { + i->WatermarkStatusChanged(sessionId, status); + } + } + + private: + Exchange::IContentProtection* _implementation; + Core::ProxyType _secManager; + Core::ProxyType _watermark; + Storage _sessionStorage; + Storage _watermarkStorage; + Storage _palettedImageDataStorage; + std::list _clients; + Core::CriticalSection _clientLock; + }; + +} // namespace Plugin +} // namespace WPEFramework diff --git a/ContentProtection/ContentProtectionPlugin.json b/ContentProtection/ContentProtectionPlugin.json new file mode 100644 index 0000000000..68d9a12575 --- /dev/null +++ b/ContentProtection/ContentProtectionPlugin.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://raw.githubusercontent.com/rdkcentral/rdkservices/main/Tools/json_generator/schemas/plugin.schema.json", + "info": { + "title": "ContentProtection Plugin", + "callsign": "org.rdk.ContentProtection", + "locator": "libWPEFrameworkContentProtection.so", + "status": "production", + "description": "The `ContentProtection` plugin proxies content security capabilities of the existing DRM plugins" + } +} \ No newline at end of file diff --git a/ContentProtection/Module.cpp b/ContentProtection/Module.cpp new file mode 100644 index 0000000000..f3869fc7b9 --- /dev/null +++ b/ContentProtection/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 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. + */ + +#include "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/ContentProtection/Module.h b/ContentProtection/Module.h new file mode 100644 index 0000000000..1c1583f7ac --- /dev/null +++ b/ContentProtection/Module.h @@ -0,0 +1,33 @@ +/* + * 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 +#ifndef MODULE_NAME +#define MODULE_NAME Plugin_ContentProtection +#endif + +#include +#if (((THUNDER_VERSION_MAJOR >= 4) && (THUNDER_VERSION_MINOR == 4)) || ((THUNDER_VERSION >= 4) && !defined(THUNDER_VERSION_MINOR))) +#include +#else +#include +#endif + +#undef EXTERNAL +#define EXTERNAL