#pragma once #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; 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 { using namespace std::string_view_literals; return coreResponse.HasMember("status") and coreResponse["status"].GetString() == "ERROR"sv; } bool isUnHealthy(const Document & coreResponse) const { return coreResponse.HasParseError(); } protected: HttpHandler http{}; public: CoreHandler() = delete; CoreHandler(const Configuration & config) : _config{config}, http{} {} 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 { Document resp{&alloc}; resp.Parse(response.body.c_str()); log(LogLevel::Debug, "Begin, 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); log(LogLevel::Error, "Rublon Core responded with '%s' exception", exception->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}}; } return resp; } tl::unexpected< Error > handleCoreException(std::string_view exceptionString) const { // can happen only dyring 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()}}; // other exceptions, just "throw" return tl::unexpected{ Error{CoreHandlerError{CoreHandlerError::RublonCoreException, std::string{exceptionString.data(), exceptionString.size()}}}}; } 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 += _config.apiServer.c_str(); 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 = std::make_unique< WebSocket >(); } return _ws->AttachToCore(_config.apiServer, tid); } auto listen() const { return _ws->listen(); } }; } // namespace rublon