diff --git a/WebBridge/CMakeLists.txt b/WebBridge/CMakeLists.txt index d2dd1db0e3..755a142d16 100644 --- a/WebBridge/CMakeLists.txt +++ b/WebBridge/CMakeLists.txt @@ -15,8 +15,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -set(PLUGIN_NAME WebBridge) -set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) +project(WebBridge) + +cmake_minimum_required(VERSION 3.3) + +find_package(WPEFramework) + +project_version(1.0.0) + +set(MODULE_NAME ${NAMESPACE}${PROJECT_NAME}) + +message("Setup ${MODULE_NAME} v${PROJECT_VERSION}") set(PLUGIN_WEBBRIDGE_STARTUPORDER "" CACHE STRING "To configure startup order of WebBridge plugin") @@ -39,4 +48,4 @@ target_link_libraries(${MODULE_NAME} install(TARGETS ${MODULE_NAME} DESTINATION lib/${STORAGE_DIRECTORY}/plugins) -write_config(${PLUGIN_NAME}) +write_config() diff --git a/WebBridge/WebBridge.cpp b/WebBridge/WebBridge.cpp index 7166be425d..14f04835c1 100644 --- a/WebBridge/WebBridge.cpp +++ b/WebBridge/WebBridge.cpp @@ -25,324 +25,522 @@ 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(WebBridge, API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH); - - class EXTERNAL Registration : public Core::JSON::Container { - private: - Registration(const Registration&) = delete; - Registration& operator=(const Registration&) = delete; - - public: - Registration() - : Core::JSON::Container() - , Event() - , Callsign() - { - Add(_T("event"), &Event); - Add(_T("id"), &Callsign); +ENUM_CONVERSION_BEGIN(Plugin::WebBridge::context) + + { Plugin::WebBridge::context::NONE, _TXT("none") }, + { Plugin::WebBridge::context::ADDED, _TXT("added") }, + { Plugin::WebBridge::context::WRAPPED, _TXT("wrapped") }, + +ENUM_CONVERSION_END(Plugin::WebBridge::context); + + 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 + {} + ); } - ~Registration() + + SERVICE_REGISTRATION(WebBridge, API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH) + + class Registration : public Core::JSON::Container { + public: + Registration(Registration&&) = delete; + Registration(const Registration&) = delete; + Registration& operator=(const Registration&) = delete; + + Registration() + : Core::JSON::Container() + , Event() + , Callsign() + { + Add(_T("event"), &Event); + Add(_T("id"), &Callsign); + } + ~Registration() override = default; + + public: + Core::JSON::String Event; + Core::JSON::String Callsign; + }; + class Message : public Core::JSONRPC::Message { + public: + class CallContext : public Core::JSON::Container { + public: + CallContext(CallContext&&) = delete; + CallContext(const CallContext&) = delete; + CallContext& operator= (const CallContext&) = delete; + + CallContext() + : Core::JSON::Container() + , Callsign() + , ChannelId(0) + , Token() + , OriginalId(0) { + Add(_T("callsign"), &Callsign); + Add(_T("channel"), &ChannelId); + Add(_T("token"), &Token); + Add(_T("id"), &OriginalId); + } + ~CallContext() override = default; + + void Clear() { + Callsign.Clear(); + ChannelId.Clear(); + Token.Clear(); + OriginalId.Clear(); + } + + public: + Core::JSON::String Callsign; + Core::JSON::DecUInt32 ChannelId; + Core::JSON::String Token; + Core::JSON::DecUInt32 OriginalId; + }; + + public: + Message(Message&&) = delete; + Message(const Message&) = delete; + Message& operator= (const Message&) = delete; + + Message() + : Core::JSONRPC::Message() { + Add(_T("context"), &Context); + Add(_T("request"), &Request); + Add(_T("response"), &Response); + + Core::JSONRPC::Message::JSONRPC.Clear(); + Request.JSONRPC.Clear(); + Response.JSONRPC.Clear(); + } + ~Message() override = default; + + void Clear() + { + Request.Id.Clear(); + Request.Designator.Clear(); + Request.Parameters.Clear(); + Request.Result.Clear(); + Request.Error.Clear(); + Request.JSONRPC.Clear(); + + Response.Id.Clear(); + Response.Designator.Clear(); + Response.Parameters.Clear(); + Response.Result.Clear(); + Response.Error.Clear(); + Response.JSONRPC.Clear(); + + Context.Clear(); + + Core::JSONRPC::Message::Id.Clear(); + Core::JSONRPC::Message::Designator.Clear(); + Core::JSONRPC::Message::Parameters.Clear(); + Core::JSONRPC::Message::Result.Clear(); + Core::JSONRPC::Message::Error.Clear(); + Core::JSONRPC::Message::JSONRPC.Clear(); + } + + public: + CallContext Context; + Core::JSONRPC::Message Request; + Core::JSONRPC::Message Response; + }; + + static Core::ProxyPoolType g_BridgeMessages(8); + + // ------------------------------------------------------------------------------------------------------- + // IPluginExtended methods + // ------------------------------------------------------------------------------------------------------- + const string WebBridge::Initialize(PluginHost::IShell* service) /* override */ { - } + ASSERT(_service == nullptr); + ASSERT(service != nullptr); - public: - Core::JSON::String Event; - Core::JSON::String Callsign; - }; - - // ------------------------------------------------------------------------------------------------------- - // IPluginExtended methods - // ------------------------------------------------------------------------------------------------------- - const string WebBridge::Initialize(PluginHost::IShell* service) /* override */ - { - ASSERT(_service == nullptr); - ASSERT(service != nullptr); - - string message; - - Config config; - config.FromString(service->ConfigLine()); - _skipURL = static_cast(service->WebPrefix().length()); - _callsign = service->Callsign(); - _service = service; - _timeOut = (config.TimeOut.Value() * Core::Time::TicksPerMillisecond); - - // On success return empty, to indicate there is no error text. - return (message); - } - - void WebBridge::Deinitialize(PluginHost::IShell* service) /* override */ - { - ASSERT(_service == service); - - _service = nullptr; - } - - string WebBridge::Information() const /* override */ - { - // No additional info to report. - return (string()); - } - - bool WebBridge::Attach(PluginHost::Channel& channel) /* override */ { - bool assigned = false; - - // The expectation is that the JavaScript service opens up a connection to us, so we can forward the - // incomming requests, to be handled by the Service. - if ((channel.Protocol() == _T("json")) && (_javascriptService == 0)) { - _javascriptService = channel.Id(); - assigned = true; - } - return(assigned); - } - - void WebBridge::Detach(PluginHost::Channel& channel) /* override */ { - // Hopefull this does not happen as than we are loosing the actual service :-) We could do proper error handling - // if this happens :-) - _javascriptService = 0; - } - - // ------------------------------------------------------------------------------------------------------- - // IDispatcher methods - // ------------------------------------------------------------------------------------------------------- - Core::ProxyType WebBridge::Invoke(const string& token, const uint32_t channelId, const Core::JSONRPC::Message& inbound) /* override */ - { - string method; - Registration info; - - Core::ProxyType message(PluginHost::IFactories::Instance().JSONRPC()); - string designator(inbound.Designator.Value()); - - if (inbound.Id.IsSet() == true) { - message->JSONRPC = Core::JSONRPC::Message::DefaultVersion; - message->Id = inbound.Id.Value(); - } + string message; - switch (Destination(designator, method)) { - case state::STATE_INCORRECT_HANDLER: - message->Error.SetError(Core::ERROR_INVALID_DESIGNATOR); - message->Error.Text = _T("Destined invoke failed."); - break; - case state::STATE_INCORRECT_VERSION: - message->Error.SetError(Core::ERROR_INVALID_SIGNATURE); - message->Error.Text = _T("Requested version is not supported."); - break; - case state::STATE_UNKNOWN_METHOD: - message->Error.SetError(Core::ERROR_UNKNOWN_KEY); - message->Error.Text = _T("Unknown method."); - break; - case state::STATE_REGISTRATION: - info.FromString(inbound.Parameters.Value()); - Subscribe(channelId, info.Event.Value(), info.Callsign.Value(), *message); - break; - case state::STATE_UNREGISTRATION: - info.FromString(inbound.Parameters.Value()); - Unsubscribe(channelId, info.Event.Value(), info.Callsign.Value(), *message); - break; - case state::STATE_EXISTS: - message->Result = Core::NumberType(Core::ERROR_UNKNOWN_KEY).Text(); - break; - case state::STATE_NONE_EXISTING: - message->Result = Core::NumberType(Core::ERROR_NONE).Text(); - break; - case state::STATE_CUSTOM: - // Let's on behalf of the request forward it and update - uint32_t newId = Core::InterlockedIncrement(_sequenceId); - Core::Time waitTill = Core::Time::Now() + _timeOut; - - _pendingRequests.emplace(std::piecewise_construct, - std::forward_as_tuple(newId), - std::forward_as_tuple(channelId, message->Id.Value(), waitTill)); - - message->Id = newId; - message->Parameters = inbound.Parameters; - message->Designator = inbound.Designator; - - TRACE(Trace::Information, (_T("Request: [%d] from [%d], method: [%s]"), message->Id.Value(), channelId, method.c_str())); - - _service->Submit(_javascriptService, Core::ProxyType(message)); - - // Wait for ID to return, we can not report anything back yet... - message.Release(); - - if (_timeOut != 0) { - _cleaner.Schedule(waitTill); + Config config; + config.FromString(service->ConfigLine()); + _skipURL = static_cast(service->WebPrefix().length()); + _callsign = service->Callsign(); + _service = service; + _service->AddRef(); + + _mode = config.Context.Value(); + _timeOut = (config.TimeOut.Value() * Core::Time::TicksPerMillisecond); + +#ifndef USE_THUNDER_R4 + if (message.length() != 0) { + Deinitialize(service); } +#endif - break; + // On success return empty, to indicate there is no error text. + return (message); } - return message; - } - - void WebBridge::Activate(PluginHost::IShell* /* service */) /* override */ { - // We did what we needed to do in the Intialize. - } + void WebBridge::Deinitialize(PluginHost::IShell* service) /* override */ + { + if (_service != nullptr) { + ASSERT(_service == service); - void WebBridge::Deactivate() /* override */ { - // We did what we needed to do in the Deintialize. - } + _service->Release(); + _service = nullptr; + } + } - // ------------------------------------------------------------------------------------------------------- - // IWebSocket methods - // ------------------------------------------------------------------------------------------------------- - Core::ProxyType WebBridge::Inbound(const string& /* identifier */) /* override */{ - // There is a message coming in over the JSON WebSocket path!, give it storage space.. - return (Core::ProxyType(PluginHost::IFactories::Instance().JSONRPC())); - } + string WebBridge::Information() const /* override */ + { + // No additional info to report. + return (string()); + } - Core::ProxyType WebBridge::Inbound(const uint32_t ID, const Core::ProxyType& element) /* override */ { + bool WebBridge::Attach(PluginHost::Channel& channel) /* override */ { + bool assigned = false; - Core::ProxyType message(element); + // The expectation is that the JavaScript service opens up a connection to us, so we can forward the + // incomming requests, to be handled by the Service. + if (_javascriptService == 0) { + Web::ProtocolsArray protocols = channel.Protocols(); + if (std::find(protocols.begin(), protocols.end(), string(_T("json"))) != protocols.end()) { + _javascriptService = channel.Id(); + assigned = true; + } + } + return(assigned); + } - ASSERT(message.IsValid() == true); + void WebBridge::Detach(PluginHost::Channel& channel) /* override */ { + // Hopefull this does not happen as than we are loosing the actual service :-) We could do proper error handling + // if this happens :-) + _javascriptService = 0; + } - if (message.IsValid()) { + // ------------------------------------------------------------------------------------------------------- + // IDispatcher methods + // ------------------------------------------------------------------------------------------------------- +#ifndef USE_THUNDER_R4 + Core::ProxyType WebBridge::Invoke(const string& token, const uint32_t channelId, const Core::JSONRPC::Message& inbound) /* override */ + { + string method; + Registration info; - if (message->Id.IsSet() == false) { + Core::ProxyType message(PluginHost::IFactories::Instance().JSONRPC()); + string designator(inbound.Designator.Value()); - string eventName(message->Method()); + if (inbound.Id.IsSet() == true) { + message->JSONRPC = Core::JSONRPC::Message::DefaultVersion; + message->Id = inbound.Id.Value(); + } - // Check for control messages between server and us.. - if (InternalMessage(message) == false) { + switch (Destination(designator, method)) { + case state::STATE_INCORRECT_HANDLER: + message->Error.SetError(Core::ERROR_INVALID_DESIGNATOR); + message->Error.Text = _T("Destined invoke failed."); + break; + case state::STATE_INCORRECT_VERSION: + message->Error.SetError(Core::ERROR_INVALID_SIGNATURE); + message->Error.Text = _T("Requested version is not supported."); + break; + case state::STATE_UNKNOWN_METHOD: + message->Error.SetError(Core::ERROR_UNKNOWN_KEY); + message->Error.Text = _T("Unknown method."); + break; + case state::STATE_REGISTRATION: + info.FromString(inbound.Parameters.Value()); + Subscribe(channelId, info.Event.Value(), info.Callsign.Value(), *message); + break; + case state::STATE_UNREGISTRATION: + info.FromString(inbound.Parameters.Value()); + Unsubscribe(channelId, info.Event.Value(), info.Callsign.Value(), *message); + break; + case state::STATE_EXISTS: + message->Result = Core::NumberType(Core::ERROR_UNKNOWN_KEY).Text(); + break; + case state::STATE_NONE_EXISTING: + message->Result = Core::NumberType(Core::ERROR_NONE).Text(); + break; + case state::STATE_CUSTOM: + // Let's on behalf of the request forward it and update + uint32_t newId = Core::InterlockedIncrement(_sequenceId); + Core::Time waitTill = Core::Time::Now() + _timeOut; + + _pendingRequests.emplace(std::piecewise_construct, + std::forward_as_tuple(newId), + std::forward_as_tuple(channelId, message->Id.Value(), waitTill)); + + message->Id = newId; + message->Parameters = inbound.Parameters; + message->Designator = inbound.Designator; + + TRACE(Trace::Information, (_T("Request: [%d] from [%d], method: [%s]"), message->Id.Value(), channelId, method.c_str())); + + _service->Submit(_javascriptService, Core::ProxyType(message)); + + // Wait for ID to return, we can not report anything back yet... + message.Release(); + + if (_timeOut != 0) { + _cleaner.Schedule(waitTill); + } - // This is an event, we need event handling.. - _adminLock.Lock(); + break; + } - ObserverMap::iterator index = _observers.find(eventName); + return message; + } - if (index != _observers.end()) { - for (const Observer& entry : index->second) { - Core::ProxyType outbound(PluginHost::IFactories::Instance().JSONRPC()); - outbound->Designator = (entry.Designator().empty() == false ? entry.Designator() + '.' + eventName : eventName); - outbound->Parameters = message->Parameters.Value(); + void WebBridge::Activate(PluginHost::IShell* /* service */) /* override */ { + // We did what we needed to do in the Intialize. + } - _service->Submit(entry.Id(), Core::ProxyType(outbound)); - } - } + void WebBridge::Deactivate() /* override */ { + // We did what we needed to do in the Deintialize. + } +#else + Core::hresult + WebBridge::Invoke( + IDispatcher::ICallback* callback, + const uint32_t channelId, + const uint32_t id, + const string& token, + const string& method, + const string& parameters, + string& response) /* override */ + { + uint32_t result(Core::ERROR_BAD_REQUEST); + Core::JSONRPC::Handler* handler(PluginHost::JSONRPC::Handler(method)); + string realMethod(Core::JSONRPC::Message::Method(method)); - _adminLock.Unlock(); - } + if (handler == nullptr) { + result = Core::ERROR_INVALID_RANGE; + } + else if (realMethod == _T("exists")) { + result = Core::ERROR_NONE; + if (handler->Exists(realMethod) == Core::ERROR_NONE) { + response = _T("1"); + } + else { + response = _T("0"); + } } - else { - uint32_t requestId, channelId = 0; - - // This is the response to an invoked method, Let's see who should get this repsonse :-) - _adminLock.Lock(); - PendingMap::iterator index = _pendingRequests.find(message->Id.Value()); - if (index != _pendingRequests.end()) { - channelId = index->second.ChannelId(); - requestId = index->second.SequenceId(); - _pendingRequests.erase(index); + else if (handler->Exists(realMethod) == Core::ERROR_NONE) { + + // Let's on behalf of the request forward it and update + string messageToSend(parameters); + Core::ProxyType message(g_BridgeMessages.Element()); + uint32_t newId = Core::_InterlockedIncrement(_sequenceId); + Core::Time waitTill = Core::Time::Now() + _timeOut; + + _pendingRequests.emplace(std::piecewise_construct, + std::forward_as_tuple(newId), + std::forward_as_tuple(callback, channelId, id, waitTill)); + + switch (_mode) { + case WebBridge::context::ADDED: { + message->Context.ChannelId = channelId; + message->Context.OriginalId = id; + message->Context.Token = token; + message->Context.Callsign = _callsign; + } + case WebBridge::context::NONE: { + break; } - _adminLock.Unlock(); + case WebBridge::context::WRAPPED: { + Message wrapper; + + wrapper.Context.ChannelId = channelId; + wrapper.Context.OriginalId = id; + wrapper.Context.Token = token; + wrapper.Parameters = parameters; + wrapper.ToString(messageToSend); + break; + } + } + + message->Id = newId; + message->Parameters = messageToSend; + message->Designator = method; + + TRACE(Trace::Information, (_T("Request: [%d] from [%d], method: [%s]"), newId, channelId, method.c_str())); - if (channelId != 0) { - TRACE(Trace::Information, (_T("Response: [%d] to [%d]"), requestId, channelId)); + _service->Submit(_javascriptService, Core::ProxyType(message)); - // Oke, there is someone waiting for a response! - message->Id = requestId; + // Wait for ID to return, we can not report anything back yet... + message.Release(); + + if (_timeOut != 0) { #ifndef USE_THUNDER_R4 - _service->Submit(channelId, Core::proxy_cast(message)); + _cleaner.Schedule(waitTill); #else - _service->Submit(channelId, Core::ProxyType(message)); -#endif /* USE_THUNDER_R4 */ + _cleaner.Reschedule(waitTill); +#endif } + + result = ~0; // No resposne to report yet.... } + + return (result); } + Core::hresult WebBridge::Revoke(ICallback* callback) /* override*/ { + // Remove the interface from the pendings.. + _adminLock.Lock(); - // We will never report anything back here :-) - return (Core::ProxyType()); - } - - // ------------------------------------------------------------------------------------------------------- - // Private methods - // ------------------------------------------------------------------------------------------------------- - void WebBridge::Cleanup() { - // Lets see if there are still any pending request we should report Missing In Action :-) - Core::Time now (Core::Time::Now()); - Core::Time nextSlot; - - _adminLock.Lock(); - PendingMap::iterator index(_pendingRequests.begin()); - while (index != _pendingRequests.end()) { - if (now >= index->second.Issued()) { - // Send and Error to the requester.. - Core::ProxyType message(PluginHost::IFactories::Instance().JSONRPC()); - message->Error.SetError(Core::ERROR_TIMEDOUT); - message->Error.Text = _T("There is no response form the server within time!!!"); - message->Id = index->second.SequenceId(); - - TRACE(Trace::Warning, (_T("Got a timeout on channelId [%d] for request [%d]"), index->second.ChannelId(), message->Id.Value())); - - _service->Submit(index->second.ChannelId(), Core::ProxyType(message)); - index = _pendingRequests.erase(index); - } - else { - if ((nextSlot.IsValid() == false) || (nextSlot > index->second.Issued())) { - nextSlot = index->second.Issued(); + PendingMap::iterator index = _pendingRequests.begin(); + + while (index != _pendingRequests.end()) { + if (index->second != callback) { + index++; + } + else { + index = _pendingRequests.erase(index); } - index++; } + + _adminLock.Lock(); + + return (PluginHost::JSONRPC::Revoke(callback)); } - _adminLock.Unlock(); - if (nextSlot.IsValid()) { - _cleaner.Schedule(nextSlot); +#endif + // ------------------------------------------------------------------------------------------------------- + // IWebSocket methods + // ------------------------------------------------------------------------------------------------------- + Core::ProxyType WebBridge::Inbound(const string& /* identifier */) /* override */ { + // There is a message coming in over the JSON WebSocket path!, give it storage space.. + return (Core::ProxyType(PluginHost::IFactories::Instance().JSONRPC())); } - } - bool WebBridge::InternalMessage(const Core::ProxyType& message) { - bool result = false; + Core::ProxyType WebBridge::Inbound(const uint32_t ID, const Core::ProxyType& element) /* override */ { + + Core::ProxyType message(element); + + ASSERT(message.IsValid() == true); + + if (message.IsValid()) { + + if (message->Id.IsSet() == false) { + + string eventName(message->Method()); + + // Check for control messages between server and us.. + if (InternalMessage(message) == false) { + + // This is an event, we need event handling.. + PluginHost::JSONRPC::Event(eventName, message->Parameters.Value()); + } + } + else { + // This is the response to an invoked method, Let's see who should get this repsonse :-) + _adminLock.Lock(); + PendingMap::iterator index = _pendingRequests.find(message->Id.Value()); + if (index != _pendingRequests.end()) { + uint32_t requestId, channelId; + IDispatcher::ICallback* callback; + + channelId = index->second.ChannelId(); + requestId = index->second.SequenceId(); + callback = index->second.Callback(); + + ASSERT(callback != nullptr); - string eventName(message->Method()); + TRACE(Trace::Information, (_T("Response: [%d] to [%d]"), requestId, channelId)); - if (eventName == "registerjsonrpcmethods") { - result = true; - Core::JSON::ArrayType parameter; - parameter.FromString(message->Parameters.Value()); - Core::JSON::ArrayType::Iterator index(parameter.Elements()); +#ifndef USE_THUNDER_R4 - _supportedVersions.clear(); + // Oke, there is someone waiting for a response! + message->Id = requestId; + _service->Submit(channelId, Core::proxy_cast(message)); +#else + if (callback != nullptr) { + // Oke, there is someone waiting for a response! + callback->Response(channelId, requestId, message->Result.Value()); + callback->Release(); + } - while (index.Next() == true) { - string entry = index.Current().Value(); - uint8_t version = Core::JSONRPC::Message::Version(entry); - string method = Core::JSONRPC::Message::Method(entry); - VersionMap::iterator placement = _supportedVersions.find(version); +#endif /* USE_THUNDER_R4 */ + _pendingRequests.erase(index); + } + _adminLock.Unlock(); + } + } + + // We will never report anything back here :-) + return (Core::ProxyType()); + } - if (placement == _supportedVersions.end()) { - auto newEntry = _supportedVersions.emplace(std::piecewise_construct, - std::forward_as_tuple(version), - std::forward_as_tuple()); + // ------------------------------------------------------------------------------------------------------- + // Private methods + // ------------------------------------------------------------------------------------------------------- + void WebBridge::Dispatch() { + // Lets see if there are still any pending request we should report Missing In Action :-) + Core::Time now(Core::Time::Now()); + Core::Time nextSlot; + + _adminLock.Lock(); + PendingMap::iterator index(_pendingRequests.begin()); + while (index != _pendingRequests.end()) { + if (now >= index->second.Issued()) { + // Send and Error to the requester.. + IDispatcher::ICallback* callback = index->second.Callback(); + + ASSERT(callback != nullptr); + + if (callback != nullptr) { + callback->Error(index->second.ChannelId(), index->second.SequenceId(), Core::ERROR_TIMEDOUT, _T("There is no response form the server within time!!!")); + callback->Release(); + } + + TRACE(Trace::Warning, (_T("Got a timeout on channelId [%d] for request [%d]"), index->second.ChannelId(), index->second.SequenceId())); - newEntry.first->second.push_back(method); + index = _pendingRequests.erase(index); } - else if (std::find(placement->second.begin(), placement->second.end(), method) == placement->second.end()) { - // Check if this label does not already exist - placement->second.push_back(method); + else { + if ((nextSlot.IsValid() == false) || (nextSlot > index->second.Issued())) { + nextSlot = index->second.Issued(); + } + index++; } } + _adminLock.Unlock(); + + if (nextSlot.IsValid()) { +#ifndef USE_THUNDER_R4 + _cleaner.Schedule(nextSlot); +#else + _cleaner.Reschedule(nextSlot); +#endif + } } - return (result); - } + bool WebBridge::InternalMessage(const Core::ProxyType& message) { + bool result = false; + + string eventName(message->Method()); + + if (eventName == "registerjsonrpcmethods") { + result = true; + Core::JSON::ArrayType parameter; + parameter.FromString(message->Parameters.Value()); + Core::JSON::ArrayType::Iterator index(parameter.Elements()); + + while (index.Next() == true) { + string entry = index.Current().Value(); + + PluginHost::JSONRPC::RegisterMethod(Core::JSONRPC::Message::Version(entry), Core::JSONRPC::Message::Method(entry)); + } + } + + return (result); + } -} // namespace Plugin + } // namespace Plugin } // namespace WPEFramework diff --git a/WebBridge/WebBridge.h b/WebBridge/WebBridge.h index 51a5fa15ec..9af52ff772 100644 --- a/WebBridge/WebBridge.h +++ b/WebBridge/WebBridge.h @@ -22,328 +22,188 @@ #include "Module.h" namespace WPEFramework { -namespace Plugin { + namespace Plugin { - class WebBridge : - public PluginHost::IPluginExtended, - public PluginHost::IDispatcher, - public PluginHost::IWebSocket - { - private: - enum class state { - STATE_INCORRECT_HANDLER, - STATE_INCORRECT_VERSION, - STATE_UNKNOWN_METHOD, - STATE_REGISTRATION, - STATE_UNREGISTRATION, - STATE_EXISTS, - STATE_NONE_EXISTING, - STATE_CUSTOM - }; - class Observer { - public: - Observer(const Observer&) = delete; - Observer& operator=(const Observer&) = delete; - - Observer(const uint32_t id, const string& designator) - : _id(id) - , _designator(designator) - { - } - ~Observer() = default; - - public: - bool operator==(const Observer& rhs) const - { - return ((rhs._id == _id) && (rhs._designator == _designator)); - } - bool operator!=(const Observer& rhs) const - { - return (!operator==(rhs)); - } - - uint32_t Id() const - { - return (_id); - } - const string& Designator() const - { - return (_designator); - } - - private: - uint32_t _id; - string _designator; - }; - class Request { - public: - Request() = delete; - Request(const Request&) = delete; - Request& operator=(const Request&) = delete; - - Request(const uint32_t channelId, const uint32_t sequenceId, const Core::Time& timeOut) - : _channelId(channelId) - , _sequenceId(sequenceId) - , _issued(timeOut) { - } - ~Request() = default; - - public: - uint32_t ChannelId() const { - return (_channelId); - } - uint32_t SequenceId() const { - return (_sequenceId); - } - const Core::Time& Issued() const { - return (_issued); - } - - private: - uint32_t _channelId; - uint32_t _sequenceId; - Core::Time _issued; - }; - class Cleaner { + class WebBridge : + public PluginHost::IPluginExtended, + public PluginHost::IDispatcher, + public PluginHost::IWebSocket, + { private: - using BaseClass = Core::IWorkerPool::JobType; + enum class state { + STATE_INCORRECT_HANDLER, + STATE_INCORRECT_VERSION, + STATE_UNKNOWN_METHOD, + STATE_REGISTRATION, + STATE_UNREGISTRATION, + STATE_EXISTS, + STATE_NONE_EXISTING, + STATE_CUSTOM + }; + class Request { + public: + Request() = delete; + Request(Request&&) = delete; + Request(const Request&) = delete; + Request& operator=(const Request&) = delete; + + Request(IDispatcher::ICallback* callback, const uint32_t channelId, const uint32_t sequenceId, const Core::Time& timeOut) + : _callback(callback) + , _channelId(channelId) + , _sequenceId(sequenceId) + , _issued(timeOut) { + _callback->AddRef(); + } + ~Request() { + ASSERT(_callback != nullptr); + _callback->Release(); + _callback = nullptr; + } + bool operator== (const IDispatcher::ICallback* callback) const { + return (_callback == callback); + } + bool operator!= (const IDispatcher::ICallback* callback) const { + return (!operator==(callback)); + } - public: - Cleaner(const Cleaner&) = delete; - Cleaner& operator=(const Cleaner&) = delete; + public: + IDispatcher::ICallback* Callback() { + _callback->AddRef(); + return (_callback); + } + uint32_t ChannelId() const { + return (_channelId); + } + uint32_t SequenceId() const { + return (_sequenceId); + } + const Core::Time& Issued() const { + return (_issued); + } - Cleaner(WebBridge& parent) : _parent(parent) { - } - ~Cleaner() = default; + private: + IDispatcher::ICallback* _callback; + uint32_t _channelId; + uint32_t _sequenceId; + Core::Time _issued; + }; + using PendingMap = std::unordered_map; public: - void Dispatch() { - _parent.Cleanup(); - } - - private: - WebBridge& _parent; - }; + enum context : uint8_t { + NONE, + ADDED, + WRAPPED + }; + class Config : public Core::JSON::Container { + public: + Config(Config&&) = delete; + Config(const Config&) = delete; + Config& operator= (const Config&) = delete; + + Config() + : Core::JSON::Container() + , TimeOut(3000) + , Context(context::NONE) + { + Add(_T("timeout"), &TimeOut); + Add(_T("context"), &Context); + } + ~Config() override = default; - using ObserverList = std::list; - using ObserverMap = std::map; - using MethodList = std::vector; - using VersionMap = std::map; - using PendingMap = std::map; + public: + Core::JSON::DecUInt16 TimeOut; + Core::JSON::EnumType Context; + }; - public: - class Config : public Core::JSON::Container { public: - Config() - : Core::JSON::Container() - , TimeOut(3000) + WebBridge(WebBridge&&) = delete; + WebBridge(const WebBridge&) = delete; + WebBridge& operator=(const WebBridge&) = delete; + + #ifdef __WINDOWS__ + #pragma warning(disable: 4355) + #endif + WebBridge() + : _adminLock() + , _skipURL(0) + , _mode(context::NONE) + , _service(nullptr) + , _callsign() + , _pendingRequests() + , _javascriptService(0) + , _sequenceId(1) + , _timeOut(0) + , _cleaner(*this) { - Add(_T("timeout"), &TimeOut); - } - ~Config() override = default; + } + #ifdef __WINDOWS__ + #pragma warning(default: 4355) + #endif + ~WebBridge() override = default; + + BEGIN_INTERFACE_MAP(WebBridge) + INTERFACE_ENTRY(PluginHost::IPlugin) + INTERFACE_ENTRY(PluginHost::IPluginExtended) + INTERFACE_ENTRY(PluginHost::IWebSocket) + INTERFACE_ENTRY(PluginHost::IDispatcher) + END_INTERFACE_MAP public: - Core::JSON::String Bind; - Core::JSON::DecUInt16 TimeOut; - }; - - public: - WebBridge(const WebBridge&) = delete; - WebBridge& operator=(const WebBridge&) = delete; + // IPlugin methods + // ------------------------------------------------------------------------------------------------------- + //! ==================================== CALLED ON THREADPOOL THREAD ====================================== + const string Initialize(PluginHost::IShell* service) override; + //! ==================================== CALLED ON THREADPOOL THREAD ====================================== + void Deinitialize(PluginHost::IShell* service) override; + //! ==================================== CALLED ON THREADPOOL THREAD ====================================== + string Information() const override; + + // IDispatcher (override message) + // ------------------------------------------------------------------------------------------------------- +#fndef USE_THUNDER_R4 + //! ==================================== CALLED ON THREADPOOL THREAD ====================================== + Core::ProxyType Invoke(const string& token, const uint32_t channelId, const Core::JSONRPC::Message& message) override; + //! ==================================== CALLED ON THREADPOOL THREAD ====================================== + void Activate(PluginHost::IShell* service) override; + //! ==================================== CALLED ON THREADPOOL THREAD ====================================== + void Deactivate() override; +#else + //! ==================================== CALLED ON THREADPOOL THREAD ====================================== + Core::hresult Invoke(const uint32_t channelId, const uint32_t id, const string& token, const string& method, const string& parameters, string& response) override; + Core::hresult Revoke(ICallback* callback) override; +#endif + // IPluginExtended + // ------------------------------------------------------------------------------------------------------- + //! ================================== CALLED ON COMMUNICATION THREAD ===================================== + bool Attach(PluginHost::Channel& channel) override; + //! ================================== CALLED ON COMMUNICATION THREAD ===================================== + void Detach(PluginHost::Channel& channel) override; + + // IWebSocket + // ------------------------------------------------------------------------------------------------------- + //! ================================== CALLED ON COMMUNICATION THREAD ===================================== + Core::ProxyType Inbound(const string& identifier) override; + //! ==================================== CALLED ON THREADPOOL THREAD ====================================== + Core::ProxyType Inbound(const uint32_t ID, const Core::ProxyType& element) override; - #ifdef __WINDOWS__ - #pragma warning(disable: 4355) - #endif - WebBridge() - : _adminLock() - , _skipURL(0) - , _service(nullptr) - , _callsign() - , _supportedVersions() - , _observers() - , _pendingRequests() - , _javascriptService(0) - , _sequenceId(1) - , _timeOut(0) - , _cleaner(*this) - { - } - #ifdef __WINDOWS__ - #pragma warning(default: 4355) - #endif - ~WebBridge() override = default; - - BEGIN_INTERFACE_MAP(WebBridge) - INTERFACE_ENTRY(PluginHost::IPlugin) - INTERFACE_ENTRY(PluginHost::IPluginExtended) - INTERFACE_ENTRY(PluginHost::IWebSocket) - INTERFACE_ENTRY(PluginHost::IDispatcher) - END_INTERFACE_MAP - - public: - // IPlugin methods - // ------------------------------------------------------------------------------------------------------- - //! ==================================== CALLED ON THREADPOOL THREAD ====================================== - const string Initialize(PluginHost::IShell* service) override; - //! ==================================== CALLED ON THREADPOOL THREAD ====================================== - void Deinitialize(PluginHost::IShell* service) override; - //! ==================================== CALLED ON THREADPOOL THREAD ====================================== - string Information() const override; - - // IDispatcher - // ------------------------------------------------------------------------------------------------------- - //! ==================================== CALLED ON THREADPOOL THREAD ====================================== - Core::ProxyType Invoke(const string& token, const uint32_t channelId, const Core::JSONRPC::Message& message) override; - //! ==================================== CALLED ON THREADPOOL THREAD ====================================== - void Activate(PluginHost::IShell* service) override; - //! ==================================== CALLED ON THREADPOOL THREAD ====================================== - void Deactivate() override; - - // IPluginExtended - // ------------------------------------------------------------------------------------------------------- - //! ================================== CALLED ON COMMUNICATION THREAD ===================================== - bool Attach(PluginHost::Channel& channel) override; - //! ================================== CALLED ON COMMUNICATION THREAD ===================================== - void Detach(PluginHost::Channel& channel) override; - - // IWebSocket - // ------------------------------------------------------------------------------------------------------- - //! ================================== CALLED ON COMMUNICATION THREAD ===================================== - Core::ProxyType Inbound(const string& identifier) override; - //! ==================================== CALLED ON THREADPOOL THREAD ====================================== - Core::ProxyType Inbound(const uint32_t ID, const Core::ProxyType& element) override; - - - private: - void Cleanup(); - bool InternalMessage(const Core::ProxyType& message); - - bool HasMethodSupport(const VersionMap::const_iterator& index, const string& method) const { - bool result = false; - - if (index != _supportedVersions.cend()) { - result = (std::find(index->second.cbegin(), index->second.cend(), method) != index->second.cend()); - } - else { - VersionMap::const_iterator index = _supportedVersions.begin(); - - while ((result == false) && (index != _supportedVersions.end())) { - result = (std::find(index->second.cbegin(), index->second.cend(), method) != index->second.cend()); - index++; - } - } - - return (result); - } - state Destination(const string& designator, string& handler) const - { - state result = state::STATE_INCORRECT_HANDLER; - string callsign(Core::JSONRPC::Message::Callsign(designator)); - - // If the message is routed through the controlelr, the callsign is empty by now! - if ((callsign.empty()) || (callsign == _callsign)) { - // Seems we are on the right handler.. - // now see if someone supports this version - uint8_t version = Core::JSONRPC::Message::Version(designator); - VersionMap::const_iterator methods = _supportedVersions.cend(); - - // See if there was a version given.. - if (version != static_cast(~0)) { - methods = _supportedVersions.find(version); - if (methods == _supportedVersions.cend()) { - result = state::STATE_INCORRECT_VERSION; - } - } - - if (result == state::STATE_INCORRECT_HANDLER) { - string method = Core::JSONRPC::Message::Method(designator); - - if (method == _T("register")) { - result = state::STATE_REGISTRATION; - } - else if (method == _T("unregister")) { - result = state::STATE_UNREGISTRATION; - } - else if (method == _T("exists")) { - result = HasMethodSupport(methods, method) ? state::STATE_EXISTS : state::STATE_NONE_EXISTING; - } - else if (HasMethodSupport(methods, method) == true) { - result = state::STATE_CUSTOM; - handler = method; - } - else { - result = state::STATE_UNKNOWN_METHOD; - } - } - } - return (result); - } - void Subscribe(const uint32_t channelId, const string& eventName, const string& callsign, Core::JSONRPC::Message& response) - { - _adminLock.Lock(); - - ObserverMap::iterator index = _observers.find(eventName); - - if (index == _observers.end()) { - _observers[eventName].emplace_back(channelId, callsign); - response.Result = _T("0"); - } - else if (std::find(index->second.begin(), index->second.end(), Observer(channelId, callsign)) == index->second.end()) { - index->second.emplace_back(channelId, callsign); - response.Result = _T("0"); - } - else { - response.Error.SetError(Core::ERROR_DUPLICATE_KEY); - response.Error.Text = _T("Duplicate registration. Only 1 remains!!!"); - } - - _adminLock.Unlock(); - } - void Unsubscribe(const uint32_t channelId, const string& eventName, const string& callsign, Core::JSONRPC::Message& response) - { - _adminLock.Lock(); - - ObserverMap::iterator index = _observers.find(eventName); - - if (index != _observers.end()) { - ObserverList& clients = index->second; - ObserverList::iterator loop = clients.begin(); - Observer key(channelId, callsign); - - while ((loop != clients.end()) && (*loop != key)) { - loop++; - } - - if (loop != clients.end()) { - clients.erase(loop); - if (clients.empty() == true) { - _observers.erase(index); - } - response.Result = _T("0"); - } - } - - if (response.Result.IsSet() == false) { - response.Error.SetError(Core::ERROR_UNKNOWN_KEY); - response.Error.Text = _T("Registration not found!!!"); - } - - _adminLock.Unlock(); - } + private: + friend Core::ThreadPool::JobType; + void Dispatch(); + bool InternalMessage(const Core::ProxyType& message); - private: - Core::CriticalSection _adminLock; - uint8_t _skipURL; - PluginHost::IShell* _service; - string _callsign; - VersionMap _supportedVersions; - ObserverMap _observers; - PendingMap _pendingRequests; - uint32_t _javascriptService; - uint32_t _sequenceId; - uint32_t _timeOut; - Core::WorkerPool::JobType _cleaner; - }; + private: + Core::CriticalSection _adminLock; + uint8_t _skipURL; + context _mode; + PluginHost::IShell* _service; + string _callsign; + PendingMap _pendingRequests; + uint32_t _javascriptService; + uint32_t _sequenceId; + uint32_t _timeOut; + Core::WorkerPool::JobType _cleaner; + }; -} // namespace Plugin + } // namespace Plugin } // namespace WPEFramework