diff --git a/CMakeLists.txt b/CMakeLists.txt index 0babd81..bc2609c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,5 +9,7 @@ endif() project(up2date-cpp) # Add sub directories -add_subdirectory(hawkbit) +add_subdirectory(modules) +add_subdirectory(dps) +add_subdirectory(ddi) add_subdirectory(example) diff --git a/README.md b/README.md index 51b1e27..b1c64cb 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [RITMS UP2DATE](https://ritms.online) is a cloud ready solution for unified software and firmware management. Use this for implementing lifecycle management for the full stack of drivers and firmware of connected devices. -RITMS UP2DATE is based on open and worldwide adopted building blocks, the most important is [Eclipse Hawkbit](https://www.eclipse.org/hawkbit/) which provides open and flexible Direct Device Integration (DDI) API and Management API. +RITMS UP2DATE is based on open and worldwide adopted building blocks, the most important is [Eclipse Hawkbit](https://www.eclipse.org/ddi/) which provides open and flexible Direct Device Integration (DDI) API and Management API. RITMS UP2DATE extends Eclipse Hawkbit API with zero-cost maintenance device provisioning based on X509 certificates. The Public Key Infrastructure deployed to cloud governs digital certificates to secure end-to-end communications. Devices are automatically provisioned to connect the update service in a secure way. diff --git a/ddi/CMakeLists.txt b/ddi/CMakeLists.txt new file mode 100644 index 0000000..c462a66 --- /dev/null +++ b/ddi/CMakeLists.txt @@ -0,0 +1,21 @@ +project (ddi LANGUAGES CXX) + +set(VCPKG_FEATURE_FLAGS "manifests") +# Add a library with the above sources +file(GLOB SOURCES "src/*.cpp") +add_library(${PROJECT_NAME} ${SOURCES}) +add_library(sub::ddi ALIAS ${PROJECT_NAME}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) + +target_include_directories( ${PROJECT_NAME} + PUBLIC ${PROJECT_SOURCE_DIR}/include +) + +find_package(RapidJSON CONFIG REQUIRED) + +target_link_libraries( ${PROJECT_NAME} PRIVATE + sub::modules + rapidjson +) + diff --git a/ddi/include/ddi.hpp b/ddi/include/ddi.hpp new file mode 100644 index 0000000..21308b5 --- /dev/null +++ b/ddi/include/ddi.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "ddi/hawkbit_response.hpp" +#include "ddi/ddi_client.hpp" diff --git a/ddi/include/ddi/ddi_client.hpp b/ddi/include/ddi/ddi_client.hpp new file mode 100644 index 0000000..a844add --- /dev/null +++ b/ddi/include/ddi/ddi_client.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include +#include + +#include "hawkbit_event_handler.hpp" + +namespace ddi { + // Main class that used for communication with hawkBit + class Client { + public: + + // infinity loop + virtual void run() = 0; + + virtual ~Client() = default; + }; + + class AuthRestoreHandler { + public: + + virtual void setTLS(const std::string &crt, const std::string &key) = 0; + + // should be used full url which contains controllerId + virtual void setEndpoint(const std::string &endpoint) = 0; + // default value for tenant will be inherited for all child classes + virtual void setEndpoint(std::string &hawkbitEndpoint, + const std::string &controllerId, const std::string &tenant = "default") = 0; + + virtual void setDeviceToken(const std::string &) = 0; + + virtual void setGatewayToken(const std::string &) = 0; + + virtual ~AuthRestoreHandler() = default; + }; + + class AuthErrorHandler { + public: + virtual void onAuthError(std::unique_ptr) = 0; + + virtual ~AuthErrorHandler() = default; + }; + + class DDIClientBuilder { + public: + static std::unique_ptr newInstance(); + + virtual DDIClientBuilder *setDefaultPollingTimeout(int pollingTimeout_) = 0; + + virtual DDIClientBuilder *setEventHandler(std::shared_ptr handler) = 0; + + virtual DDIClientBuilder *addHeader(const std::string &, const std::string &) = 0; + + virtual DDIClientBuilder *setGatewayToken(const std::string &) = 0; + + virtual DDIClientBuilder *setDeviceToken(const std::string &) = 0; + + virtual DDIClientBuilder *notVerifyServerCertificate() = 0; + + virtual DDIClientBuilder *setHawkbitEndpoint(const std::string &) = 0; + + virtual DDIClientBuilder *setAuthErrorHandler(std::shared_ptr) = 0; + + virtual DDIClientBuilder *setTLS(const std::string &, const std::string &) = 0; + + // all child classes will have the same default tenant value + virtual DDIClientBuilder *setHawkbitEndpoint(const std::string &endpoint, + const std::string &controllerId_, + const std::string &tenant_ = "default") = 0; + + virtual std::unique_ptr build() = 0; + + virtual ~DDIClientBuilder() = default; + }; + +} diff --git a/hawkbit/include/hawkbit/hawkbit_actions.hpp b/ddi/include/ddi/hawkbit_actions.hpp similarity index 99% rename from hawkbit/include/hawkbit/hawkbit_actions.hpp rename to ddi/include/ddi/hawkbit_actions.hpp index 2e97df4..00657a9 100644 --- a/hawkbit/include/hawkbit/hawkbit_actions.hpp +++ b/ddi/include/ddi/hawkbit_actions.hpp @@ -3,7 +3,7 @@ #include #include -namespace hawkbit { +namespace ddi { // This part contains actions that will be given to the EventHandler as callback params // Do not copy or use these classes outside the handler class diff --git a/hawkbit/include/hawkbit/hawkbit_event_handler.hpp b/ddi/include/ddi/hawkbit_event_handler.hpp similarity index 96% rename from hawkbit/include/hawkbit/hawkbit_event_handler.hpp rename to ddi/include/ddi/hawkbit_event_handler.hpp index e4126d7..06cd589 100644 --- a/hawkbit/include/hawkbit/hawkbit_event_handler.hpp +++ b/ddi/include/ddi/hawkbit_event_handler.hpp @@ -5,7 +5,7 @@ #include "hawkbit_response.hpp" #include "hawkbit_actions.hpp" -namespace hawkbit { +namespace ddi { // user defined event handler for hawkBit client class EventHandler { diff --git a/hawkbit/include/hawkbit/exceptions.hpp b/ddi/include/ddi/hawkbit_exceptions.hpp similarity index 76% rename from hawkbit/include/hawkbit/exceptions.hpp rename to ddi/include/ddi/hawkbit_exceptions.hpp index c5cbd6c..1554700 100644 --- a/hawkbit/include/hawkbit/exceptions.hpp +++ b/ddi/include/ddi/hawkbit_exceptions.hpp @@ -1,6 +1,6 @@ #pragma once -namespace hawkbit { +namespace ddi { const int HTTP_UNAUTHORIZED = 401; const int HTTP_OK = 200; const int HTTP_CREATED = 201; @@ -46,12 +46,26 @@ namespace hawkbit { } }; - class http_lib_error: public std::exception { + class http_lib_error : public std::exception { std::string message; public: - http_lib_error(int error_num) { + explicit http_lib_error(int error_num) { message = "HTTP request error. Error code " + std::to_string(error_num); } + + const char *what() const noexcept override { + return message.c_str(); + } + }; + + // to catch and handle with on auth error handler + class client_initialize_error : public std::exception { + std::string message; + public: + explicit client_initialize_error(const std::string &msg) { + message = "Client not initialized properly: " + msg; + } + const char *what() const noexcept override { return message.c_str(); } diff --git a/hawkbit/include/hawkbit/hawkbit_response.hpp b/ddi/include/ddi/hawkbit_response.hpp similarity index 97% rename from hawkbit/include/hawkbit/hawkbit_response.hpp rename to ddi/include/ddi/hawkbit_response.hpp index 0bf551c..11dfb19 100644 --- a/hawkbit/include/hawkbit/hawkbit_response.hpp +++ b/ddi/include/ddi/hawkbit_response.hpp @@ -6,7 +6,7 @@ #include -namespace hawkbit { +namespace ddi { class ResponseDeliveryListener { public: virtual void onSuccessfulDelivery() = 0; @@ -33,7 +33,7 @@ namespace hawkbit { static std::string executionToString(const Execution &); - virtual Finished getFinished() = 0; + virtual Finished getFinished() = 0; virtual Execution getExecution() = 0; diff --git a/hawkbit/src/acions_impl.cpp b/ddi/src/acions_impl.cpp similarity index 90% rename from hawkbit/src/acions_impl.cpp rename to ddi/src/acions_impl.cpp index 6999bf4..f53b8db 100644 --- a/hawkbit/src/acions_impl.cpp +++ b/ddi/src/acions_impl.cpp @@ -3,10 +3,10 @@ #include "actions_impl.hpp" #include "rapidjson/document.h" -#include "hawkbit_client_impl.hpp" +#include "ddi_client_impl.hpp" #include "utils.hpp" -namespace hawkbit { +namespace ddi { int CancelAction_::getId() { return id; } @@ -15,7 +15,7 @@ namespace hawkbit { return stopId; } - std::unique_ptr CancelAction_::fromString(const std::string& body) { + std::unique_ptr CancelAction_::fromString(const std::string &body) { rapidjson::Document document; document.Parse<0>(body.c_str()); @@ -48,8 +48,8 @@ namespace hawkbit { throw unexpected_payload(); if (!document.HasMember("id") || !document.HasMember("deployment") || - !document["deployment"].HasMember("update") || !document["deployment"].HasMember("download") - || !document["deployment"].HasMember("chunks")) { + !document["deployment"].HasMember("update") || !document["deployment"].HasMember("download") + || !document["deployment"].HasMember("chunks")) { throw unexpected_payload(); } @@ -68,9 +68,9 @@ namespace hawkbit { deploymentBase->inMaintenanceWindow = true; } - const rapidjson::Value& chunks_ = document["deployment"]["chunks"]; + const rapidjson::Value &chunks_ = document["deployment"]["chunks"]; for (rapidjson::Value::ConstValueIterator itr = chunks_.Begin(); itr != chunks_.End(); ++itr) { - const rapidjson::Value& chunk = *itr; + const rapidjson::Value &chunk = *itr; if (!chunk.HasMember("part") || !chunk.HasMember("version") || !chunk.HasMember("name") || !chunk.HasMember("artifacts")) { throw unexpected_payload(); @@ -83,17 +83,17 @@ namespace hawkbit { chunkR->version = chunk["version"].GetString(); chunkR->part = chunk["part"].GetString(); - const rapidjson::Value& artifacts_ = chunk["artifacts"]; + const rapidjson::Value &artifacts_ = chunk["artifacts"]; for (rapidjson::Value::ConstValueIterator itr_a = artifacts_.Begin(); itr_a != artifacts_.End(); ++itr_a) { - const rapidjson::Value& artifact = *itr_a; + const rapidjson::Value &artifact = *itr_a; if (!artifact.HasMember("filename") || !artifact.HasMember("hashes") || !artifact.HasMember("size") - || !artifact.HasMember("_links") || !artifact["hashes"].HasMember("sha256") - || !artifact["hashes"].HasMember("sha1") || !artifact["hashes"].HasMember("md5") - || !artifact["_links"].HasMember("download-http")) { + || !artifact.HasMember("_links") || !artifact["hashes"].HasMember("sha256") + || !artifact["hashes"].HasMember("sha1") || !artifact["hashes"].HasMember("md5") + || !artifact["_links"].HasMember("download-http")) { throw unexpected_payload(); } Hashes hashesR; - auto artifactR = new Artifact_(); + auto artifactR = new Artifact_(); auto artifactPtr = std::shared_ptr(artifactR); artifactR->filename = artifact["filename"].GetString(); artifactR->fileSize = artifact["size"].GetInt(); @@ -113,7 +113,6 @@ namespace hawkbit { } - int DeploymentBase_::getId() { return id; } diff --git a/hawkbit/src/actions_impl.hpp b/ddi/src/actions_impl.hpp similarity index 95% rename from hawkbit/src/actions_impl.hpp rename to ddi/src/actions_impl.hpp index 0f30c86..1ae0b04 100644 --- a/hawkbit/src/actions_impl.hpp +++ b/ddi/src/actions_impl.hpp @@ -1,11 +1,11 @@ #pragma once #include -#include "hawkbit/hawkbit_actions.hpp" +#include "ddi/hawkbit_actions.hpp" #include "uriparse.hpp" #include "httplib.h" -namespace hawkbit { +namespace ddi { // Define actions from hawkBit enum Actions_ { GET_CONFIG_DATA, CANCEL_ACTION, DEPLOYMENT_BASE, @@ -49,7 +49,7 @@ namespace hawkbit { // used for get httpClient and its Headers class DownloadProvider { public: - virtual void downloadTo(uri::URI, const std::string&) = 0; + virtual void downloadTo(uri::URI, const std::string &) = 0; // get file as string virtual std::string getBody(uri::URI) = 0; diff --git a/ddi/src/ddi_client_builder.cpp b/ddi/src/ddi_client_builder.cpp new file mode 100644 index 0000000..2aed042 --- /dev/null +++ b/ddi/src/ddi_client_builder.cpp @@ -0,0 +1,113 @@ +#include "ddi/ddi_client.hpp" +#include "ddi_client_impl.hpp" +#include "uriparse.hpp" +#include "utils.hpp" + +namespace ddi { + std::unique_ptr DDIClientBuilder::newInstance() { + return std::unique_ptr(new DefaultClientBuilderImpl()); + } + + DDIClientBuilder *DefaultClientBuilderImpl::setEventHandler(std::shared_ptr handler_) { + handler = handler_; + + return this; + } + + DDIClientBuilder *DefaultClientBuilderImpl::setHawkbitEndpoint(const std::string &endpoint) { + this->hawkbitUri = endpoint; + + return this; + } + + DDIClientBuilder *DefaultClientBuilderImpl::setHawkbitEndpoint(const std::string &endpoint, + const std::string &controllerId, + const std::string &tenant) { + + return setHawkbitEndpoint(hawkbitEndpointFrom(endpoint, controllerId, tenant)); + } + + DDIClientBuilder *DefaultClientBuilderImpl::setDefaultPollingTimeout(int pollingTimeout_) { + pollingTimeout = pollingTimeout_; + + return this; + } + + DDIClientBuilder *DefaultClientBuilderImpl::addHeader(const std::string &k, const std::string &v) { + defaultHeaders.insert({k, v}); + + return this; + } + + DDIClientBuilder *DefaultClientBuilderImpl::setGatewayToken(const std::string &token_) { + if (authVariant != AuthorizeVariants::NOT_SET) { + throw std::runtime_error("Another authority type is already set"); + } + + token = token_; + authVariant = AuthorizeVariants::GATEWAY_TOKEN; + + return this; + } + + DDIClientBuilder *DefaultClientBuilderImpl::setDeviceToken(const std::string &token_) { + if (authVariant != AuthorizeVariants::NOT_SET) { + throw std::runtime_error("Another authority type is already set"); + } + + token = token_; + authVariant = AuthorizeVariants::DEVICE_TOKEN; + + return this; + } + + DDIClientBuilder *DefaultClientBuilderImpl::setTLS(const std::string &crt_, const std::string &key_) { + if (authVariant != AuthorizeVariants::NOT_SET) { + throw std::runtime_error("Another authority type is already set"); + } + crt = crt_; + key = key_; + authVariant = AuthorizeVariants::M_TLS_KEYPAIR; + + return this; + } + + DDIClientBuilder *DefaultClientBuilderImpl::notVerifyServerCertificate() { + verifyServerCertificate = false; + + return this; + } + + + DDIClientBuilder *DefaultClientBuilderImpl::setAuthErrorHandler(std::shared_ptr e) { + authErrorHandler = e; + + return this; + } + + std::unique_ptr DefaultClientBuilderImpl::build() { + auto cli = new HawkbitCommunicationClient(); + auto cliPtr = std::unique_ptr(cli); + + if (!hawkbitUri.empty()) + cli->setEndpoint(hawkbitUri); + + cli->defaultSleepTime = pollingTimeout; + cli->currentSleepTime = pollingTimeout; + cli->handler = handler; + cli->defaultHeaders = defaultHeaders; + cli->serverCertificateVerify = verifyServerCertificate; + cli->authErrorHandler = authErrorHandler; + + if (authVariant == AuthorizeVariants::M_TLS_KEYPAIR) { + cli->setTLS(crt, key); + } else if (authVariant == AuthorizeVariants::GATEWAY_TOKEN) { + cli->setGatewayToken(token); + } else if (authVariant == AuthorizeVariants::DEVICE_TOKEN) { + cli->setDeviceToken(token); + } + + return cliPtr; + } + +} \ No newline at end of file diff --git a/hawkbit/src/hawkbit_client_impl.cpp b/ddi/src/ddi_client_impl.cpp similarity index 54% rename from hawkbit/src/hawkbit_client_impl.cpp rename to ddi/src/ddi_client_impl.cpp index 22c47c6..87d65ed 100644 --- a/hawkbit/src/hawkbit_client_impl.cpp +++ b/ddi/src/ddi_client_impl.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #define RAPIDJSON_HAS_STDSTRING 1 @@ -8,13 +9,18 @@ #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" -#include "hawkbit_client_impl.hpp" +#include "ddi_client_impl.hpp" #include "response_impl.hpp" #include "actions_impl.hpp" #include "utils.hpp" -namespace hawkbit { +namespace ddi { + + const char *AUTHORIZATION_HEADER = "Authorization"; + const char *GATEWAY_TOKEN_HEADER = "GatewayToken"; + const char *TARGET_TOKEN_HEADER = "TargetToken"; + void checkHttpCode(int presented, int expected) { if (presented == HTTP_UNAUTHORIZED) throw unauthorized_exception(); @@ -22,8 +28,41 @@ namespace hawkbit { throw http_unexpected_code_exception(presented, expected); } + class AuthRestoreHandler_ : public AuthRestoreHandler { + HawkbitCommunicationClient *cli; + public: + explicit AuthRestoreHandler_(HawkbitCommunicationClient *cli_) : cli(cli_) {} + + void setTLS(const std::string &crt, const std::string &key) override { + cli->setTLS(crt, key); + } + + void setEndpoint(const std::string &endpoint) override { + cli->setEndpoint(endpoint); + } + + void setDeviceToken(const std::string &token) override { + cli->setDeviceToken(token); + } + + void setGatewayToken(const std::string &token) override { + cli->setGatewayToken(token); + } + + void setEndpoint(std::string &hawkbitEndpoint, const std::string &controllerId, + const std::string &tenant = "default") override { + + cli->setEndpoint(hawkbitEndpoint, controllerId, tenant); + }; + }; [[noreturn]] void HawkbitCommunicationClient::run() { + if (hawkbitURI.isEmpty()) { + if (!authErrorHandler) throw client_initialize_error("endpoint or AuthErrorHandler is not set"); + authErrorHandler->onAuthError( + std::make_unique(this)); + } + while (true) { ignoreSleep = false; doPoll(); @@ -32,12 +71,32 @@ namespace hawkbit { } } - httplib::Client HawkbitCommunicationClient::newHttpClient(uri::URI &hostEndpoint) { + httplib::Client HawkbitCommunicationClient::newHttpClient(uri::URI &hostEndpoint) const { + // key pair auth + if (mTLSKeypair.isSet) { + BIO *bio_crt = BIO_new(BIO_s_mem()); + BIO_puts(bio_crt, mTLSKeypair.crt.c_str()); + X509 *certificate = PEM_read_bio_X509(bio_crt, nullptr, nullptr, nullptr); + BIO_free(bio_crt); - return httplib::Client(hostEndpoint.getScheme() + "://" + hostEndpoint.getAuthority()); + BIO *bio_key = BIO_new(BIO_s_mem()); + BIO_puts(bio_key, mTLSKeypair.key.c_str()); + EVP_PKEY *key = PEM_read_bio_PrivateKey(bio_key, nullptr, nullptr, nullptr); + BIO_free(bio_key); + + + return httplib::Client(hostEndpoint.getScheme() + "://" + hostEndpoint.getAuthority(), + certificate, key); + } + + auto cli = httplib::Client(hostEndpoint.getScheme() + "://" + hostEndpoint.getAuthority()); + cli.enable_server_certificate_verification(serverCertificateVerify); + + return cli; } - void fillResponseDocument(Response *response, rapidjson::Document &document) { + // set actionId here (hawkbit api requires it but in docs not) + void fillResponseDocument(Response *response, rapidjson::Document &document, int actionId) { if (response == nullptr) { throw wrong_response(); } @@ -57,6 +116,10 @@ namespace hawkbit { status.AddMember("details", details, document.GetAllocator()); document.AddMember("status", status, document.GetAllocator()); + + if (actionId >= 0) { + document.AddMember("id", std::to_string(actionId), document.GetAllocator()); + } } void HawkbitCommunicationClient::followConfigData(uri::URI &followURI) { @@ -81,14 +144,14 @@ namespace hawkbit { auto builder = ResponseBuilder::newInstance(); auto resp = builder->setFinished(Response::SUCCESS)->setExecution(Response::CLOSED)->build(); - fillResponseDocument(resp.get(), document); + fillResponseDocument(resp.get(), document, -1); rapidjson::StringBuffer buf; rapidjson::Writer writer(buf); document.Accept(writer); retryHandler(followURI, [&](httplib::Client &cli) { - return cli.Put(followURI.getPath().c_str(),defaultHeaders, buf.GetString(), "application/json"); + return cli.Put(followURI.getPath().c_str(), defaultHeaders, buf.GetString(), "application/json"); }); ignoreSleep = req->isIgnoredSleep(); @@ -110,12 +173,14 @@ namespace hawkbit { return cli.Get(followURI.getPath().c_str(), defaultHeaders); }); - auto cliResp = handler->onCancelAction(CancelAction_::fromString(resp->body)); + auto cancelAction = CancelAction_::fromString(resp->body); + auto actionId = cancelAction->getId(); + auto cliResp = handler->onCancelAction(std::move(cancelAction)); rapidjson::Document document; document.SetObject(); - fillResponseDocument(cliResp.get(), document); + fillResponseDocument(cliResp.get(), document, actionId); rapidjson::StringBuffer buf; rapidjson::Writer writer(buf); @@ -142,15 +207,17 @@ namespace hawkbit { } void HawkbitCommunicationClient::followDeploymentBase(uri::URI &followURI) { - auto resp = retryHandler(followURI, [&](httplib::Client &cli) { + auto resp = retryHandler(followURI, [&](httplib::Client &cli) { return cli.Get(followURI.getPath().c_str(), defaultHeaders); }); - auto cliResp = handler->onDeploymentAction(DeploymentBase_::from(resp->body, this)); + auto deploymentBase = DeploymentBase_::from(resp->body, this); + auto actionId = deploymentBase->getId(); + auto cliResp = handler->onDeploymentAction(std::move(deploymentBase)); rapidjson::Document document; document.SetObject(); - fillResponseDocument(cliResp.get(), document); + fillResponseDocument(cliResp.get(), document, actionId); rapidjson::StringBuffer buf; rapidjson::Writer writer(buf); @@ -198,49 +265,101 @@ namespace hawkbit { } } - void HawkbitCommunicationClient::downloadTo(uri::URI downloadURI, const std::string& path) { - std::ofstream file(path); - retryHandler(downloadURI, [&](httplib::Client &cli) { - return cli.Get(downloadURI.getPath().c_str(), defaultHeaders, - [] (const httplib::Response &r) { - checkHttpCode(r.status, HTTP_OK); - return true; - }, - [&] (const char * data, size_t size) { - file.write(data, size); - return !file.bad(); - } - ); - }); + void HawkbitCommunicationClient::downloadTo(uri::URI downloadURI, const std::string &path) { + std::ofstream file(path, std::ios::binary); + retryHandler(downloadURI, [&](httplib::Client &cli) { + return cli.Get(downloadURI.getPath().c_str(), defaultHeaders, + [](const httplib::Response &r) { + checkHttpCode(r.status, HTTP_OK); + return true; + }, + [&](const char *data, size_t size) { + file.write(data, size); + return !file.bad(); + } + ); + }); } std::string HawkbitCommunicationClient::getBody(uri::URI downloadURI) { - return retryHandler(downloadURI, [&](httplib::Client &cli){ + return retryHandler(downloadURI, [&](httplib::Client &cli) { return cli.Get(downloadURI.getPath().c_str(), defaultHeaders); })->body; } - void HawkbitCommunicationClient::downloadWithReceiver(uri::URI downloadURI, std::function func) { + void HawkbitCommunicationClient::downloadWithReceiver(uri::URI downloadURI, + std::function func) { retryHandler(downloadURI, [&](httplib::Client &cli) { - return cli.Get(downloadURI.getPath().c_str(), defaultHeaders, - [] (const httplib::Response &r) { + return cli.Get(downloadURI.getPath().c_str(), defaultHeaders, + [](const httplib::Response &r) { checkHttpCode(r.status, HTTP_OK); return true; }, func - ); + ); }); } - httplib::Result HawkbitCommunicationClient::retryHandler(uri::URI reqUri, const std::function &func) { + httplib::Result HawkbitCommunicationClient::wrappedRequest(uri::URI reqUri, const std::function &func) { auto cli = newHttpClient(reqUri); auto resp = func(cli); if (resp.error() != httplib::Error::Success) { - throw http_lib_error((int)resp.error()); + throw http_lib_error((int) resp.error()); } checkHttpCode(resp->status, HTTP_OK); return resp; } + httplib::Result HawkbitCommunicationClient::retryHandler(uri::URI reqUri, const std::function &func) { + try { + return wrappedRequest(reqUri, func); + } catch (unauthorized_exception &e) { + if (!authErrorHandler) throw e; + authErrorHandler->onAuthError( + std::make_unique(this)); + } + + return wrappedRequest(reqUri, func);; + } + + void HawkbitCommunicationClient::setTLS(const std::string &crt, const std::string &key) { + mTLSKeypair.isSet = true; + mTLSKeypair.crt = crt; + mTLSKeypair.key = key; + defaultHeaders.erase(AUTHORIZATION_HEADER); + } + + std::string formatAuthHeader(const std::string &authType, const std::string &val) { + return authType + " " + val; + } + + void HawkbitCommunicationClient::setEndpoint(const std::string &endpoint) { + hawkbitURI = uri::URI::fromString(endpoint); + } + + void HawkbitCommunicationClient::setDeviceToken(const std::string &token) { + defaultHeaders.insert({AUTHORIZATION_HEADER, + formatAuthHeader(TARGET_TOKEN_HEADER, token)}); + mTLSKeypair.isSet = false; + mTLSKeypair.crt = ""; + mTLSKeypair.key = ""; + } + + void HawkbitCommunicationClient::setGatewayToken(const std::string &token) { + defaultHeaders.insert({AUTHORIZATION_HEADER, + formatAuthHeader(GATEWAY_TOKEN_HEADER, token)}); + mTLSKeypair.isSet = false; + mTLSKeypair.crt = ""; + mTLSKeypair.key = ""; + } + + void HawkbitCommunicationClient::setEndpoint(std::string &hawkbitEndpoint, const std::string &controllerId, + const std::string &tenant) { + + setEndpoint(hawkbitEndpointFrom(hawkbitEndpoint, controllerId, tenant)); + } + } \ No newline at end of file diff --git a/ddi/src/ddi_client_impl.hpp b/ddi/src/ddi_client_impl.hpp new file mode 100644 index 0000000..8b6dbe3 --- /dev/null +++ b/ddi/src/ddi_client_impl.hpp @@ -0,0 +1,134 @@ +#pragma once + +#include +#include + +#include "httplib.h" +#include "uriparse.hpp" +#include "ddi/hawkbit_event_handler.hpp" +#include "ddi/hawkbit_exceptions.hpp" +#include "actions_impl.hpp" +#include "ddi/ddi_client.hpp" + +namespace ddi { + + struct PollingData_; + + class HawkbitCommunicationClient : public DownloadProvider, public Client, public AuthRestoreHandler { + protected: + uri::URI hawkbitURI; + + httplib::Headers defaultHeaders; + + std::shared_ptr handler; + std::shared_ptr authErrorHandler; + + int defaultSleepTime; + int currentSleepTime; + + bool ignoreSleep; + + bool serverCertificateVerify = true; + + struct { + std::string crt; + std::string key; + bool isSet = false; + } mTLSKeypair; + + // starting hawkbit communication logic. + // returns execute time in ms + void doPoll(); + + // call user-defined handler and send config data to hawkBit + void followConfigData(uri::URI &); + + // call user-defined handler to process cancelAction and send response to hawkBit + void followCancelAction(uri::URI &); + + // call user-defined handler to process deploymentBase and send response to hawkBit + void followDeploymentBase(uri::URI &); + + // all requests should go via retryHandler + httplib::Result wrappedRequest(uri::URI, const std::function &); + + httplib::Result retryHandler(uri::URI, const std::function &); + + // creates httpClient with predefined params + httplib::Client newHttpClient(uri::URI &) const; + + public: + + [[noreturn]] virtual void run() override; + + void downloadTo(uri::URI uri, const std::string &path) override; + + std::string getBody(uri::URI uri) override; + + void downloadWithReceiver(uri::URI uri, std::function function) override; + + void setTLS(const std::string &crt, const std::string &key) override; + + void setEndpoint(const std::string &endpoint) override; + + void setEndpoint(std::string &hawkbitEndpoint, + const std::string &controllerId, const std::string &tenant = "default") override; + + void setDeviceToken(const std::string &string) override; + + void setGatewayToken(const std::string &string) override; + + friend class DefaultClientBuilderImpl; + }; + + class DefaultClientBuilderImpl : public DDIClientBuilder { + std::string hawkbitUri; + + int pollingTimeout = 30000; + + httplib::Headers defaultHeaders; + + std::shared_ptr authErrorHandler; + std::shared_ptr handler; + + std::string token; + std::string crt, key; + + enum AuthorizeVariants { + NOT_SET, + GATEWAY_TOKEN, + DEVICE_TOKEN, + M_TLS_KEYPAIR + }; + + bool verifyServerCertificate = true; + + AuthorizeVariants authVariant = AuthorizeVariants::NOT_SET; + + public: + DDIClientBuilder *setHawkbitEndpoint(const std::string &) override; + + DDIClientBuilder *setDefaultPollingTimeout(int pollingTimeout_) override; + + DDIClientBuilder *setEventHandler(std::shared_ptr handler) override; + + DDIClientBuilder *addHeader(const std::string &, const std::string &) override; + + DDIClientBuilder *setGatewayToken(const std::string &) override; + + DDIClientBuilder *setDeviceToken(const std::string &) override; + + DDIClientBuilder *notVerifyServerCertificate() override; + + + DDIClientBuilder *setTLS(const std::string &crt, const std::string &key) override; + + DDIClientBuilder *setAuthErrorHandler(std::shared_ptr) override; + + DDIClientBuilder *setHawkbitEndpoint(const std::string &endpoint, + const std::string &controllerId_, const std::string &tenant_ = "default") override; + + std::unique_ptr build() override; + }; + +} \ No newline at end of file diff --git a/hawkbit/src/hawkbit_response.cpp b/ddi/src/hawkbit_response.cpp similarity index 98% rename from hawkbit/src/hawkbit_response.cpp rename to ddi/src/hawkbit_response.cpp index 766d016..e8e96cd 100644 --- a/hawkbit/src/hawkbit_response.cpp +++ b/ddi/src/hawkbit_response.cpp @@ -1,6 +1,6 @@ #include "response_impl.hpp" -namespace hawkbit { +namespace ddi { std::shared_ptr ConfigResponseBuilder::newInstance() { return std::shared_ptr(new ConfigResponseBuilderImpl()); } diff --git a/hawkbit/src/response_impl.cpp b/ddi/src/response_impl.cpp similarity index 99% rename from hawkbit/src/response_impl.cpp rename to ddi/src/response_impl.cpp index bb9c659..ae37870 100644 --- a/hawkbit/src/response_impl.cpp +++ b/ddi/src/response_impl.cpp @@ -1,6 +1,6 @@ #include "response_impl.hpp" -namespace hawkbit { +namespace ddi { Response::Finished ResponseImpl::getFinished() { return finished; } diff --git a/hawkbit/src/response_impl.hpp b/ddi/src/response_impl.hpp similarity index 97% rename from hawkbit/src/response_impl.hpp rename to ddi/src/response_impl.hpp index 9278a1e..2a3d2a5 100644 --- a/hawkbit/src/response_impl.hpp +++ b/ddi/src/response_impl.hpp @@ -1,6 +1,6 @@ -#include "hawkbit/hawkbit_response.hpp" +#include "ddi/hawkbit_response.hpp" -namespace hawkbit { +namespace ddi { class ResponseImpl : public Response { private: Finished finished; diff --git a/ddi/src/utils.cpp b/ddi/src/utils.cpp new file mode 100644 index 0000000..ccb0740 --- /dev/null +++ b/ddi/src/utils.cpp @@ -0,0 +1,20 @@ +#include "utils.hpp" +#include "ddi/hawkbit_exceptions.hpp" + +namespace ddi { + uri::URI parseHrefObject(const rapidjson::Value &hrefObject) { + try { + return uri::URI::fromString(hrefObject["href"].GetString()); + } catch (std::exception &) { + throw unexpected_payload(); + } + } + + std::string + hawkbitEndpointFrom(const std::string &endpoint, const std::string &controllerId_, const std::string &tenant_) { + auto hawkbitEndpoint = uri::URI::fromString(endpoint); + return hawkbitEndpoint.getScheme() + "://" + hawkbitEndpoint.getAuthority() + "/" + tenant_ + + "/controller/v1/" + + controllerId_; + } +} \ No newline at end of file diff --git a/ddi/src/utils.hpp b/ddi/src/utils.hpp new file mode 100644 index 0000000..8e11332 --- /dev/null +++ b/ddi/src/utils.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "rapidjson/document.h" +#include "uriparse.hpp" + + +namespace ddi { + + uri::URI parseHrefObject(const rapidjson::Value &hrefObject); + + std::string hawkbitEndpointFrom(const std::string &endpoint, const std::string &controllerId_, const std::string &tenant_); +} \ No newline at end of file diff --git a/dps/CMakeLists.txt b/dps/CMakeLists.txt new file mode 100644 index 0000000..95e45f9 --- /dev/null +++ b/dps/CMakeLists.txt @@ -0,0 +1,20 @@ +project (dps LANGUAGES CXX) + + +# Add a library with the above sources +file(GLOB SOURCES "src/*.cpp") +add_library(${PROJECT_NAME} ${SOURCES}) +add_library(sub::dps ALIAS ${PROJECT_NAME}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) + +find_package(RapidJSON CONFIG REQUIRED) + +target_include_directories( ${PROJECT_NAME} + PUBLIC ${PROJECT_SOURCE_DIR}/include +) + +target_link_libraries( ${PROJECT_NAME} + PRIVATE sub::modules + rapidjson +) \ No newline at end of file diff --git a/dps/include/ritms_dps.hpp b/dps/include/ritms_dps.hpp new file mode 100644 index 0000000..054fcd1 --- /dev/null +++ b/dps/include/ritms_dps.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +namespace ritms { + namespace dps { + + // mTLSKeyPair contains payload required to authorize in up2date services + class mTLSKeyPair { + public: + // get private key + virtual std::string getKey() = 0; + + // get public key + virtual std::string getCrt() = 0; + + virtual ~mTLSKeyPair() = default; + }; + + // ProvisioningData contains data received from up2date DPS service + class ProvisioningData { + public: + + // get mTLSKeyPair should be presented in provisioning data + virtual std::unique_ptr getKeyPair() = 0; + + // up2date service endpoint (for client tenant) + virtual std::string getUp2DateEndpoint() = 0; + + virtual ~ProvisioningData() = default; + }; + + // ProvisioningClient uses for provisioning in ritms service + class ProvisioningClient { + public: + + virtual std::unique_ptr doProvisioning() = 0; + + virtual ~ProvisioningClient() = default; + }; + + // ProvisioningClient builder for default cloud provisioning + class CloudProvisioningClientBuilder { + public: + + static std::unique_ptr newInstance(); + + // set client certificate (required) + virtual CloudProvisioningClientBuilder *setAuthCrt(const std::string &crt) = 0; + + // set provisioning endpoint (required) + virtual CloudProvisioningClientBuilder *setEndpoint(const std::string &endpoint) = 0; + + // can set additional headers for provisioning client + virtual CloudProvisioningClientBuilder *addHeader(const std::string &, const std::string &) = 0; + + virtual std::unique_ptr build() = 0; + }; + + } +} \ No newline at end of file diff --git a/dps/include/ritms_exceptions.hpp b/dps/include/ritms_exceptions.hpp new file mode 100644 index 0000000..f5d235d --- /dev/null +++ b/dps/include/ritms_exceptions.hpp @@ -0,0 +1,44 @@ +#include +#include + +namespace ritms { + namespace dps { + const int HTTP_UNAUTHORIZED = 401; + const int HTTP_OK = 200; + const int HTTP_CREATED = 201; + + class httplib_error: public std::exception { + std::string message; + public: + httplib_error(int error_num) { + message = "HTTPLib request error. Error code " + std::to_string(error_num); + } + const char *what() const noexcept override { + return message.c_str(); + } + }; + + class provisioning_error: public std::exception { + std::string message; + public: + provisioning_error(int error_num) { + message = "Provisioning request error. HTTP code " + std::to_string(error_num); + } + const char *what() const noexcept override { + return message.c_str(); + } + }; + + class up2date_cloud_error: public std::exception { + std::string message; + public: + up2date_cloud_error(const std::string &msg) { + message = "Error from cloud: " + msg; + } + + const char *what() const noexcept override { + return message.c_str(); + } + }; + } +} \ No newline at end of file diff --git a/dps/src/provisioning_client_impl.cpp b/dps/src/provisioning_client_impl.cpp new file mode 100644 index 0000000..8521ec0 --- /dev/null +++ b/dps/src/provisioning_client_impl.cpp @@ -0,0 +1,59 @@ +#include + +#include "ritms_dps_impl.hpp" +#include "ritms_exceptions.hpp" +#include "httplib.h" + +#define RAPIDJSON_HAS_STDSTRING 1 + +#include "rapidjson/document.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" + + +namespace ritms { + namespace dps { + + std::string ProvisioningClient_impl::formatCertificateUpdatePayload() { + rapidjson::Document document; + document.SetObject(); + document.AddMember("tq", crt, document.GetAllocator()); + + rapidjson::StringBuffer buf; + rapidjson::Writer writer(buf); + document.Accept(writer); + + return buf.GetString(); + } + + std::unique_ptr ProvisioningClient_impl::doProvisioning() { + auto resp = httplib::Client(provisioningURI.getScheme() + + "://" + provisioningURI.getAuthority()) + .Post(provisioningURI.getPath().c_str(), provisioningHeaders, + formatCertificateUpdatePayload(), "application/json"); + if (resp.error() != httplib::Error::Success) { + throw httplib_error((int)resp.error()); + } + if (resp->status != HTTP_OK && resp->status != HTTP_CREATED) { + throw provisioning_error(resp->status); + } + + rapidjson::Document document; + document.Parse<0>(resp->body.c_str()); + + if (document.HasParseError() || !document.HasMember("crt") + || !document.HasMember("endpoint") || !document.HasMember("key")) + throw up2date_cloud_error("Bad payload received from DPS"); + + return std::unique_ptr( + new ProvisioningData_impl( + std::make_unique( + document["crt"].GetString(), document["key"].GetString() + ), + document["endpoint"].GetString() + ) + ); + } + + } +} \ No newline at end of file diff --git a/dps/src/ritms_dps_impl.cpp b/dps/src/ritms_dps_impl.cpp new file mode 100644 index 0000000..241d177 --- /dev/null +++ b/dps/src/ritms_dps_impl.cpp @@ -0,0 +1,69 @@ +#include "ritms_dps_impl.hpp" + +namespace ritms { + namespace dps { + + // mTLSKeyPair_impl -------------------------------------------------------------------------------- + + std::string mTLSKeyPair_impl::getKey() { + return key; + } + + std::string mTLSKeyPair_impl::getCrt() { + return crt; + } + + // ProvisioningData_impl --------------------------------------------------------------------------- + + std::unique_ptr ProvisioningData_impl::getKeyPair() { + + return std::unique_ptr( + new mTLSKeyPair_impl(*this->keyPair) + ); + } + + std::string ProvisioningData_impl::getUp2DateEndpoint() { + return up2DateEndpoint; + } + + + // CloudProvisioningClientBuilder_impl ------------------------------------------------------------- + + std::unique_ptr CloudProvisioningClientBuilder::newInstance() { + + return std::unique_ptr(new CloudProvisioningClientBuilder_impl()); + } + + CloudProvisioningClientBuilder *CloudProvisioningClientBuilder_impl::setAuthCrt(const std::string &crt_) { + this->crt = crt_; + + return this; + } + + CloudProvisioningClientBuilder * + CloudProvisioningClientBuilder_impl::setEndpoint(const std::string &endpoint) { + this->provisioningURI = uri::URI::fromString(endpoint); + + return this; + } + + CloudProvisioningClientBuilder * + CloudProvisioningClientBuilder_impl::addHeader(const std::string &key, + const std::string &val) { + this->provisioningHeaders.insert({key, val}); + + return this; + } + + std::unique_ptr CloudProvisioningClientBuilder_impl::build() { + auto provisioningClient = new ProvisioningClient_impl(); + auto cli = std::unique_ptr(provisioningClient); + + provisioningClient->provisioningHeaders = provisioningHeaders; + provisioningClient->crt = crt; + provisioningClient->provisioningURI = provisioningURI; + + return cli; + } + } +} \ No newline at end of file diff --git a/dps/src/ritms_dps_impl.hpp b/dps/src/ritms_dps_impl.hpp new file mode 100644 index 0000000..35e5339 --- /dev/null +++ b/dps/src/ritms_dps_impl.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include + +#include "ritms_dps.hpp" +#include "uriparse.hpp" +#include "httplib.h" + + +namespace ritms { + namespace dps { + + class mTLSKeyPair_impl : public mTLSKeyPair { + std::string crt, key; + + public: + mTLSKeyPair_impl(std::string crt_, std::string key_) : crt(std::move(crt_)), key(std::move(key_)) {}; + + std::string getKey() override; + std::string getCrt() override; + }; + + + + class ProvisioningData_impl : public ProvisioningData { + std::unique_ptr keyPair; + std::string up2DateEndpoint; + + public: + + ProvisioningData_impl(std::unique_ptr kp, + std::string up2DateEndpoint_) + : keyPair(std::move(kp)), up2DateEndpoint(std::move(up2DateEndpoint_)) {}; + + std::unique_ptr getKeyPair() override; + std::string getUp2DateEndpoint() override; + }; + + + class ProvisioningClient_impl : public ProvisioningClient { + std::string crt; + uri::URI provisioningURI; + httplib::Headers provisioningHeaders; + + public: + std::string formatCertificateUpdatePayload(); + std::unique_ptr doProvisioning() override; + + friend class CloudProvisioningClientBuilder_impl; + }; + + class CloudProvisioningClientBuilder_impl : public CloudProvisioningClientBuilder { + std::string crt; + uri::URI provisioningURI; + httplib::Headers provisioningHeaders; + + public: + + CloudProvisioningClientBuilder *setAuthCrt(const std::string &crt) override; + + CloudProvisioningClientBuilder *setEndpoint(const std::string &endpoint) override; + + CloudProvisioningClientBuilder * + addHeader(const std::string &key, const std::string &val) override; + + std::unique_ptr build() override; + }; + + + } +} \ No newline at end of file diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 757e170..c222639 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,7 +1,4 @@ -project(hawkbit_example_cli) +project(example) -add_executable(${PROJECT_NAME} main.cpp) - -target_link_libraries(${PROJECT_NAME} - sub::hawkbitl -) \ No newline at end of file +add_subdirectory(standalone_client) +add_subdirectory(up2date_client) \ No newline at end of file diff --git a/example/Dockerfile b/example/Dockerfile index 8f3eb2f..aeddb70 100644 --- a/example/Dockerfile +++ b/example/Dockerfile @@ -5,7 +5,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install libssl-dev ca-certificates jq \ && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* -COPY build/example/hawkbit_example_cli /opt/up2date +COPY build/example/up2date_client/up2date_cli /opt/up2date COPY up2datew.sh /opt/ WORKDIR /opt diff --git a/example/main.cpp b/example/include/basic_handler.hpp similarity index 77% rename from example/main.cpp rename to example/include/basic_handler.hpp index 0062ba6..63ae7f1 100644 --- a/example/main.cpp +++ b/example/include/basic_handler.hpp @@ -1,13 +1,9 @@ #include #include -#include "hawkbit.hpp" +#include "ddi.hpp" -using namespace hawkbit; - -const char *AUTH_CERT_PATH_ENV_NAME = "CERT_PATH"; -const char *PROVISIONING_ENDPOINT_ENV_NAME = "PROVISIONING_ENDPOINT"; -const char *X_APIG_TOKEN_ENV_NAME = "X_APIG_TOKEN"; +using namespace ddi; class CancelActionFeedbackDeliveryListener : public ResponseDeliveryListener { public: @@ -87,8 +83,8 @@ class Handler : public EventHandler { std::cout << ">> CancelAction: id " << action->getId() << ", stopId " << action->getStopId() << std::endl; return ResponseBuilder::newInstance() - ->setExecution(hawkbit::Response::CLOSED) - ->setFinished(hawkbit::Response::SUCCESS) + ->setExecution(ddi::Response::CLOSED) + ->setFinished(ddi::Response::SUCCESS) ->addDetail("Some feedback") ->addDetail("One more feedback") ->addDetail("Really important feedback") @@ -115,25 +111,3 @@ char *getEnvOrExit(const char *name) { } return env; } - -int main() { - std::cout << "hawkBit-cpp client started..." << std::endl; - - auto clientCertificatePath = getEnvOrExit(AUTH_CERT_PATH_ENV_NAME); - auto provisioningEndpoint = getEnvOrExit(PROVISIONING_ENDPOINT_ENV_NAME); - // special variable for cloud - auto xApigToken = getEnvOrExit(X_APIG_TOKEN_ENV_NAME); - - std::ifstream t((std::string(clientCertificatePath))); - if (!t.is_open()) { - std::cout << "File " << clientCertificatePath << " not exists" << std::endl; - } - std::string crt((std::istreambuf_iterator(t)), - std::istreambuf_iterator()); - - auto builder = ClientBuilder::newInstance(); - builder->setCrt(crt)->setProvisioningEndpoint(std::string(provisioningEndpoint)) - ->setEventHandler(std::shared_ptr(new Handler())) - ->addProvisioningHeader("X-Apig-AppCode", std::string(xApigToken)) - ->build()->run(); -} \ No newline at end of file diff --git a/example/standalone_client/CMakeLists.txt b/example/standalone_client/CMakeLists.txt new file mode 100644 index 0000000..1410126 --- /dev/null +++ b/example/standalone_client/CMakeLists.txt @@ -0,0 +1,11 @@ +project(standalone_cli) + +add_executable(${PROJECT_NAME} main.cpp) + +target_include_directories( ${PROJECT_NAME} + PRIVATE ${PROJECT_SOURCE_DIR}/../include +) + +target_link_libraries(${PROJECT_NAME} + sub::ddi +) \ No newline at end of file diff --git a/example/standalone_client/main.cpp b/example/standalone_client/main.cpp new file mode 100644 index 0000000..2dc2732 --- /dev/null +++ b/example/standalone_client/main.cpp @@ -0,0 +1,22 @@ +#include "basic_handler.hpp" + +const char *GATEWAY_TOKEN_ENV_NAME = "GATEWAY_TOKEN"; +const char *HAWKBIT_ENDPOINT_ENV_NAME = "HAWKBIT_ENDPOINT"; +const char *CONTROLLER_ID_ENV_NAME = "CONTROLLER_ID"; + +int main() { + std::cout << "simple hawkBit-cpp client started..." << std::endl; + + auto gatewayToken = getEnvOrExit(GATEWAY_TOKEN_ENV_NAME); + auto hawkbitEndpoint = getEnvOrExit(HAWKBIT_ENDPOINT_ENV_NAME); + auto controllerId = getEnvOrExit(CONTROLLER_ID_ENV_NAME); + + + auto builder = DDIClientBuilder::newInstance(); + builder->setHawkbitEndpoint(hawkbitEndpoint, controllerId) + ->setGatewayToken(gatewayToken) + ->setEventHandler(std::shared_ptr(new Handler())) + ->notVerifyServerCertificate() + ->build() + ->run(); +} \ No newline at end of file diff --git a/example/up2date_client/CMakeLists.txt b/example/up2date_client/CMakeLists.txt new file mode 100644 index 0000000..6c72b7d --- /dev/null +++ b/example/up2date_client/CMakeLists.txt @@ -0,0 +1,12 @@ +project(up2date_cli) + +add_executable(${PROJECT_NAME} main.cpp) + +target_include_directories( ${PROJECT_NAME} + PRIVATE ${PROJECT_SOURCE_DIR}/../include +) + +target_link_libraries(${PROJECT_NAME} + sub::ddi + sub::dps +) \ No newline at end of file diff --git a/example/up2date_client/main.cpp b/example/up2date_client/main.cpp new file mode 100644 index 0000000..93f86c8 --- /dev/null +++ b/example/up2date_client/main.cpp @@ -0,0 +1,72 @@ +#include "basic_handler.hpp" +#include "ritms_dps.hpp" +#include +#include + +const char *AUTH_CERT_PATH_ENV_NAME = "CERT_PATH"; +const char *PROVISIONING_ENDPOINT_ENV_NAME = "PROVISIONING_ENDPOINT"; +const char *X_APIG_TOKEN_ENV_NAME = "X_APIG_TOKEN"; + +using namespace ritms::dps; +using namespace ddi; + +class DPSInfoReloadHandler : public AuthErrorHandler { + std::unique_ptr client; + +public: + explicit DPSInfoReloadHandler(std::unique_ptr client_) : client(std::move(client_)) {}; + + void onAuthError(std::unique_ptr ptr) override { + for (;;) { + try { + std::cout << "==============================================" << std::endl; + std::cout << "|DPSInfoReloadHandler| Starting provisioning..." << std::endl; + auto payload = client->doProvisioning(); + std::cout << "|DPSInfoReloadHandler| ... done" << std::endl; + auto keyPair = payload->getKeyPair(); + std::cout << "|DPSInfoReloadHandler| Setting TLS ..." << std::endl; + ptr->setTLS(keyPair->getCrt(), keyPair->getKey()); + std::cout << "|DPSInfoReloadHandler| Setting endpoint [" << payload->getUp2DateEndpoint() << "] ..." << std::endl; + ptr->setEndpoint(payload->getUp2DateEndpoint()); + std::cout << "==============================================" << std::endl; + return; + } + catch (std::exception &e) { + std::cout << "provisioning error: " << e.what() << " still trying..." << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(3000)); + } + } + } +}; + +int main() { + std::cout << "up2date hawkBit-cpp client started..." << std::endl; + + auto clientCertificatePath = getEnvOrExit(AUTH_CERT_PATH_ENV_NAME); + auto provisioningEndpoint = getEnvOrExit(PROVISIONING_ENDPOINT_ENV_NAME); + // special variable for cloud + auto xApigToken = getEnvOrExit(X_APIG_TOKEN_ENV_NAME); + + std::ifstream t((std::string(clientCertificatePath))); + if (!t.is_open()) { + std::cout << "File " << clientCertificatePath << " not exists" << std::endl; + throw std::runtime_error(std::string("fail: cannot open file :").append(clientCertificatePath)); + } + std::string crt((std::istreambuf_iterator(t)), + std::istreambuf_iterator()); + + auto dpsBuilder = CloudProvisioningClientBuilder::newInstance(); + auto dpsClient = dpsBuilder->setEndpoint(provisioningEndpoint) + ->setAuthCrt(crt) + ->addHeader("X-Apig-AppCode", std::string(xApigToken)) + ->build(); + + auto authErrorHandler = std::shared_ptr(new DPSInfoReloadHandler(std::move(dpsClient))); + + + auto builder = DDIClientBuilder::newInstance(); + builder->setAuthErrorHandler(authErrorHandler) + ->setEventHandler(std::shared_ptr(new Handler())) + ->build() + ->run(); +} \ No newline at end of file diff --git a/hawkbit/include/hawkbit.hpp b/hawkbit/include/hawkbit.hpp deleted file mode 100644 index 01dc0c3..0000000 --- a/hawkbit/include/hawkbit.hpp +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -#include "hawkbit/hawkbit_response.hpp" -#include "hawkbit/hawkbit_client.hpp" diff --git a/hawkbit/include/hawkbit/hawkbit_client.hpp b/hawkbit/include/hawkbit/hawkbit_client.hpp deleted file mode 100644 index dd878cf..0000000 --- a/hawkbit/include/hawkbit/hawkbit_client.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include -#include - -#include "hawkbit_event_handler.hpp" - -namespace hawkbit { - // Main class that used for communication with hawkBit - class Client { - public: - - // infinity loop - virtual void run() = 0; - - virtual ~Client() = default; - }; - - class ClientBuilder { - public: - - static std::unique_ptr newInstance(); - - virtual ClientBuilder *setCrt(const std::string&) = 0; - - virtual ClientBuilder *setProvisioningEndpoint(const std::string&) = 0; - - virtual ClientBuilder *setDefaultPollingTimeout(int pollingTimeout_) = 0; - - virtual ClientBuilder *setEventHandler(std::shared_ptr handler) = 0; - - virtual ClientBuilder *addHeader(const std::string &, const std::string &) = 0; - - virtual ClientBuilder *addProvisioningHeader(const std::string &, const std::string &) = 0; - - virtual std::unique_ptr build() = 0; - - virtual ~ClientBuilder() = default; - }; - - -} diff --git a/hawkbit/src/client_builder_impl.cpp b/hawkbit/src/client_builder_impl.cpp deleted file mode 100644 index 34cd6b9..0000000 --- a/hawkbit/src/client_builder_impl.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include - -#include "hawkbit/hawkbit_client.hpp" -#include "provisioning_client_impl.hpp" - -namespace hawkbit { - std::unique_ptr ClientBuilder::newInstance() { - return std::unique_ptr(new ProvisioningClientBuilderImpl()); - } - - ClientBuilder *ProvisioningClientBuilderImpl::setCrt(const std::string& root_) { - crt = root_; - - return this; - } - - ClientBuilder *ProvisioningClientBuilderImpl::setProvisioningEndpoint(const std::string& provisioningEndpoint_) { - provisioningURI = uri::URI::fromString(provisioningEndpoint_); - - return this; - } - - ClientBuilder *ProvisioningClientBuilderImpl::setDefaultPollingTimeout(int pollingTimeout_) { - pollingTimeout = pollingTimeout_; - - return this; - } - - ClientBuilder *ProvisioningClientBuilderImpl::setEventHandler(std::shared_ptr handler_) { - handler = handler_; - - return this; - } - - ClientBuilder *ProvisioningClientBuilderImpl::addHeader(const std::string &key, const std::string &value) { - defaultHeaders.insert({key, value}); - - return this; - } - - ClientBuilder * - ProvisioningClientBuilderImpl::addProvisioningHeader(const std::string &key, const std::string &value) { - provisioningHeaders.insert({key, value}); - - return this; - } - - std::unique_ptr ProvisioningClientBuilderImpl::build() { - auto cli = new ProvisioningClientImpl(); - auto cliPtr = std::unique_ptr(cli); - cli->defaultHeaders = defaultHeaders; - cli->provisioningHeaders = provisioningHeaders; - cli->provisioningURI = provisioningURI; - cli->defaultSleepTime = pollingTimeout; - cli->currentSleepTime = pollingTimeout; - cli->crt = crt; - cli->handler = handler; - - return cliPtr; - } - -} \ No newline at end of file diff --git a/hawkbit/src/hawkbit_client_impl.hpp b/hawkbit/src/hawkbit_client_impl.hpp deleted file mode 100644 index 7e07f40..0000000 --- a/hawkbit/src/hawkbit_client_impl.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include -#include - -#include "httplib.h" -#include "uriparse.hpp" -#include "hawkbit/hawkbit_event_handler.hpp" -#include "hawkbit/exceptions.hpp" -#include "actions_impl.hpp" -#include "hawkbit/hawkbit_client.hpp" - -namespace hawkbit { - - struct PollingData_; - - class HawkbitCommunicationClient : public DownloadProvider, public Client { - protected: - uri::URI hawkbitURI; - httplib::Headers defaultHeaders; - std::shared_ptr handler; - int defaultSleepTime; - int currentSleepTime; - bool ignoreSleep; - - // starting hawkbit communication logic. - // returns execute time in ms - void doPoll(); - - // call user-defined handler and send config data to hawkBit - void followConfigData(uri::URI &); - - // call user-defined handler to process cancelAction and send response to hawkBit - void followCancelAction(uri::URI &); - - // call user-defined handler to process deploymentBase and send response to hawkBit - void followDeploymentBase(uri::URI &); - - // all requests should go via retryHandler - virtual httplib::Result retryHandler(uri::URI, const std::function &); - // creates httpClient with predefined params - virtual httplib::Client newHttpClient(uri::URI &); - - public: - - [[noreturn]] virtual void run() override; - - void downloadTo(uri::URI uri, const std::string& path) override; - - std::string getBody(uri::URI uri) override; - - void downloadWithReceiver(uri::URI uri, std::function function) override; - }; - -} \ No newline at end of file diff --git a/hawkbit/src/provisioning_client_impl.cpp b/hawkbit/src/provisioning_client_impl.cpp deleted file mode 100644 index 2739989..0000000 --- a/hawkbit/src/provisioning_client_impl.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "provisioning_client_impl.hpp" -#include "httplib.h" - -#ifndef RAPIDJSON_HAS_STDSTRING -#define RAPIDJSON_HAS_STDSTRING 1 -#endif - -#include "rapidjson/document.h" -#include "rapidjson/stringbuffer.h" -#include "rapidjson/writer.h" - -namespace hawkbit { - - void ProvisioningClientImpl::run() { - updateCredentials(); - HawkbitCommunicationClient::run(); - } - - httplib::Result ProvisioningClientImpl::retryHandler(uri::URI uri1, const std::function &function) { - // if certificates become invalid we try to renew it - try { - return HawkbitCommunicationClient::retryHandler(uri1, function); - } catch (unauthorized_exception&) { - updateCredentials(); - } - // if getting exception again not handling it - return HawkbitCommunicationClient::retryHandler(uri1, function); - } - - std::string formatCertificateUpdatePayload(const std::string& crt) { - rapidjson::Document document; - document.SetObject(); - document.AddMember("tq", crt, document.GetAllocator()); - - rapidjson::StringBuffer buf; - rapidjson::Writer writer(buf); - document.Accept(writer); - - return buf.GetString(); - } - - void ProvisioningClientImpl::updateCredentials() { - auto resp = httplib::Client(provisioningURI.getScheme() + - "://" + provisioningURI.getAuthority()) - .Post(provisioningURI.getPath().c_str(), provisioningHeaders, - formatCertificateUpdatePayload(crt), "application/json"); - if (resp.error() != httplib::Error::Success) { - throw http_lib_error((int)resp.error()); - } - if (resp->status != HTTP_OK && resp->status != HTTP_CREATED) { - throw http_unexpected_code_exception(resp->status, HTTP_OK); - } - - rapidjson::Document document; - document.Parse<0>(resp->body.c_str()); - - if (document.HasParseError() || !document.HasMember("crt") - || !document.HasMember("endpoint") || !document.HasMember("key")) - throw unexpected_payload(); - - provisioningPayload.crt = document["crt"].GetString(); - provisioningPayload.key = document["key"].GetString(); - hawkbitURI = uri::URI::fromString(document["endpoint"].GetString()); - } - - httplib::Client ProvisioningClientImpl::newHttpClient(uri::URI &uri1) { - BIO *bio_crt = BIO_new(BIO_s_mem()); - BIO_puts(bio_crt, provisioningPayload.crt.c_str()); - X509 *certificate = PEM_read_bio_X509(bio_crt, nullptr, nullptr, nullptr); - BIO_free(bio_crt); - - BIO *bio_key = BIO_new(BIO_s_mem()); - BIO_puts(bio_key, provisioningPayload.key.c_str()); - EVP_PKEY *key = PEM_read_bio_PrivateKey(bio_key, nullptr, nullptr, nullptr); - BIO_free(bio_key); - - - return httplib::Client(uri1.getScheme() + "://" + uri1.getAuthority(), certificate, key); - } - -} \ No newline at end of file diff --git a/hawkbit/src/provisioning_client_impl.hpp b/hawkbit/src/provisioning_client_impl.hpp deleted file mode 100644 index 2694906..0000000 --- a/hawkbit/src/provisioning_client_impl.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include - -#include "hawkbit/hawkbit_event_handler.hpp" -#include "httplib.h" -#include "hawkbit_client_impl.hpp" - -namespace hawkbit { - class ProvisioningClientImpl : public HawkbitCommunicationClient { - std::string crt; - uri::URI provisioningURI; - httplib::Headers provisioningHeaders; - - struct provisioningPayload { - std::string crt; - std::string key; - } provisioningPayload; - - httplib::Result - retryHandler(uri::URI uri1, const std::function &function) override; - - void updateCredentials(); - - httplib::Client newHttpClient(uri::URI &uri1) override; - - friend class ProvisioningClientBuilderImpl; - public: - void run() override; - - }; - - class ProvisioningClientBuilderImpl: public ClientBuilder { - std::string crt; - uri::URI provisioningURI; - int pollingTimeout = 30000; - std::shared_ptr handler; - httplib::Headers defaultHeaders; - httplib::Headers provisioningHeaders; - - - public: - ClientBuilder *setCrt(const std::string&root_) override; - - ClientBuilder *setProvisioningEndpoint(const std::string& provisioningEndpoint_) override; - - ClientBuilder *setDefaultPollingTimeout(int pollingTimeout_) override; - - ClientBuilder *setEventHandler(std::shared_ptr handler) override; - - ClientBuilder *addHeader(const std::string &string, const std::string &string1) override; - - std::unique_ptr build() override; - - ClientBuilder *addProvisioningHeader(const std::string &string, const std::string &string1) override; - - }; -} \ No newline at end of file diff --git a/hawkbit/src/utils.cpp b/hawkbit/src/utils.cpp deleted file mode 100644 index ebff32e..0000000 --- a/hawkbit/src/utils.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "utils.hpp" -#include "hawkbit/exceptions.hpp" - -namespace hawkbit { - uri::URI parseHrefObject(const rapidjson::Value &hrefObject) { - try { - return uri::URI::fromString(hrefObject["href"].GetString()); - } catch (std::exception) { - throw unexpected_payload(); - } - } -} \ No newline at end of file diff --git a/hawkbit/src/utils.hpp b/hawkbit/src/utils.hpp deleted file mode 100644 index b38460c..0000000 --- a/hawkbit/src/utils.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "rapidjson/document.h" -#include "uriparse.hpp" - - -namespace hawkbit { - - uri::URI parseHrefObject(const rapidjson::Value &hrefObject); -} \ No newline at end of file diff --git a/hawkbit/CMakeLists.txt b/modules/CMakeLists.txt similarity index 67% rename from hawkbit/CMakeLists.txt rename to modules/CMakeLists.txt index 01a4a7b..371b95a 100644 --- a/hawkbit/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -1,29 +1,26 @@ -project (hawkbit LANGUAGES CXX) +project (modules LANGUAGES CXX) -set(VCPKG_FEATURE_FLAGS "manifests") # Add a library with the above sources -file(GLOB SOURCES "src/*.cpp") +file(GLOB SOURCES src/*.cpp) + add_library(${PROJECT_NAME} ${SOURCES}) -add_library(sub::hawkbitl ALIAS ${PROJECT_NAME}) +add_library(sub::modules ALIAS ${PROJECT_NAME}) set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) + target_include_directories( ${PROJECT_NAME} - PUBLIC ${PROJECT_SOURCE_DIR}/include + PUBLIC ${PROJECT_SOURCE_DIR}/include ) - -find_package(RapidJSON CONFIG REQUIRED) - find_package(Threads REQUIRED) find_package(OpenSSL COMPONENTS Crypto SSL REQUIRED) target_link_libraries( ${PROJECT_NAME} PRIVATE + sub::modules Threads::Threads $<$:ws2_32> $<$:crypt32> $<$:cryptui> OpenSSL::SSL OpenSSL::Crypto - rapidjson -) - +) \ No newline at end of file diff --git a/hawkbit/src/httplib.h b/modules/include/httplib.h similarity index 100% rename from hawkbit/src/httplib.h rename to modules/include/httplib.h diff --git a/hawkbit/src/uriparse.hpp b/modules/include/uriparse.hpp similarity index 95% rename from hawkbit/src/uriparse.hpp rename to modules/include/uriparse.hpp index ec46134..99f2aa7 100644 --- a/hawkbit/src/uriparse.hpp +++ b/modules/include/uriparse.hpp @@ -11,6 +11,8 @@ namespace uri { std::string fragment; public: + bool isEmpty(); + bool hasPath(); bool hasQuery(); diff --git a/hawkbit/src/httplib.cpp b/modules/src/httplib.cpp similarity index 100% rename from hawkbit/src/httplib.cpp rename to modules/src/httplib.cpp diff --git a/hawkbit/src/uriparse.cpp b/modules/src/uriparse.cpp similarity index 95% rename from hawkbit/src/uriparse.cpp rename to modules/src/uriparse.cpp index d4c9b9a..d33aeb5 100644 --- a/hawkbit/src/uriparse.cpp +++ b/modules/src/uriparse.cpp @@ -74,4 +74,8 @@ namespace uri { return retUri; } + bool URI::isEmpty() { + return scheme.empty() || authority.empty(); + } + } \ No newline at end of file