#pragma once #include "rublon/error.hpp" #include "rublon/static_string.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace rublon { template < typename HttpHandler = CURL > class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { Configuration _config; mutable std::unique_ptr< WebSocket > _ws; /// TODO try to remove mutable modyfier void signRequest(Request & request) const { request.headers["X-Rublon-Signature"] = std::pmr::string{signData(request.body, _config.secretKey).c_str(), request.headers.get_allocator()}; } bool hasSignature(const Response & response) const { if(not response.headers.count("x-rublon-signature")) { log(LogLevel::Error, "No x-rublon-signature"); return false; } return true; } // bool hasRateLimit(const Response & response)const { // if(not response.headers.count("x-ratelimit-remaining")) { // log(LogLevel::Error, "No x-ratelimit-remaining"); // return false; // } // return true; // } // bool rateIsAcceptable(const Response &response)const{ //// todo validate rate type and number // } bool signatureIsNatValid(const Response & response) const { const auto & xRubResp = response.headers.at("x-rublon-signature"); const auto & sign = signData(response.body, _config.secretKey); const bool signatureMatch = xRubResp == sign.data(); if(not signatureMatch) log(LogLevel::Error, "Signature mismatch %s != %s ", xRubResp.c_str(), sign.data()); return not signatureMatch; } bool hasException(const Document & coreResponse) const { log(LogLevel::Trace, "Checking error status in core response"); using namespace std::string_view_literals; return coreResponse.HasMember("status") and coreResponse["status"].GetString() == "ERROR"sv; } bool isUnHealthy(const Document & coreResponse) const { log(LogLevel::Trace, "Checking errors in core response"); return coreResponse.HasParseError(); } protected: HttpHandler http{}; public: CoreHandler() = delete; CoreHandler(const Configuration & config) : _config{config}, _ws{std::make_unique(_config)}, http{_config} { log(LogLevel::Debug, "Core Handler apiServer: %s", _config.apiServer.c_str()); } CoreHandler(CoreHandler &&) noexcept = default; CoreHandler & operator=(CoreHandler &&) = default; CoreHandler(const CoreHandler &) = delete; CoreHandler & operator=(const CoreHandler &) = delete; ~CoreHandler() noexcept = default; tl::expected< std::reference_wrapper< const Response >, Error > validateSignature(const Response & response) const { if(hasSignature(response) and signatureIsNatValid(response)) { log(LogLevel::Error, "rublon core response is not signed properly"); return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}}; } return response; } // tl::expected< std::reference_wrapper< const Response >, Error > validateRate(const Response & response) const { // if(hasSignature(response) and signatureIsNatValid(response)) { // log(LogLevel::Error, "rublon core response is not signed properly"); // return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}}; // } // return response; // } tl::expected< Document, Error > validateResponse(RapidJSONPMRAlloc & alloc, const Response & response) const { tl::expected< Document, Error > resp{&alloc}; resp->Parse(response.body.c_str()); log(LogLevel::Debug, "Starting Core Response validation"); if(isUnHealthy(*resp)) { log(LogLevel::Error, "Rublon Core responded with broken data"); return tl::unexpected{CoreHandlerError{CoreHandlerError::BrokenData}}; } else if(hasException(*resp)) { const auto * exception = JSONPointer{"/result/exception", &alloc}.Get(*resp); const auto * message = JSONPointer{"/result/errorMessage", &alloc}.Get(*resp); log(LogLevel::Error, "Rublon Core responded with '%s' exception", exception != nullptr ? exception->GetString() : "not specyfied"); log(LogLevel::Error, "%s", message != nullptr ? message->GetString() : ""); return handleCoreException(exception->GetString()); } else if(not hasSignature(response)) { // additional check for mallformed responses (A invalid response, without any x-rublon-signature will stop at this check) return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}}; } log(LogLevel::Debug, "Core Response validated OK"); return resp; } tl::unexpected< Error > handleCoreException(std::string_view exceptionString) const { log(LogLevel::Debug, "TMP got core exception: %s", exceptionString.data() ); // can happen only during check application step if(auto error = RublonCheckApplicationException::fromString(exceptionString); error.has_value()) return tl::unexpected{Error{error.value()}}; // auth problems likely bad configuration in rublon admin console if(auto error = RublonAuthenticationInterrupt::fromString(exceptionString); error.has_value()) return tl::unexpected{Error{error.value()}}; // verification error wrong passcode etc. if(auto error = WerificationError::fromString(exceptionString); error.has_value()) return tl::unexpected{Error{error.value()}}; // CoreHandlerError::TransactionAccessTokenExpiredException // other exceptions, just "throw" return tl::unexpected{Error{CoreHandlerError{CoreHandlerError::RublonCoreException}}}; } tl::expected< Document, Error > handleError(const Error & error) const { return tl::unexpected{Error{error}}; } tl::expected< Document, Error > request(RapidJSONPMRAlloc & mr, std::string_view path, const Document & body) const { memory::StrictMonotonic_8k_HeapResource memoryResource; const auto validateSignature = [&](const auto & arg) { return this->validateSignature(arg); }; const auto validateResponse = [&](const auto & arg) { return this->validateResponse(mr, arg); }; const auto handleError = [&](const auto & error) { return this->handleError(error); }; const auto pmrs = [&](const auto & txt) { return std::pmr::string{txt, &memoryResource}; }; Request request{&memoryResource}; Response response{&memoryResource}; request.headers["Content-Type"] = pmrs("application/json"); request.headers["Accept"] = pmrs("application/json"); stringifyTo(body, request.body); signRequest(request); std::pmr::string uri{&memoryResource}; uri.reserve(_config.apiServer.size() + path.size() + 1); uri += _config.apiServer; uri += path.data(); return http .request(uri, request, response) // .and_then(validateSignature) .and_then(validateResponse) .or_else(handleError); } bool createWSConnection(std::string_view tid) const { if(not _ws){ _ws.reset(new WebSocket (_config)); } /// TODO connect can be separated from subscribtion on event /// TODO status of attach is not checked return _ws->AttachToCore(tid); } RublonEventData listen() const { return _ws->listen(); } }; } // namespace rublon