#pragma once #include #include #include #include #include #include #include #include #include namespace rublon { template < typename HttpHandler = CURL > class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { std::reference_wrapper< const Configuration > _config; mutable std::unique_ptr< WebSocket > _ws; /// TODO try to remove mutable modyfier HttpHandler http{}; const Configuration & config() const noexcept { return _config.get(); } void signRequest(Request & request) const { request.headers["X-Rublon-Signature"] = signData(request.body, config().secretKey).c_str(); } 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(); } public: CoreHandler() = delete; CoreHandler(const Configuration & baseconfig) : _config{baseconfig}, _ws{std::make_unique< WebSocket >(_config)}, http{config()} { log(LogLevel::Debug, "Core Handler apiServer: %s", config().apiServer.c_str()); } CoreHandler(const CoreHandler &) = delete; CoreHandler(CoreHandler &&) noexcept = delete; CoreHandler & operator=(const CoreHandler &) = delete; CoreHandler & operator=(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 { // 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::Monotonic_4k_Resource 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); }; Request request{&memoryResource}; Response response{&memoryResource}; request.headers["Content-Type"] = "application/json"; request.headers["Accept"] = "application/json"; stringifyTo(body, request.body); signRequest(request); std::pmr::string uri{&memoryResource}; uri.reserve(conservative_estimate(config().apiServer, path.size())); 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