From ca3cdcde950b28cbb756dbf663b62e80d0f604d2 Mon Sep 17 00:00:00 2001 From: parag-pv Date: Mon, 16 Sep 2024 15:11:24 -0400 Subject: [PATCH 1/4] Native sdk unit tests framework initial commit --- languages/cpp/src/shared/CMakeLists.txt | 55 +- .../cpp/src/shared/include/json_engine.h | 165 +++ languages/cpp/src/shared/src/CMakeLists.txt | 2 + .../src/shared/src/Transport/MockTransport.h | 1076 +++++++++++++++++ 4 files changed, 1296 insertions(+), 2 deletions(-) create mode 100644 languages/cpp/src/shared/include/json_engine.h create mode 100644 languages/cpp/src/shared/src/Transport/MockTransport.h diff --git a/languages/cpp/src/shared/CMakeLists.txt b/languages/cpp/src/shared/CMakeLists.txt index fe7a0085..711f298b 100644 --- a/languages/cpp/src/shared/CMakeLists.txt +++ b/languages/cpp/src/shared/CMakeLists.txt @@ -29,8 +29,57 @@ else () set(FIREBOLT_LIBRARY_TYPE SHARED) endif () +include(FetchContent) + +message("Fetching nlohmann json... ") +set(nlohmann_json_VERSION v3.11.3 CACHE STRING "Fetch nlohmann::json version") +FetchContent_Declare( + nlohmann_json + GIT_REPOSITORY https://github.com/nlohmann/json + GIT_TAG ${nlohmann_json_VERSION} +) +FetchContent_GetProperties(nlohmann_json) +if(NOT nlohmann_json) + FetchContent_Populate(nlohmann_json) + add_subdirectory(${nlohmann_json_SOURCE_DIR} ${nlohmann_json_BUILD_DIR}) +endif() +FetchContent_MakeAvailable(nlohmann_json) + +message("Fetching nlohmann json-schema-validator... ") +FetchContent_Declare( + nlohmann_json_schema_validator + GIT_REPOSITORY https://github.com/pboettch/json-schema-validator.git + GIT_TAG 2.3.0 +) +FetchContent_GetProperties(nlohmann_json_schema_validator) +if(NOT nlohmann_json_schema_validator) + FetchContent_Populate(nlohmann_json_schema_validator) + add_subdirectory(${nlohmann_json_schema_validator_SOURCE_DIR} ${nlohmann_json_schema_validator_BUILD_DIR}) +endif() +FetchContent_MakeAvailable(nlohmann_json_schema_validator) + +message("Fetching googletest... ") +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest + GIT_TAG v1.15.2 +) +FetchContent_GetProperties(googletest) +if(NOT googletest_POPULATED) + FetchContent_Populate(googletest) + add_subdirectory(${googletest_SOURCE_DIR} ${google_BUILD_DIR}) +endif() +FetchContent_MakeAvailable(googletest) + +include_directories( + ${nlohmann_json_SOURCE_DIR}/include + ${nlohmann_json_schema_validator_SOURCE_DIR}/src + ${googletest_SOURCE_DIR}/googletest/include + ${googletest_SOURCE_DIR}/googlemock/include +) + if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${SYSROOT_PATH}/usr" CACHE INTERNAL "" FORCE) + set(CMAKE_INSTALL_PREFIX ${SYSROOT_PATH}/usr CACHE INTERNAL "" FORCE) set(CMAKE_PREFIX_PATH ${SYSROOT_PATH}/usr/lib/cmake CACHE INTERNAL "" FORCE) endif() @@ -42,6 +91,8 @@ include(HelperFunctions) set(FIREBOLT_NAMESPACE ${PROJECT_NAME} CACHE STRING "Namespace of the project") +message("CMAKE_PREFIX_PATH: " ${CMAKE_PREFIX_PATH}) + find_package(WPEFramework CONFIG REQUIRED) add_subdirectory(src) @@ -53,4 +104,4 @@ endif() # make sure others can make use cmake settings of Firebolt OpenRPC configure_file( "${CMAKE_SOURCE_DIR}/cmake/project.cmake.in" "${CMAKE_BINARY_DIR}/${FIREBOLT_NAMESPACE}Config.cmake" - @ONLY) + @ONLY) \ No newline at end of file diff --git a/languages/cpp/src/shared/include/json_engine.h b/languages/cpp/src/shared/include/json_engine.h new file mode 100644 index 00000000..79ab589d --- /dev/null +++ b/languages/cpp/src/shared/include/json_engine.h @@ -0,0 +1,165 @@ +// #include +#include + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include +#include + +using nlohmann::json; +using nlohmann::json_schema::json_validator; +using namespace ::testing; + +#define REMOVE_QUOTES(s) (s.substr(1, s.length() - 2)) +#define STRING_TO_BOOL(s) (s == "true" ? true : false) + + +inline std::string capitalizeFirstChar(std::string str) { + if (!str.empty()) { + str[0] = std::toupper(str[0]); + } + return str; +} + + +class JsonEngine +{ + private: + std::fstream _file; + nlohmann::json _data; + + public: + + JsonEngine() + { + if (!_file.is_open()) + _file.open("../dist/firebolt-core-open-rpc.json"); + _file >> _data; + } + + ~JsonEngine(){ + if (_file.is_open()) + _file.close(); + } + + std::string get_value(const std::string& method_name) + { + for (const auto &method : _data["methods"]) + { + if (method.contains("name") && (method["name"] == method_name)) + { + auto value = method["examples"][0]["result"]["value"]; + return value.dump(); + } + } + return ""; + } + + + #ifndef UNIT_TEST + + // template + void MockRequest(const WPEFramework::Core::JSONRPC::Message* message) + { + std::cout << "Inside JSON engine MockRequest function" << std::endl; + std::string methodName = capitalizeFirstChar(message->Designator.Value().c_str()); + + /* TODO: Add a flag here that will be set to true if the method name is found in the rpc block, u + Use the flag to validate "Method not found" or other errors from SDK if applicable */ + for (const auto &method : _data["methods"]) + { + if (method.contains("name") && (method["name"] == methodName)) + { + // Method name validation + EXPECT_EQ(methodName, method["name"]); + + // ID Validation + // TODO: Check if id gets incremented by 1 for each request + std::cout << "MockRequest actual message.Id.Value(): " << message->Id.Value() << std::endl; + EXPECT_THAT(message->Id, AllOf(Ge(1),Le(std::numeric_limits::max()))); + + // Schema validation + const json requestParams = json::parse(message->Parameters.Value()); + std::cout << "Schema validator requestParams JSON: " << requestParams.dump() << std::endl; + if(method["params"].empty()) { + std::cout << "Params is empty" << std::endl; + EXPECT_EQ(requestParams, "{}"_json); + } + else { + std::cout << "Params is NOT empty" << std::endl; + const json openRPCSchema = method["params"][0]["schema"]; + std::cout << "Schema validator schema JSON: " << openRPCSchema.dump() << std::endl; + + json_validator validator; + try{ + validator.set_root_schema(openRPCSchema); + validator.validate(requestParams); + // EXPECT_NO_THROW(validator.validate(requestParams)); // For usage without try catch + std::cout << "Schema validation succeeded" << std::endl; + } + catch (const std::exception &e){ + FAIL() << "Schema validation error: " << e.what() << std::endl; + } + } + + // DUMMY SCHEMA VALIDATION - TO BE REMOVED + // const json openRPCSchema = R"( + // { + // "title": "AdConfigurationOptions", + // "type": "object", + // "properties": { + // "coppa": { + // "type": "boolean", + // "description": "Whether or not the app requires US COPPA compliance." + // }, + // "environment": { + // "type": "string", + // "enum": [ + // "prod", + // "test" + // ], + // "default": "prod", + // "description": "Whether the app is running in a production or test mode." + // }, + // "authenticationEntity": { + // "type": "string", + // "description": "The authentication provider, when it is separate entity than the app provider, e.g. an MVPD." + // } + // } + // })"_json; + // const json requestParams = json::parse(message->Parameters.Value()); + // // const json requestParams = R"({"options":{}})"_json; + // json_validator validator; + // try{ + // validator.set_root_schema(openRPCSchema); + // validator.validate(requestParams); + // // EXPECT_NO_THROW(validator.validate(requestParams)); // For usage without try catch + // std::cout << "Schema validation succeeded" << std::endl; + // } + // catch (const std::exception &e){ + // FAIL() << "Schema validation error: " << e.what() << std::endl; + // } + } + } + } + + template + Firebolt::Error MockResponse(WPEFramework::Core::JSONRPC::Message &message, RESPONSE &response) + { + std::cout << "Inside JSON engine MockResponse function" << std::endl; + std::string methodName = capitalizeFirstChar(message.Designator.Value().c_str()); + + // Loop through the methods to find the one with the given name + for (const auto &method : _data["methods"]) + { + if (method.contains("name") && (method["name"] == methodName)) + { + message.Result = method["examples"][0]["result"]["value"].dump(); + } + } + return Firebolt::Error::None; + } +#endif +}; + diff --git a/languages/cpp/src/shared/src/CMakeLists.txt b/languages/cpp/src/shared/src/CMakeLists.txt index 222ba35f..05b521e1 100644 --- a/languages/cpp/src/shared/src/CMakeLists.txt +++ b/languages/cpp/src/shared/src/CMakeLists.txt @@ -37,6 +37,8 @@ find_package(${NAMESPACE}WebSocket CONFIG REQUIRED) find_package(${NAMESPACE}WebSocket CONFIG REQUIRED) find_package(${NAMESPACE}Core CONFIG REQUIRED) +include_directories(${CMAKE_SOURCE_DIR}/build/${FIREBOLT_NAMESPACE}/usr/include/) + target_link_libraries(${TARGET} PUBLIC ${NAMESPACE}WebSocket::${NAMESPACE}WebSocket diff --git a/languages/cpp/src/shared/src/Transport/MockTransport.h b/languages/cpp/src/shared/src/Transport/MockTransport.h new file mode 100644 index 00000000..ff0d2c5b --- /dev/null +++ b/languages/cpp/src/shared/src/Transport/MockTransport.h @@ -0,0 +1,1076 @@ +/* + * Copyright 2023 Comcast Cable Communications Management, LLC + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "Module.h" +#include "error.h" +#include "json_engine.h" + +// #define MY_DEBUG(message, value) +// #define MY_DEBUG(message, value) std::cout << "[MyDebug] " << __FILE__ << " "<< __func__ << "() " << message << ": "<< value << std::endl; + +namespace FireboltSDK +{ + + using namespace WPEFramework::Core::TypeTraits; + + template + class CommunicationChannel + { + public: + typedef std::function Callback; + class Entry + { + private: + Entry(const Entry &) = delete; + Entry &operator=(const Entry &rhs) = delete; + struct Synchronous + { + Synchronous() + : _signal(false, true), _response() + { + } + WPEFramework::Core::Event _signal; + std::list> _response; + }; + struct ASynchronous + { + ASynchronous(const uint32_t waitTime, const Callback &completed) + : _waitTime(WPEFramework::Core::Time::Now().Add(waitTime).Ticks()), _completed(completed) + { + } + uint64_t _waitTime; + Callback _completed; + }; + + public: + Entry() + : _synchronous(true), _info() + { + } + Entry(const uint32_t waitTime, const Callback &completed) + : _synchronous(false), _info(waitTime, completed) + { + } + ~Entry() + { + if (_synchronous == true) + { + _info.sync.~Synchronous(); + } + else + { + _info.async.~ASynchronous(); + } + } + + public: + const WPEFramework::Core::ProxyType &Response() const + { + return (*(_info.sync._response.begin())); + } + bool Signal(const WPEFramework::Core::ProxyType &response) + { + if (_synchronous == true) + { + _info.sync._response.push_back(response); + _info.sync._signal.SetEvent(); + } + else + { + _info.async._completed(*response); + } + + return (_synchronous == false); + } + const uint64_t &Expiry() const + { + return (_info.async._waitTime); + } + void Abort(const uint32_t id) + { + if (_synchronous == true) + { + _info.sync._signal.SetEvent(); + } + else + { + MESSAGETYPE message; + ToMessage(id, message, WPEFramework::Core::ERROR_ASYNC_ABORTED); + _info.async._completed(message); + } + } + bool Expired(const uint32_t id, const uint64_t ¤tTime, uint64_t &nextTime) + { + bool expired = false; + + if (_synchronous == false) + { + if (_info.async._waitTime > currentTime) + { + if (_info.async._waitTime < nextTime) + { + nextTime = _info.async._waitTime; + } + } + else + { + MESSAGETYPE message; + ToMessage(id, message, WPEFramework::Core::ERROR_TIMEDOUT); + _info.async._completed(message); + expired = true; + } + } + return (expired); + } + bool WaitForResponse(const uint32_t waitTime) + { + return (_info.sync._signal.Lock(waitTime) == WPEFramework::Core::ERROR_NONE); + } + + private: + void ToMessage(const uint32_t id, WPEFramework::Core::JSONRPC::Message &message, uint32_t error) + { + message.Id = id; + message.Error.Code = error; + switch (error) + { + case WPEFramework::Core::ERROR_ASYNC_ABORTED: + { + message.Error.Text = _T("Pending a-sync call has been aborted"); + break; + } + case WPEFramework::Core::ERROR_TIMEDOUT: + { + message.Error.Text = _T("Pending a-sync call has timed out"); + break; + } + } + } + + bool _synchronous; + union Info + { + public: + Info() + : sync() + { + } + Info(const uint32_t waitTime, const Callback &completed) + : async(waitTime, completed) + { + } + ~Info() + { + } + Synchronous sync; + ASynchronous async; + } _info; + }; + + private: + class FactoryImpl + { + private: + FactoryImpl(const FactoryImpl &) = delete; + FactoryImpl &operator=(const FactoryImpl &) = delete; + + class WatchDog + { + private: + WatchDog() = delete; + WatchDog &operator=(const WatchDog &) = delete; + + public: + WatchDog(CLIENT *client) + : _client(client) + { + } + WatchDog(const WatchDog ©) + : _client(copy._client) + { + } + ~WatchDog() + { + } + + bool operator==(const WatchDog &rhs) const + { + return (rhs._client == _client); + } + bool operator!=(const WatchDog &rhs) const + { + return (!operator==(rhs)); + } + + public: + uint64_t Timed(const uint64_t scheduledTime) + { + return (_client->Timed()); + } + + private: + CLIENT *_client; + }; + + friend WPEFramework::Core::SingletonType; + + FactoryImpl() + : _messageFactory(2), _watchDog(WPEFramework::Core::Thread::DefaultStackSize(), _T("TransportCleaner")) + { + } + + public: + static FactoryImpl &Instance() + { + return (WPEFramework::Core::SingletonType::Instance()); + } + + ~FactoryImpl() + { + } + + public: + WPEFramework::Core::ProxyType Element(const string &) + { + return (_messageFactory.Element()); + } + void Trigger(const uint64_t &time, CLIENT *client) + { + _watchDog.Trigger(time, client); + } + void Revoke(CLIENT *client) + { + _watchDog.Revoke(client); + } + + private: + WPEFramework::Core::ProxyPoolType _messageFactory; + WPEFramework::Core::TimerType _watchDog; + }; + + class ChannelImpl : public WPEFramework::Core::StreamJSONType, FactoryImpl &, INTERFACE> + { + private: + ChannelImpl(const ChannelImpl &) = delete; + ChannelImpl &operator=(const ChannelImpl &) = delete; + + typedef WPEFramework::Core::StreamJSONType, FactoryImpl &, INTERFACE> BaseClass; + + public: + ChannelImpl(CommunicationChannel *parent, const WPEFramework::Core::NodeId &remoteNode, const string &path, const string &query, const bool mask) + : BaseClass(5, FactoryImpl::Instance(), path, _T("JSON"), query, "", false, mask, false, remoteNode.AnyInterface(), remoteNode, 512, 512), _parent(*parent) + { + } + ~ChannelImpl() override = default; + + public: + void Received(WPEFramework::Core::ProxyType &response) override + { + WPEFramework::Core::ProxyType inbound(response); + + ASSERT(inbound.IsValid() == true); + if (inbound.IsValid() == true) + { + _parent.Inbound(inbound); + } + } + void Send(WPEFramework::Core::ProxyType &msg) override + { +#ifdef __DEBUG__ + string message; + ToMessage(msg, message); + TRACE_L1("Message: %s send", message.c_str()); +#endif + } + void StateChange() override + { + _parent.StateChange(); + } + bool IsIdle() const override + { + return (true); + } + + private: + void ToMessage(const WPEFramework::Core::ProxyType &jsonObject, string &message) const + { + WPEFramework::Core::ProxyType inbound(jsonObject); + + ASSERT(inbound.IsValid() == true); + if (inbound.IsValid() == true) + { + inbound->ToString(message); + } + } + void ToMessage(const WPEFramework::Core::ProxyType &jsonObject, string &message) const + { + WPEFramework::Core::ProxyType inbound(jsonObject); + + ASSERT(inbound.IsValid() == true); + if (inbound.IsValid() == true) + { + std::vector values; + inbound->ToBuffer(values); + if (values.empty() != true) + { + WPEFramework::Core::ToString(values.data(), static_cast(values.size()), false, message); + } + } + } + + private: + CommunicationChannel &_parent; + }; + + protected: + CommunicationChannel(const WPEFramework::Core::NodeId &remoteNode, const string &path, const string &query, const bool mask) + : _channel(this, remoteNode, path, query, mask), _sequence(0) + { + } + + public: + ~CommunicationChannel() = default; + static WPEFramework::Core::ProxyType Instance(const WPEFramework::Core::NodeId &remoteNode, const string &path, const string &query, const bool mask = true) + { + static WPEFramework::Core::ProxyMapType channelMap; + + string searchLine = remoteNode.HostAddress() + '@' + path; + + return (channelMap.template Instance(searchLine, remoteNode, path, query, mask)); + } + + public: + static void Trigger(const uint64_t &time, CLIENT *client) + { + FactoryImpl::Instance().Trigger(time, client); + } + static WPEFramework::Core::ProxyType Message() + { + return (FactoryImpl::Instance().Element(string())); + } + uint32_t Sequence() const + { + return (++_sequence); + } + void Register(CLIENT &client) + { + _adminLock.Lock(); + ASSERT(std::find(_observers.begin(), _observers.end(), &client) == _observers.end()); + _observers.push_back(&client); + if (true) + { + client.Opened(); + } + _adminLock.Unlock(); + } + void Unregister(CLIENT &client) + { + _adminLock.Lock(); + typename std::list::iterator index(std::find(_observers.begin(), _observers.end(), &client)); + if (index != _observers.end()) + { + _observers.erase(index); + } + FactoryImpl::Instance().Revoke(&client); + _adminLock.Unlock(); + } + +#ifdef UNIT_TEST + void Submit(const WPEFramework::Core::ProxyType &message) + { + std::cout << "Inside Transport Submit function 1" << std::endl; + _channel.Submit(message); + } +#else + void Submit(const WPEFramework::Core::ProxyType &message) + { + std::cout << "Inside Mock Transport Submit function 1" << std::endl; + const WPEFramework::Core::JSONRPC::Message* jsonRpcMessage = dynamic_cast(message.operator->()); + std::unique_ptr jsonEngine = std::make_unique(); + jsonEngine->MockRequest(jsonRpcMessage); + } +#endif + bool IsSuspended() const + { + return (_channel.IsSuspended()); + } + uint32_t Initialize() + { + return (Open(0)); + } + void Deinitialize() + { + Close(); + } + bool IsOpen() + { + // return (_channel.IsOpen() == true); + return true; + } + + protected: + void StateChange() + { + _adminLock.Lock(); + typename std::list::iterator index(_observers.begin()); + while (index != _observers.end()) + { + if (_channel.IsOpen() == true) + { + (*index)->Opened(); + } + else + { + (*index)->Closed(); + } + index++; + } + _adminLock.Unlock(); + } + bool Open(const uint32_t waitTime) + { + // bool result = true; + // if (_channel.IsClosed() == true) { + // result = (_channel.Open(waitTime) == WPEFramework::Core::ERROR_NONE); + // } + // return (result); + return true; + } + void Close() + { + _channel.Close(WPEFramework::Core::infinite); + } + + private: + int32_t Inbound(const WPEFramework::Core::ProxyType &inbound) + { + int32_t result = WPEFramework::Core::ERROR_UNAVAILABLE; + _adminLock.Lock(); + typename std::list::iterator index(_observers.begin()); + while ((result != WPEFramework::Core::ERROR_NONE) && (index != _observers.end())) + { + result = (*index)->Submit(inbound); + index++; + } + _adminLock.Unlock(); + + return (result); + } + + private: + WPEFramework::Core::CriticalSection _adminLock; + ChannelImpl _channel; + mutable std::atomic _sequence; + std::list _observers; + }; + + class IEventHandler + { + public: + virtual Firebolt::Error ValidateResponse(const WPEFramework::Core::ProxyType &jsonResponse, bool &enabled) = 0; + virtual Firebolt::Error Dispatch(const string &eventName, const WPEFramework::Core::ProxyType &jsonResponse) = 0; + virtual ~IEventHandler() = default; + }; + + template + class Transport + { + private: + using Channel = CommunicationChannel; + using Entry = typename CommunicationChannel::Entry; + using PendingMap = std::unordered_map; + using EventMap = std::map; + typedef std::function &jsonResponse, bool &enabled)> EventResponseValidatioionFunction; + + class CommunicationJob : public WPEFramework::Core::IDispatch + { + protected: + CommunicationJob(const WPEFramework::Core::ProxyType &inbound, class Transport *parent) + : _inbound(inbound), _parent(parent) + { + } + + public: + CommunicationJob() = delete; + CommunicationJob(const CommunicationJob &) = delete; + CommunicationJob &operator=(const CommunicationJob &) = delete; + + ~CommunicationJob() = default; + + public: + static WPEFramework::Core::ProxyType Create(const WPEFramework::Core::ProxyType &inbound, class Transport *parent); + + void Dispatch() override + { + _parent->Inbound(_inbound); + } + + private: + const WPEFramework::Core::ProxyType _inbound; + class Transport *_parent; + }; + + class ConnectionJob : public WPEFramework::Core::IDispatch + { + protected: + ConnectionJob(class Transport *parent) + : _parent(parent) + { + } + + public: + ConnectionJob() = delete; + ConnectionJob(const ConnectionJob &) = delete; + ConnectionJob &operator=(const ConnectionJob &) = delete; + + ~ConnectionJob() = default; + + public: + static WPEFramework::Core::ProxyType Create(class Transport *parent); + + void Dispatch() override + { + if (Firebolt::Error::None != _parent->WaitForLinkReady()) + { + _parent->NotifyStatus(Firebolt::Error::Timedout); + } + } + + private: + const WPEFramework::Core::ProxyType _inbound; + class Transport *_parent; + }; + + protected: + static constexpr uint32_t DefaultWaitTime = 10000; + + inline void Announce() + { + _channel->Register(*this); + } + + private: + static constexpr const TCHAR *PathPrefix = _T("/"); + + public: + typedef std::function Listener; + + public: + Transport() = delete; + Transport(const Transport &) = delete; + Transport &operator=(Transport &) = delete; + Transport(const WPEFramework::Core::URL &url, const uint32_t waitTime, const Listener listener) + : _adminLock(), _connectId(WPEFramework::Core::NodeId(url.Host().Value().c_str(), url.Port().Value())), _channel(Channel::Instance(_connectId, ((url.Path().Value().rfind(PathPrefix, 0) == 0) ? url.Path().Value() : string(PathPrefix + url.Path().Value())), url.Query().Value(), true)), _eventHandler(nullptr), _pendingQueue(), _scheduledTime(0), _waitTime(waitTime), _listener(listener), _connected(false), _status(Firebolt::Error::NotConnected) + { + _channel->Register(*this); + WPEFramework::Core::ProxyType job = WPEFramework::Core::ProxyType(WPEFramework::Core::ProxyType::Create(this)); + WPEFramework::Core::IWorkerPool::Instance().Submit(job); + } + + virtual ~Transport() + { + _channel->Unregister(*this); + + for (auto &element : _pendingQueue) + { + element.second.Abort(element.first); + } + } + + public: + inline bool IsOpen() + { + // return _channel->IsOpen(); + return true; + } + + void Revoke(const string &eventName) + { + _adminLock.Lock(); + _eventMap.erase(eventName); + _adminLock.Unlock(); + } + + void SetEventHandler(IEventHandler *eventHandler) + { + _eventHandler = eventHandler; + } + +#ifdef UNIT_TEST + template + Firebolt::Error Invoke(const string& method, const PARAMETERS& parameters, RESPONSE& response) + { + std::cout << "Inside OG Transport Invoke function" << std::endl; + Entry slot; + uint32_t id = _channel->Sequence(); + Firebolt::Error result = Send(method, parameters, id); + if (result == Firebolt::Error::None) { + result = WaitForResponse(id, response, _waitTime); + } + + return (result); + } +#else + template + Firebolt::Error Invoke(const string &method, const PARAMETERS ¶meters, RESPONSE &response) + { + std::cout << "Inside Mock Transport Invoke function" << std::endl; + Entry slot; + uint32_t id = _channel->Sequence(); + std::cout << "Inside Mock Transport Invoke function - id: " << id << std::endl; + Firebolt::Error result = Send(method, parameters, id); + + WPEFramework::Core::JSONRPC::Message message; + message.Designator = method; + std::unique_ptr jsonEngine = std::make_unique(); + result = jsonEngine->MockResponse(message, response); + FromMessage((INTERFACE *)&response, message); + + // return Firebolt::Error::None; + return (result); + } +#endif + + template + Firebolt::Error InvokeAsync(const string &method, const PARAMETERS ¶meters, uint32_t &id) + { + Entry slot; + id = _channel->Sequence(); + return Send(method, parameters, id); + } + + template + Firebolt::Error WaitForResponse(const uint32_t& id, RESPONSE& response, const uint32_t waitTime) + { + int32_t result = WPEFramework::Core::ERROR_TIMEDOUT; + _adminLock.Lock(); + typename PendingMap::iterator index = _pendingQueue.find(id); + Entry& slot(index->second); + _adminLock.Unlock(); + + if (slot.WaitForResponse(waitTime) == true) { + WPEFramework::Core::ProxyType jsonResponse = slot.Response(); + + // See if we have a jsonResponse, maybe it was just the connection + // that closed? + if (jsonResponse.IsValid() == true) { + if (jsonResponse->Error.IsSet() == true) { + result = jsonResponse->Error.Code.Value(); + } + else { + result = WPEFramework::Core::ERROR_NONE; + if ((jsonResponse->Result.IsSet() == true) + && (jsonResponse->Result.Value().empty() == false)) { + FromMessage((INTERFACE*)&response, *jsonResponse); + } + } + } + } else { + result = WPEFramework::Core::ERROR_TIMEDOUT; + } + _adminLock.Lock(); + _pendingQueue.erase(id); + _adminLock.Unlock(); + return FireboltErrorValue(result); + } + + void Abort(uint32_t id) + { + _adminLock.Lock(); + typename PendingMap::iterator index = _pendingQueue.find(id); + Entry &slot(index->second); + _adminLock.Unlock(); + slot.Abort(id); + } + + template + Firebolt::Error Subscribe(const string &eventName, const string ¶meters, RESPONSE &response) + { + Entry slot; + uint32_t id = _channel->Sequence(); + Firebolt::Error result = Send(eventName, parameters, id); + if (result == Firebolt::Error::None) + { + _adminLock.Lock(); + _eventMap.emplace(std::piecewise_construct, + std::forward_as_tuple(eventName), + std::forward_as_tuple(~0)); + _adminLock.Unlock(); + + result = WaitForEventResponse(id, eventName, response, _waitTime); + } + + return (result); + } + + Firebolt::Error Unsubscribe(const string &eventName, const string ¶meters) + { + Revoke(eventName); + Entry slot; + uint32_t id = _channel->Sequence(); + + return Send(eventName, parameters, id); + } + + void NotifyStatus(Firebolt::Error status) + { + _listener(false, status); + } + + Firebolt::Error WaitForLinkReady() + { + uint32_t waiting = _waitTime; + static constexpr uint32_t SLEEPSLOT_TIME = 100; + + // Right, a wait till connection is closed is requested.. + while ((waiting > 0) && (IsOpen() == false) && (_status == Firebolt::Error::NotConnected)) + { + + uint32_t sleepSlot = (waiting > SLEEPSLOT_TIME ? SLEEPSLOT_TIME : waiting); + + // Right, lets sleep in slices of 100 ms + SleepMs(sleepSlot); + + waiting -= (waiting == WPEFramework::Core::infinite ? 0 : sleepSlot); + } + return (((waiting == 0) || (IsOpen() == true)) ? Firebolt::Error::None : Firebolt::Error::Timedout); + } + + private: + friend Channel; + inline bool IsEvent(const uint32_t id, string &eventName) + { + _adminLock.Lock(); + for (auto &event : _eventMap) + { + if (event.second == id) + { + eventName = event.first; + break; + } + } + _adminLock.Unlock(); + return (eventName.empty() != true); + } + uint64_t Timed() + { + uint64_t result = ~0; + uint64_t currentTime = WPEFramework::Core::Time::Now().Ticks(); + + // Lets see if some callback are expire. If so trigger and remove... + _adminLock.Lock(); + + typename PendingMap::iterator index = _pendingQueue.begin(); + + while (index != _pendingQueue.end()) + { + + if (index->second.Expired(index->first, currentTime, result) == true) + { + index = _pendingQueue.erase(index); + } + else + { + index++; + } + } + _scheduledTime = (result != static_cast(~0) ? result : 0); + + _adminLock.Unlock(); + + return (_scheduledTime); + } + + virtual void Opened() + { + _status = Firebolt::Error::None; + if (_connected != true) + { + _connected = true; + _listener(_connected, _status); + } + } + + void Closed() + { + // Abort any in progress RPC command: + _adminLock.Lock(); + + // See if we issued anything, if so abort it.. + while (_pendingQueue.size() != 0) + { + + _pendingQueue.begin()->second.Abort(_pendingQueue.begin()->first); + _pendingQueue.erase(_pendingQueue.begin()); + } + + _adminLock.Unlock(); + if (_connected != false) + { + _connected = false; + _listener(_connected, _status); + } + } + + int32_t Submit(const WPEFramework::Core::ProxyType &inbound) + { + std::cout << "Inside Transport Submit function 2" << std::endl; + int32_t result = WPEFramework::Core::ERROR_UNAVAILABLE; + WPEFramework::Core::ProxyType job = WPEFramework::Core::ProxyType(WPEFramework::Core::ProxyType::Create(inbound, this)); + WPEFramework::Core::IWorkerPool::Instance().Submit(job); + return 0; + } + + int32_t Inbound(const WPEFramework::Core::ProxyType &inbound) + { + int32_t result = WPEFramework::Core::ERROR_INVALID_SIGNATURE; + + ASSERT(inbound.IsValid() == true); + + if ((inbound->Id.IsSet() == true) && (inbound->Result.IsSet() || inbound->Error.IsSet())) + { + // Looks like this is a response.. + ASSERT(inbound->Parameters.IsSet() == false); + ASSERT(inbound->Designator.IsSet() == false); + + _adminLock.Lock(); + + // See if we issued this.. + typename PendingMap::iterator index = _pendingQueue.find(inbound->Id.Value()); + + if (index != _pendingQueue.end()) + { + + if (index->second.Signal(inbound) == true) + { + _pendingQueue.erase(index); + } + + result = WPEFramework::Core::ERROR_NONE; + _adminLock.Unlock(); + } + else + { + _adminLock.Unlock(); + string eventName; + if (IsEvent(inbound->Id.Value(), eventName)) + { + _eventHandler->Dispatch(eventName, inbound); + } + } + } + + return (result); + } + + template + Firebolt::Error Send(const string &method, const PARAMETERS ¶meters, const uint32_t &id) + { + std::cout << "Inside Transport Send function" << std::endl; + int32_t result = WPEFramework::Core::ERROR_UNAVAILABLE; + + if ((_channel.IsValid() == true) && (_channel->IsSuspended() == true)) + { + result = WPEFramework::Core::ERROR_ASYNC_FAILED; + } + else if (_channel.IsValid() == true) + { + + result = WPEFramework::Core::ERROR_ASYNC_FAILED; + + WPEFramework::Core::ProxyType message(Channel::Message()); + message->Id = id; + message->Designator = method; + ToMessage(parameters, message); + + _adminLock.Lock(); + + typename std::pair newElement = + _pendingQueue.emplace(std::piecewise_construct, + std::forward_as_tuple(id), + std::forward_as_tuple()); + ASSERT(newElement.second == true); + + if (newElement.second == true) + { + + _adminLock.Unlock(); + + _channel->Submit(WPEFramework::Core::ProxyType(message)); + + message.Release(); + result = WPEFramework::Core::ERROR_NONE; + } + } + return FireboltErrorValue(result); + } + + static constexpr uint32_t WAITSLOT_TIME = 100; + template + Firebolt::Error WaitForEventResponse(const uint32_t &id, const string &eventName, RESPONSE &response, const uint32_t waitTime) + { + Firebolt::Error result = Firebolt::Error::Timedout; + _adminLock.Lock(); + typename PendingMap::iterator index = _pendingQueue.find(id); + Entry &slot(index->second); + _adminLock.Unlock(); + + uint8_t waiting = waitTime; + do + { + uint32_t waitSlot = (waiting > WAITSLOT_TIME ? WAITSLOT_TIME : waiting); + if (slot.WaitForResponse(waitSlot) == true) + { + WPEFramework::Core::ProxyType jsonResponse = slot.Response(); + + // See if we have a jsonResponse, maybe it was just the connection + // that closed? + if (jsonResponse.IsValid() == true) + { + if (jsonResponse->Error.IsSet() == true) + { + result = FireboltErrorValue(jsonResponse->Error.Code.Value()); + } + else + { + if ((jsonResponse->Result.IsSet() == true) && (jsonResponse->Result.Value().empty() == false)) + { + bool enabled; + result = _eventHandler->ValidateResponse(jsonResponse, enabled); + if (result == Firebolt::Error::None) + { + FromMessage((INTERFACE *)&response, *jsonResponse); + if (enabled) + { + _adminLock.Lock(); + typename EventMap::iterator index = _eventMap.find(eventName); + if (index != _eventMap.end()) + { + index->second = id; + } + _adminLock.Unlock(); + } + } + } + } + } + } + else + { + result = Firebolt::Error::Timedout; + } + waiting -= (waiting == WPEFramework::Core::infinite ? 0 : waitSlot); + } while ((result != Firebolt::Error::None) && (waiting > 0)); + _adminLock.Lock(); + _pendingQueue.erase(id); + _adminLock.Unlock(); + + return result; + } + + public: + void FromMessage(WPEFramework::Core::JSON::IElement *response, const WPEFramework::Core::JSONRPC::Message &message) const + { + response->FromString(message.Result.Value()); + } + + void FromMessage(WPEFramework::Core::JSON::IMessagePack *response, const WPEFramework::Core::JSONRPC::Message &message) const + { + string value = message.Result.Value(); + std::vector result(value.begin(), value.end()); + response->FromBuffer(result); + } + + private: + void ToMessage(const string ¶meters, WPEFramework::Core::ProxyType &message) const + { + if (parameters.empty() != true) + { + message->Parameters = parameters; + } + } + + template + void ToMessage(PARAMETERS ¶meters, WPEFramework::Core::ProxyType &message) const + { + ToMessage((INTERFACE *)(¶meters), message); + return; + } + + void ToMessage(WPEFramework::Core::JSON::IMessagePack *parameters, WPEFramework::Core::ProxyType &message) const + { + std::vector values; + parameters->ToBuffer(values); + if (values.empty() != true) + { + string strValues(values.begin(), values.end()); + message->Parameters = strValues; + } + return; + } + + void ToMessage(WPEFramework::Core::JSON::IElement *parameters, WPEFramework::Core::ProxyType &message) const + { + string values; + parameters->ToString(values); + if (values.empty() != true) + { + message->Parameters = values; + } + return; + } + + Firebolt::Error FireboltErrorValue(const uint32_t error) + { + Firebolt::Error fireboltError = static_cast(error); + switch (error) + { + case WPEFramework::Core::ERROR_NONE: + fireboltError = Firebolt::Error::None; + break; + case WPEFramework::Core::ERROR_GENERAL: + case WPEFramework::Core::ERROR_UNAVAILABLE: + fireboltError = Firebolt::Error::General; + break; + case WPEFramework::Core::ERROR_TIMEDOUT: + fireboltError = Firebolt::Error::Timedout; + break; + default: + break; + } + + return fireboltError; + } + + private: + WPEFramework::Core::CriticalSection _adminLock; + WPEFramework::Core::NodeId _connectId; + WPEFramework::Core::ProxyType _channel; + IEventHandler *_eventHandler; + PendingMap _pendingQueue; + EventMap _eventMap; + uint64_t _scheduledTime; + uint32_t _waitTime; + Listener _listener; + bool _connected; + Firebolt::Error _status; + }; +} From 86d530041bfc90f542061fece562377f47252917 Mon Sep 17 00:00:00 2001 From: parag-pv Date: Wed, 18 Sep 2024 18:04:05 -0400 Subject: [PATCH 2/4] Fixing the buffer overflow in rpc dereferencing --- .../cpp/src/shared/include/json_engine.h | 124 +++++++++++------- 1 file changed, 76 insertions(+), 48 deletions(-) diff --git a/languages/cpp/src/shared/include/json_engine.h b/languages/cpp/src/shared/include/json_engine.h index 79ab589d..763a9b5d 100644 --- a/languages/cpp/src/shared/include/json_engine.h +++ b/languages/cpp/src/shared/include/json_engine.h @@ -1,4 +1,4 @@ -// #include +#include #include #include "gtest/gtest.h" @@ -26,16 +26,14 @@ inline std::string capitalizeFirstChar(std::string str) { class JsonEngine { private: - std::fstream _file; + std::ifstream _file; nlohmann::json _data; public: JsonEngine() { - if (!_file.is_open()) - _file.open("../dist/firebolt-core-open-rpc.json"); - _file >> _data; + _data = read_json_from_file("../firebolt-core-open-rpc.json"); } ~JsonEngine(){ @@ -56,6 +54,72 @@ class JsonEngine return ""; } + json read_json_from_file(const std::string &filename) + { + std::ifstream file(filename); + if (!file.is_open()) + { + throw std::runtime_error("Could not open file: " + filename); + } + + json j; + file >> j; + return j; + } + + json resolve_reference(const json &full_schema, const std::string &ref) + { + if (ref.find("#/") != 0) + { + throw std::invalid_argument("Only internal references supported"); + } + + std::string path = ref.substr(2); + std::istringstream ss(path); + std::string token; + json current = full_schema; + + while (std::getline(ss, token, '/')) + { + if (current.contains(token)) + { + current = current[token]; + } + else + { + throw std::invalid_argument("Invalid reference path: " + ref); + } + } + + return current; + } + + json process_schema(json schema, const json &full_schema) + { + if (schema.is_object()) + { + if (schema.contains("$ref")) + { + std::string ref = schema["$ref"]; + schema = resolve_reference(full_schema, ref); + } + + for (auto &el : schema.items()) + { + el.value() = process_schema(el.value(), full_schema); + } + } + else if (schema.is_array()) + { + for (auto &el : schema) + { + el = process_schema(el, full_schema); + } + } + + return schema; + } + #ifndef UNIT_TEST @@ -88,58 +152,22 @@ class JsonEngine } else { std::cout << "Params is NOT empty" << std::endl; - const json openRPCSchema = method["params"][0]["schema"]; - std::cout << "Schema validator schema JSON: " << openRPCSchema.dump() << std::endl; - + const json currentSchema = method["params"][0]["schema"]; + std::cout << "schema JSON before $ref: " << currentSchema.dump() << std::endl; + + json dereferenced_schema = process_schema(currentSchema, _data); + std::cout << "schema JSON after $ref: " << dereferenced_schema.dump() << std::endl; + json_validator validator; try{ - validator.set_root_schema(openRPCSchema); + validator.set_root_schema(dereferenced_schema); validator.validate(requestParams); - // EXPECT_NO_THROW(validator.validate(requestParams)); // For usage without try catch std::cout << "Schema validation succeeded" << std::endl; } catch (const std::exception &e){ FAIL() << "Schema validation error: " << e.what() << std::endl; } } - - // DUMMY SCHEMA VALIDATION - TO BE REMOVED - // const json openRPCSchema = R"( - // { - // "title": "AdConfigurationOptions", - // "type": "object", - // "properties": { - // "coppa": { - // "type": "boolean", - // "description": "Whether or not the app requires US COPPA compliance." - // }, - // "environment": { - // "type": "string", - // "enum": [ - // "prod", - // "test" - // ], - // "default": "prod", - // "description": "Whether the app is running in a production or test mode." - // }, - // "authenticationEntity": { - // "type": "string", - // "description": "The authentication provider, when it is separate entity than the app provider, e.g. an MVPD." - // } - // } - // })"_json; - // const json requestParams = json::parse(message->Parameters.Value()); - // // const json requestParams = R"({"options":{}})"_json; - // json_validator validator; - // try{ - // validator.set_root_schema(openRPCSchema); - // validator.validate(requestParams); - // // EXPECT_NO_THROW(validator.validate(requestParams)); // For usage without try catch - // std::cout << "Schema validation succeeded" << std::endl; - // } - // catch (const std::exception &e){ - // FAIL() << "Schema validation error: " << e.what() << std::endl; - // } } } } From 1e1752756b89eec4db19849590f679c1eaaeee8d Mon Sep 17 00:00:00 2001 From: parag-pv Date: Wed, 18 Sep 2024 21:54:37 -0400 Subject: [PATCH 3/4] Updated schema validator to loop through params in request --- .../cpp/src/shared/include/json_engine.h | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/languages/cpp/src/shared/include/json_engine.h b/languages/cpp/src/shared/include/json_engine.h index 763a9b5d..c851cd6b 100644 --- a/languages/cpp/src/shared/include/json_engine.h +++ b/languages/cpp/src/shared/include/json_engine.h @@ -152,20 +152,26 @@ class JsonEngine } else { std::cout << "Params is NOT empty" << std::endl; - const json currentSchema = method["params"][0]["schema"]; - std::cout << "schema JSON before $ref: " << currentSchema.dump() << std::endl; - - json dereferenced_schema = process_schema(currentSchema, _data); - std::cout << "schema JSON after $ref: " << dereferenced_schema.dump() << std::endl; - json_validator validator; - try{ - validator.set_root_schema(dereferenced_schema); - validator.validate(requestParams); - std::cout << "Schema validation succeeded" << std::endl; - } - catch (const std::exception &e){ - FAIL() << "Schema validation error: " << e.what() << std::endl; + const json openRPCParams = method["params"]; + for (auto& item : openRPCParams.items()) { + std::string key = item.key(); + json currentSchema = item.value(); + std::string paramName = currentSchema["name"]; + std::cout << "paramName: " << paramName << std::endl; + if (requestParams.contains(paramName)) { + std::cout << "RequestParams contain paramName in rpc" << std::endl; + json dereferenced_schema = process_schema(currentSchema, _data); + std::cout << "schema JSON after $ref: " << dereferenced_schema.dump() << std::endl; + try{ + validator.set_root_schema(dereferenced_schema["schema"]); + validator.validate(requestParams[paramName]); + std::cout << "Schema validation succeeded" << std::endl; + } + catch (const std::exception &e){ + FAIL() << "Schema validation error: " << e.what() << std::endl; + } + } } } } From d120d22c31f31dde6f1c55dcc8964866070e14b3 Mon Sep 17 00:00:00 2001 From: parag-pv Date: Sun, 22 Sep 2024 16:40:20 -0400 Subject: [PATCH 4/4] Removing commented code, adding ifdef conditions --- .../src/shared/src/Transport/MockTransport.h | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/languages/cpp/src/shared/src/Transport/MockTransport.h b/languages/cpp/src/shared/src/Transport/MockTransport.h index ff0d2c5b..ec28c1ac 100644 --- a/languages/cpp/src/shared/src/Transport/MockTransport.h +++ b/languages/cpp/src/shared/src/Transport/MockTransport.h @@ -23,9 +23,6 @@ #include "error.h" #include "json_engine.h" -// #define MY_DEBUG(message, value) -// #define MY_DEBUG(message, value) std::cout << "[MyDebug] " << __FILE__ << " "<< __func__ << "() " << message << ": "<< value << std::endl; - namespace FireboltSDK { @@ -396,13 +393,11 @@ namespace FireboltSDK #ifdef UNIT_TEST void Submit(const WPEFramework::Core::ProxyType &message) { - std::cout << "Inside Transport Submit function 1" << std::endl; _channel.Submit(message); } #else void Submit(const WPEFramework::Core::ProxyType &message) { - std::cout << "Inside Mock Transport Submit function 1" << std::endl; const WPEFramework::Core::JSONRPC::Message* jsonRpcMessage = dynamic_cast(message.operator->()); std::unique_ptr jsonEngine = std::make_unique(); jsonEngine->MockRequest(jsonRpcMessage); @@ -420,12 +415,19 @@ namespace FireboltSDK { Close(); } + +#ifdef UNIT_TEST + bool IsOpen() + { + return (_channel.IsOpen() == true); + } +#else bool IsOpen() { - // return (_channel.IsOpen() == true); return true; } - +#endif + protected: void StateChange() { @@ -445,15 +447,22 @@ namespace FireboltSDK } _adminLock.Unlock(); } + +#ifdef UNIT_TEST + bool Open(const uint32_t waitTime) + { + bool result = true; + if (_channel.IsClosed() == true) { + result = (_channel.Open(waitTime) == WPEFramework::Core::ERROR_NONE); + } + return (result); + } +#else bool Open(const uint32_t waitTime) { - // bool result = true; - // if (_channel.IsClosed() == true) { - // result = (_channel.Open(waitTime) == WPEFramework::Core::ERROR_NONE); - // } - // return (result); return true; } +#endif void Close() { _channel.Close(WPEFramework::Core::infinite); @@ -596,11 +605,18 @@ namespace FireboltSDK } public: + +#ifdef UNIT_TEST + inline bool IsOpen() + { + return _channel->IsOpen(); + } +#else inline bool IsOpen() { - // return _channel->IsOpen(); return true; } +#endif void Revoke(const string &eventName) { @@ -618,7 +634,6 @@ namespace FireboltSDK template Firebolt::Error Invoke(const string& method, const PARAMETERS& parameters, RESPONSE& response) { - std::cout << "Inside OG Transport Invoke function" << std::endl; Entry slot; uint32_t id = _channel->Sequence(); Firebolt::Error result = Send(method, parameters, id); @@ -635,7 +650,6 @@ namespace FireboltSDK std::cout << "Inside Mock Transport Invoke function" << std::endl; Entry slot; uint32_t id = _channel->Sequence(); - std::cout << "Inside Mock Transport Invoke function - id: " << id << std::endl; Firebolt::Error result = Send(method, parameters, id); WPEFramework::Core::JSONRPC::Message message; @@ -643,8 +657,6 @@ namespace FireboltSDK std::unique_ptr jsonEngine = std::make_unique(); result = jsonEngine->MockResponse(message, response); FromMessage((INTERFACE *)&response, message); - - // return Firebolt::Error::None; return (result); } #endif @@ -884,7 +896,6 @@ namespace FireboltSDK template Firebolt::Error Send(const string &method, const PARAMETERS ¶meters, const uint32_t &id) { - std::cout << "Inside Transport Send function" << std::endl; int32_t result = WPEFramework::Core::ERROR_UNAVAILABLE; if ((_channel.IsValid() == true) && (_channel->IsSuspended() == true)) @@ -1073,4 +1084,4 @@ namespace FireboltSDK bool _connected; Firebolt::Error _status; }; -} +} \ No newline at end of file