diff --git a/SwitchBoard/CMakeLists.txt b/SwitchBoard/CMakeLists.txt index bf465f9b7..7cd2a7d1b 100644 --- a/SwitchBoard/CMakeLists.txt +++ b/SwitchBoard/CMakeLists.txt @@ -37,16 +37,21 @@ endif() # This is a plugin so we need the Plugin configuration section. find_package(${NAMESPACE}Plugins REQUIRED) -find_package(CompileSettingsDebug REQUIRED) +find_package(CompileSettingsDebug CONFIG REQUIRED) add_library(${MODULE_NAME} SHARED SwitchBoard.cpp Module.cpp) +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) +target_compile_options (${MODULE_NAME} PRIVATE -Wno-psabi) + target_link_libraries(${MODULE_NAME} PRIVATE - ${NAMESPACE}Plugins::${NAMESPACE}Plugins - CompileSettingsDebug::CompileSettingsDebug) + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Plugins::${NAMESPACE}Plugins) # Library installation section string(TOLOWER ${NAMESPACE} STORAGENAME) diff --git a/SwitchBoard/SwitchBoard.cpp b/SwitchBoard/SwitchBoard.cpp index 9267351f6..80f402f69 100644 --- a/SwitchBoard/SwitchBoard.cpp +++ b/SwitchBoard/SwitchBoard.cpp @@ -98,9 +98,13 @@ POP_WARNING() if (_switches.size() == 0) { _service->Unregister(&_sink); + _service->Release(); _service = nullptr; _switches.clear(); } + else { + Exchange::JSwitchBoard::Register(*this, this); + } } Initialize(); @@ -115,6 +119,8 @@ POP_WARNING() ASSERT(_service == service); ASSERT(_switches.size() > 1); + Exchange::JSwitchBoard::Unregister(*this); + for (auto& index: _switches) { index.second.Unregister(&_sink); } @@ -134,111 +140,100 @@ POP_WARNING() return (string()); } - /* virtual */ void SwitchBoard::Inbound(Web::Request& request VARIABLE_IS_NOT_USED) - { - // No body required... - } - - /* virtual */ Core::ProxyType SwitchBoard::Process(const Web::Request& request) + Core::hresult SwitchBoard::Register(Exchange::ISwitchBoard::INotification* const notification) { - Core::ProxyType result(PluginHost::IFactories::Instance().Response()); - - TRACE(Trace::Information, (string(_T("Received request")))); + Core::hresult result = Core::ERROR_ALREADY_CONNECTED; - Core::TextSegmentIterator index(Core::TextFragment( - request.Path, - _skipURL, - request.Path.length() - _skipURL), - false, - '/'); - - // Always skip the first one, it is an empty part because we start with '/' if tehre are more parameters. - index.Next(); - - result->ErrorCode = Web::STATUS_BAD_REQUEST; - result->Message = string(_T("Dont understand what to do with this request")); + ASSERT(notification != nullptr); - // For now, whatever the URL, we will just, on a get, drop all info we have - if ( (request.Verb == Web::Request::HTTP_GET) && (index.Next() == false) ) { + _adminLock.Lock(); - Core::ProxyType > response(jsonBodySwitchFactory.Element()); + auto it = std::find(_notificationClients.begin(), _notificationClients.end(), notification); + ASSERT(it == _notificationClients.end()); - SwitchBoard::Iterator index(_switches); - Core::JSON::String element(true); + if (it == _notificationClients.end()) { + notification->AddRef(); - while (index.Next() == true) { - element = index.Callsign(); - response->Callsigns.Add(element); - } - - if (_defaultCallsign != nullptr) { - response->Default = _defaultCallsign->Callsign(); - } + _notificationClients.push_back(notification); - result->ErrorCode = Web::STATUS_OK; - result->Message = string(_T("OK")); - result->Body(Core::ProxyType(response)); - - } else if ( (request.Verb == Web::Request::HTTP_PUT) && (index.Next() == true) ) { - const string callSign(index.Current().Text()); - uint32_t error = Activate(callSign); - if (error != Core::ERROR_NONE) { - result->ErrorCode = Web::STATUS_NOT_MODIFIED; - result->Message = string(_T("Switch did not succeed. Error: ")) + - Core::NumberType(error).Text(); - } else { - result->ErrorCode = Web::STATUS_OK; - result->Message = string(_T("OK")); - } + result = Core::ERROR_NONE; } + _adminLock.Unlock(); + return (result); } - void SwitchBoard::Register(Exchange::ISwitchBoard::INotification* notification) + Core::hresult SwitchBoard::Unregister(const Exchange::ISwitchBoard::INotification* const notification) { + Core::hresult result = Core::ERROR_ALREADY_RELEASED; + ASSERT(notification != nullptr); _adminLock.Lock(); - std::list::iterator index(std::find(_notificationClients.begin(), _notificationClients.end(), notification)); + auto it = std::find(_notificationClients.cbegin(), _notificationClients.cend(), notification); + ASSERT(it != _notificationClients.end()); - ASSERT(index == _notificationClients.end()); + if (it != _notificationClients.end()) { + (*it)->Release(); - if (index == _notificationClients.end()) { - _notificationClients.push_back(notification); + _notificationClients.erase(it); + + result = Core::ERROR_NONE; } _adminLock.Unlock(); + + return (result); } - void SwitchBoard::Unregister(Exchange::ISwitchBoard::INotification* notification) + Core::hresult SwitchBoard::Switches(Exchange::ISwitchBoard::IStringIterator*& switches) const { - ASSERT(notification != nullptr); + std::vector list; - _adminLock.Lock(); + for (const auto& entry : _switches) { + list.push_back(entry.first); + } - std::list::iterator index(std::find(_notificationClients.begin(), _notificationClients.end(), notification)); + using Implementation = RPC::IteratorType; + switches = Core::ServiceType::Create(list); - ASSERT(index != _notificationClients.end()); + return (Core::ERROR_NONE); + } + + Core::hresult SwitchBoard::Default(string& callsign) const + { + Core::hresult result = Core::ERROR_NONE; - if (index != _notificationClients.end()) { - _notificationClients.erase(index); + if (_defaultCallsign != nullptr) { + callsign = _defaultCallsign->Callsign(); + } + else { + result = Core::ERROR_UNAVAILABLE; } - _adminLock.Unlock(); + return (result); } - /* virtual */ bool SwitchBoard::IsActive(const string& callsign) const + /* virtual */ Core::hresult SwitchBoard::IsActive(const string& callsign, bool& active) const { + Core::hresult result = Core::ERROR_NONE; std::map::const_iterator index(_switches.find(callsign)); - return (index != _switches.end() ? index->second.IsActive() : false); + if (index != _switches.end()) { + active = index->second.IsActive(); + } + else { + result = Core::ERROR_UNKNOWN_KEY; + } + + return (result); } - /* virtual */ uint32_t SwitchBoard::Activate(const string& callsign) + /* virtual */ Core::hresult SwitchBoard::Activate(const string& callsign) { - uint32_t result = (_state == INACTIVE ? Core::ERROR_ILLEGAL_STATE : Core::ERROR_INPROGRESS); + Core::hresult result = (_state == INACTIVE ? Core::ERROR_ILLEGAL_STATE : Core::ERROR_INPROGRESS); _adminLock.Lock(); @@ -283,9 +278,9 @@ POP_WARNING() return (result); } - /* virtual */ uint32_t SwitchBoard::Deactivate(const string& callsign) + /* virtual */ Core::hresult SwitchBoard::Deactivate(const string& callsign) { - uint32_t result = (_state == INACTIVE ? Core::ERROR_ILLEGAL_STATE : Core::ERROR_INPROGRESS); + Core::hresult result = (_state == INACTIVE ? Core::ERROR_ILLEGAL_STATE : Core::ERROR_INPROGRESS); _adminLock.Lock(); @@ -398,6 +393,8 @@ POP_WARNING() (*index)->Activated(callsign); index++; } + + Exchange::JSwitchBoard::Event::Activated(*this, callsign); } } diff --git a/SwitchBoard/SwitchBoard.h b/SwitchBoard/SwitchBoard.h index 5a80b6ce4..d5f13379d 100644 --- a/SwitchBoard/SwitchBoard.h +++ b/SwitchBoard/SwitchBoard.h @@ -21,11 +21,14 @@ #include "Module.h" #include +#include namespace Thunder { namespace Plugin { - class SwitchBoard : public PluginHost::IPlugin, PluginHost::IWeb, Exchange::ISwitchBoard { + class SwitchBoard : public Exchange::ISwitchBoard + , public PluginHost::IPlugin + , public PluginHost::JSONRPC { public: class Config : public Core::JSON::Container { @@ -396,31 +399,20 @@ namespace Plugin { // to this plugin. This Metadata can be used by the MetData plugin to publish this information to the ouside world. string Information() const override; - // IWeb methods - // ------------------------------------------------------------------------------------------------------- - // Whenever a request is received, it might carry some additional data in the body. This method allows - // the plugin to attach a deserializable data object (ref counted) to be loaded with any potential found - // in the body of the request. - void Inbound(Web::Request& request) override; + // ISwitchBoard interface + Core::hresult Register(Exchange::ISwitchBoard::INotification* const notification) override; + Core::hresult Unregister(const Exchange::ISwitchBoard::INotification* const notification) override; - // If everything is received correctly, the request is passed to us, on a thread from the thread pool, to - // do our thing and to return the result in the response object. Here the actual specific module work, - // based on a a request is handled. - Core::ProxyType Process(const Web::Request& request) override; + Core::hresult Switches(Exchange::ISwitchBoard::IStringIterator*& switches) const override; + Core::hresult Default(string& callsign) const override; - // IUnknown methods - // ------------------------------------------------------------------------------------------------------- - - // ISwitchBoard interface - void Register(Exchange::ISwitchBoard::INotification* notification) override; - void Unregister(Exchange::ISwitchBoard::INotification* notification) override; - bool IsActive(const string& callsign) const override; - uint32_t Activate(const string& callsign) override; - uint32_t Deactivate(const string& callsign) override; + Core::hresult IsActive(const string& callsign, bool& active) const override; + Core::hresult Activate(const string& callsign) override; + Core::hresult Deactivate(const string& callsign) override; BEGIN_INTERFACE_MAP(SwitchBoard) INTERFACE_ENTRY(PluginHost::IPlugin) - INTERFACE_ENTRY(PluginHost::IWeb) + INTERFACE_ENTRY(PluginHost::IDispatcher) INTERFACE_ENTRY(Exchange::ISwitchBoard) END_INTERFACE_MAP diff --git a/SwitchBoard/SwitchBoardPlugin.json b/SwitchBoard/SwitchBoardPlugin.json new file mode 100644 index 000000000..533d6d232 --- /dev/null +++ b/SwitchBoard/SwitchBoardPlugin.json @@ -0,0 +1,14 @@ +{ + "$schema": "plugin.schema.json", + "info": { + "title": "Switch Board Plugin", + "callsign": "SwitchBoard", + "locator": "libThunderSwitchBoard.so", + "status": "production", + "description": "This plugin is configured to manage a group of plugins, within which only one plugin can be active", + "version": "1.0" + }, + "interface": [ + { "$ref": "{cppinterfacedir}/ISwitchBoard.h" } + ] +} \ No newline at end of file diff --git a/SwitchBoard/doc/SwitchBoardPlugin.md b/SwitchBoard/doc/SwitchBoardPlugin.md new file mode 100644 index 000000000..cb297861f --- /dev/null +++ b/SwitchBoard/doc/SwitchBoardPlugin.md @@ -0,0 +1,398 @@ + + +# Switch Board Plugin + +**Version: 1.0** + +**Status: :black_circle::black_circle::black_circle:** + +SwitchBoard plugin for Thunder framework. + +### Table of Contents + +- [Introduction](#head_Introduction) +- [Description](#head_Description) +- [Configuration](#head_Configuration) +- [Interfaces](#head_Interfaces) +- [Methods](#head_Methods) +- [Properties](#head_Properties) +- [Notifications](#head_Notifications) + + +# Introduction + + +## Scope + +This document describes purpose and functionality of the SwitchBoard plugin. It includes detailed specification about its configuration, methods and properties as well as sent notifications. + + +## 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-%20Thunder.docx) | Thunder API Reference | + + +# Description + +This plugin is configured to manage a group of plugins, within which only one plugin can be active. + +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 | M/O | Description | +| :-------- | :-------- | :-------- | :-------- | +| callsign | string | mandatory | Plugin instance name (default: *SwitchBoard*) | +| classname | string | mandatory | Class name: *SwitchBoard* | +| locator | string | mandatory | Library name: *libThunderSwitchBoard.so* | +| startmode | string | mandatory | Determines in which state the plugin should be moved to at startup of the framework | + + +# Interfaces + +This plugin implements the following interfaces: + +- ISwitchBoard ([ISwitchBoard.h](https://github.com/rdkcentral/ThunderInterfaces/blob/master/interfaces/ISwitchBoard.h)) (version 1.0.0) (compliant format) +> This interface uses legacy ```lowercase``` naming convention. With the next major release the naming convention will change to ```camelCase```. + + +# Methods + +The following methods are provided by the SwitchBoard plugin: + +SwitchBoard interface methods: + +| Method | Description | +| :-------- | :-------- | +| [activate](#method_activate) | Activate a plugin by its callsign | +| [deactivate](#method_deactivate) | Deactivate a plugin by its callsign | + + +## *activate [method](#head_Methods)* + +Activate a plugin by its callsign. + +### Parameters + +| Name | Type | M/O | Description | +| :-------- | :-------- | :-------- | :-------- | +| params | object | mandatory | *...* | +| params.callsign | string | mandatory | The callsign of the plugin to activate | + +### Result + +| Name | Type | M/O | Description | +| :-------- | :-------- | :-------- | :-------- | +| result | null | mandatory | Always null | + +### Errors + +| Message | Description | +| :-------- | :-------- | +| ```ERROR_ILLEGAL_STATE``` | Not in a state to allow activation | +| ```ERROR_INPROGRESS``` | Currently processing another request | +| ```ERROR_UNAVAILABLE``` | The plugin is not available for activation | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "SwitchBoard.1.activate", + "params": { + "callsign": "WebServer" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "result": null +} +``` + + +## *deactivate [method](#head_Methods)* + +Deactivate a plugin by its callsign. + +### Parameters + +| Name | Type | M/O | Description | +| :-------- | :-------- | :-------- | :-------- | +| params | object | mandatory | *...* | +| params.callsign | string | mandatory | The callsign of the plugin to deactivate | + +### Result + +| Name | Type | M/O | Description | +| :-------- | :-------- | :-------- | :-------- | +| result | null | mandatory | Always null | + +### Errors + +| Message | Description | +| :-------- | :-------- | +| ```ERROR_ILLEGAL_STATE``` | Not in a state to allow deactivation | +| ```ERROR_INPROGRESS``` | Currently processing another request | +| ```ERROR_UNAVAILABLE``` | The plugin is not available for deactivation | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "SwitchBoard.1.deactivate", + "params": { + "callsign": "MessageControl" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "result": null +} +``` + + +# Properties + +The following properties are provided by the SwitchBoard plugin: + +SwitchBoard interface properties: + +| Property | R/W | Description | +| :-------- | :-------- | :-------- | +| [switches](#property_switches) | read-only | Get the list of switches available in the switchboard | +| [default](#property_default) | read-only | Get a callsign of the default switch | +| [isactive](#property_isactive) | read-only | Check if a plugin is active | + + +## *switches [property](#head_Properties)* + +Provides access to the get the list of switches available in the switchboard. + +> This property is **read-only**. + +### Value + +| Name | Type | M/O | Description | +| :-------- | :-------- | :-------- | :-------- | +| (property) | array | mandatory | An iterator to the list of switches | +| (property)[#] | string | mandatory | *...* | + +### Example + +#### Get Request + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "SwitchBoard.1.switches" +} +``` + +#### Get Response + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "result": [ + "..." + ] +} +``` + + +## *default [property](#head_Properties)* + +Provides access to the get a callsign of the default switch. + +> This property is **read-only**. + +### Value + +| Name | Type | M/O | Description | +| :-------- | :-------- | :-------- | :-------- | +| (property) | string | mandatory | The callsign of the default switch | + +### Errors + +| Message | Description | +| :-------- | :-------- | +| ```ERROR_UNAVAILABLE``` | No default switch available | + +### Example + +#### Get Request + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "SwitchBoard.1.default" +} +``` + +#### Get Response + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "result": "WebKitBrowser" +} +``` + + +## *isactive [property](#head_Properties)* + +Provides access to the check if a plugin is active. + +> This property is **read-only**. + +> The *callsign* parameter shall be passed as the index to the property, i.e. ``isactive@``. + +### Index + +| Name | Type | M/O | Description | +| :-------- | :-------- | :-------- | :-------- | +| callsign | string | mandatory | The callsign of the plugin to check | + +### Value + +| Name | Type | M/O | Description | +| :-------- | :-------- | :-------- | :-------- | +| (property) | boolean | mandatory | The state of the plugin, true if it is active, false if not | + +### Errors + +| Message | Description | +| :-------- | :-------- | +| ```ERROR_UNKNOWN_KEY``` | Callsign not found | + +### Example + +#### Get Request + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "SwitchBoard.1.isactive@DeviceInfo" +} +``` + +#### Get Response + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "result": 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 SwitchBoard plugin: + +SwitchBoard interface events: + +| Notification | Description | +| :-------- | :-------- | +| [activated](#notification_activated) | Signal which callsign has been switched on | + + +## *activated [notification](#head_Notifications)* + +Signal which callsign has been switched on. + +### Notification Parameters + +| Name | Type | M/O | Description | +| :-------- | :-------- | :-------- | :-------- | +| params | object | mandatory | *...* | +| params.callsign | string | mandatory | The callsign of the plugin that has been activated | + +### Example + +#### Registration + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "SwitchBoard.1.register", + "params": { + "event": "activated", + "id": "myid" + } +} +``` + +#### Notification + +```json +{ + "jsonrpc": "2.0", + "method": "myid.activated", + "params": { + "callsign": "WebKitBrowser" + } +} +``` + +> The *client ID* parameter is passed within the notification designator, i.e. ``.activated``. +