diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f51a59417b31a6f55ca56ed9cf73c95fca4e0685..2e74b11197002d0cf42a653a6c93cdd44b72435c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,5 @@ variables: - VERSION: "5.2" + VERSION: "6.2" IMAGE: "crestapi" # Overriding the variables from the template files. TESTS_DIR: test @@ -63,8 +63,6 @@ auto-fix: - merge_requests # Optionally, run this job only on merge requests build_container_job: - rules: - - if: $DO_DOCKER_IMAGE == "yes" && $CI_PIPELINE_SOURCE != 'merge_request_event' stage: build_docker_image extends: .build_kaniko variables: @@ -107,6 +105,6 @@ apitest: crestcmd: # or any other appropriate stage stage: compile-crestcmd script: - - curl -X POST -F token=$CRESTCMD_TOKEN -F ref=release-5.1-CR2 https://gitlab.cern.ch/api/v4/projects/144796/trigger/pipeline + - curl -X POST -F token=$CRESTCMD_TOKEN -F ref=release-6.1-CR1 https://gitlab.cern.ch/api/v4/projects/144796/trigger/pipeline only: - release-5.1-CR2 # or whichever branch in Project 1 should trigger Project 2 \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c555d5b70c209345b099a620814b417a0dfdcfdc..cde58007c948844dc464dc97b3a38051e44f5e61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,8 @@ set(HEADERS "CrestApi/CrestApiFs.h" "CrestApi/CrestLogger.h" "CrestApi/CrestRequest.h" + "CrestApi/CrestAuth.h" + "CrestApi/JwtUtils.h" "CrestApi/CrestApiExt.h" "CrestApi/CrestException.h" "CrestApi/CrestCondException.h" @@ -71,11 +73,15 @@ set(HEADERS "CrestApi/TagMetaDto.h" "CrestApi/TagMetaSetDto.h" "CrestApi/TagSetDto.h" + "CrestApi/RunLumiDto.h" + "CrestApi/RunLumiSetDto.h" "${CRESTAPI_VERSION_FILE_DIR}/version.h") set(SOURCES "src/CrestApi.cxx" "src/CrestRequest.cxx" + "src/CrestAuth.cxx" + "src/JwtUtils.cxx" "src/CrestLogger.cxx" "src/StringUtils.cxx" "src/CrestApiFs.cxx" @@ -103,6 +109,8 @@ set(SOURCES "src/StoreSetDto.cxx" "src/TagInfoDto.cxx" "src/TagSetDto.cxx" + "src/RunLumiDto.cxx" + "src/RunLumiSetDto.cxx" ) # Set up the build of the main library of the project. @@ -150,6 +158,8 @@ option(CRESTAPI_BUILD_EXAMPLES "Flag for building the examples" TRUE) if(CRESTAPI_BUILD_EXAMPLES) add_subdirectory(examples) +## add_subdirectory(acts) + add_subdirectory(tools) endif() # Export the project. diff --git a/CRESTAPI_USAGE.md b/CRESTAPI_USAGE.md index db8670c28fd928dd8dd0b03c06a9cdb0421379cd..731ca6bd1e6516f815236c3fd8393e6652f50d23 100644 --- a/CRESTAPI_USAGE.md +++ b/CRESTAPI_USAGE.md @@ -38,7 +38,7 @@ Here a set of code snippets represting this workflow. {"name", tagname}, {"timeType", "time"}, {"description", "test"}, - {"synchronization", "none"}, + {"synchronization", "ALL"}, {"insertionTime", "2018-12-18T11:32:58.081+0000"}, {"modificationTime", "2018-12-18T11:32:57.952+0000"}, {"payloadSpec", "JSON"}, // For athena reading you need to be careful to this field [crest-json-single-iov | crest-json-multi-iov] diff --git a/CrestApi/CrestApi.h b/CrestApi/CrestApi.h index 6caa33fcdee743e018d26323342cff9392044f3f..4ec654d21f86cec21886a37a3327d7443a960c83 100644 --- a/CrestApi/CrestApi.h +++ b/CrestApi/CrestApi.h @@ -34,6 +34,7 @@ #include <CrestApi/PayloadDto.h> #include <CrestApi/PayloadSetDto.h> #include <CrestApi/PayloadTagInfoSetDto.h> +#include <CrestApi/RunLumiSetDto.h> #include <CrestApi/StoreSetDto.h> #include <CrestApi/TagMetaSetDto.h> #include <CrestApi/TagSetDto.h> @@ -531,6 +532,13 @@ class CrestApi : public CrestApiBase { */ PayloadDto getPayloadMeta(const std::string &hash) override; + /** + * This method finds a streamer info for the hash. + * @param hash - hash. + * @return streamer info. + */ + std::string getStreamerInfo(const std::string &hash) override; + /** * This method returns the full CREST Server version. * @return CREST server version. @@ -547,7 +555,24 @@ class CrestApi : public CrestApiBase { * @return JSON payload tag info list as a PayloadTagInfoSetDto.<br> */ PayloadTagInfoSetDto listPayloadTagInfo(const std::string &tagname, int size, - int page, const std::string &sort); + int page, + const std::string &sort) override; + + /** + * This method retrieves run lumi information. + * @param since - since time (the beginning of the time interval), + * @param until - until time (end of the time interval), + * @param format - time format for since/until ([iso, number, run-lumi]), + * @param mode - the mode parameter is used to determine the meaning of + * since/until [daterange, runrange]. + * @param size - page size, + * @param page - page number, + * @param sort - sorting order (runNumber:ASC or runNumber:DESC), + * @return JSON run lumi info list as a RunLumiSetDto.<br> + */ + RunLumiSetDto listRunInfo(const std::string &since, const std::string &until, + const std::string format, const std::string mode, + int size, int page, const std::string &sort); }; } // namespace Crest diff --git a/CrestApi/CrestApiBase.h b/CrestApi/CrestApiBase.h index 07343118ef87866df0b56d24aebdd8032e048746..935b4aa8918022c740b17d3019a065684c4611fb 100644 --- a/CrestApi/CrestApiBase.h +++ b/CrestApi/CrestApiBase.h @@ -468,6 +468,13 @@ class CrestApiBase { */ virtual PayloadDto getPayloadMeta(const std::string &hash) = 0; + /** + * This method finds a streamer info for the hash. + * @param hash - hash. + * @return streamer info. + */ + virtual std::string getStreamerInfo(const std::string &hash) = 0; + /** * This method returns the full CREST Server version. * @return CREST server version. @@ -479,6 +486,19 @@ class CrestApiBase { * @return CrestApi library version. */ std::string getClientVersion(); + + /** + * This method retrieves monitoring information on payload as a list of + * payload tag information. + * @param tagname - tag name pattern, "%" can be used for any symbols, + * @param size - page size, + * @param page - page number, + * @param sort - sorting order (name:ASC or name:DESC), + * @return JSON payload tag info list as a PayloadTagInfoSetDto.<br> + */ + virtual PayloadTagInfoSetDto listPayloadTagInfo(const std::string &tagname, + int size, int page, + const std::string &sort) = 0; }; } // namespace Crest diff --git a/CrestApi/CrestApiFs.h b/CrestApi/CrestApiFs.h index 0408d2abb60ba778dbde0b9f1d34e1b2112651c3..62c7e9c0d6966b5b3f9bf0b7d0fb96f252c3bc6c 100644 --- a/CrestApi/CrestApiFs.h +++ b/CrestApi/CrestApiFs.h @@ -97,6 +97,15 @@ class CrestApiFs : public CrestApiBase { */ void flush(); + /** + * Auxiliary method to store the streamer info on the file storage. + * @param hash - the payload hash. + * @param streamerInfo - the payload streamer info. + * + */ + void createStreamerInfo(const std::string &hash, + const std::string streamerInfo); + public: /** * CrestApiFs constructor. @@ -502,6 +511,13 @@ class CrestApiFs : public CrestApiBase { */ PayloadDto getPayloadMeta(const std::string &hash) override; + /** + * This method finds a streamer info for the hash. + * @param hash - hash. + * @return streamer info. + */ + std::string getStreamerInfo(const std::string &hash) override; + /** * This method returns the full CREST Server version. * @return CREST server version. @@ -591,6 +607,19 @@ class CrestApiFs : public CrestApiBase { const std::string &compressionType, const std::string &version, const std::string &streamerInfo); + + /** + * This method retrieves monitoring information on payload as a list of + * payload tag information. + * @param tagname - tag name pattern, "%" can be used for any symbols, + * @param size - page size, + * @param page - page number, + * @param sort - sorting order (name:ASC or name:DESC), + * @return JSON payload tag info list as a PayloadTagInfoSetDto.<br> + */ + PayloadTagInfoSetDto listPayloadTagInfo(const std::string &tagname, int size, + int page, + const std::string &sort) override; }; } // namespace Crest diff --git a/CrestApi/CrestAuth.h b/CrestApi/CrestAuth.h new file mode 100644 index 0000000000000000000000000000000000000000..83bf766453871a084d1e8a7508b624b8948f4ddc --- /dev/null +++ b/CrestApi/CrestAuth.h @@ -0,0 +1,130 @@ +// CrestAuth.h +#ifndef CRESTAPI_AUTH_H +#define CRESTAPI_AUTH_H + +#include <curl/curl.h> + +#include <ctime> +#include <nlohmann/json.hpp> +#include <string> + +namespace Crest { + +/** + * Class handling authentication for CREST requests. + * It loads, refreshes, and exchanges tokens used to authorize access. + */ +class CrestAuth { + private: + std::string m_tokenFilePath; + std::string m_tokenEndpoint; + std::string m_apiEndpoint; + std::string m_clientId; + std::string m_audience; + + std::string m_exchangeToken; + std::string m_refreshToken; + std::string m_finalAccessToken; + + std::time_t m_exchangeTokenExpiresAt; + + /** + * Save the full token JSON response to the configured token file. + * @param tokenJson Full JSON response from the authentication service. + * @return true if saving was successful, false otherwise. + */ + bool saveTokens(const nlohmann::json& tokenJson) const; + + /** + * Sends a form-encoded POST request using libcurl to the given URL. + * @param url Target endpoint URL. + * @param postFields POST body formatted as application/x-www-form-urlencoded. + * @return The response body as a string, or empty string on failure. + */ + std::string postRequest(const std::string& url, + const std::string& postFields) const; + + /** + * libcurl write callback to accumulate received data into a string. + * @param contents Raw data pointer. + * @param size Size of each data chunk. + * @param nmemb Number of data chunks. + * @param userp Pointer to std::string used to collect output. + * @return Total bytes handled (size * nmemb). + */ + static size_t WriteCallback(void* contents, size_t size, size_t nmemb, + void* userp); + + public: + /** + * Default constructor. Initializes internal variables from default + * environment-based settings or hardcoded defaults. + */ + CrestAuth(); + + /** + * Loads the exchange and refresh tokens from a file on disk. + * Sets internal state based on the saved token data. + * @return true if loading and parsing succeeded; false if file missing or + * malformed. + */ + bool loadTokens(); + + /** + * Uses the current refresh token to request a new exchange token from the + * authentication service. Stores the entire response to the token file and + * updates internal state. + * @return true if the refresh was successful, false otherwise. + */ + bool refreshExchangeToken(); + + /** + * Exchanges the current exchange token for a final access token used for + * authorization. This does not persist the access token to file. + * @return true if access token was successfully acquired, false otherwise. + */ + bool exchangeTokenForAccess(); + + /** + * Checks whether the current exchange token is expired based on the 'exp' + * claim. + * @return true if the exchange token is expired or invalid. + */ + bool isExchangeTokenExpired() const; + + /** + * Returns the currently cached access token. This method does not initiate a + * new exchange. + * @return Access token string if previously acquired; otherwise an empty + * string. + */ + std::string getAccessToken() const; + + /** + * Initializes exchange credentials: + * - Loads exchange and refresh tokens from file + * - Refreshes the exchange token if expired + * - Performs token exchange to obtain an access token + * @return true if all steps succeed; throws std::runtime_error on fatal + * errors. + */ + bool setupExchangeCredentials(); + + /** + * Requests an access token using the OAuth2 client credentials flow. + * Reads client ID and client secret from environment variables. + * Stores the resulting access token in memory. + * + * Environment variables: + * - CREST_CLIENT_ID + * - CREST_CLIENT_SECRET + * + * @return true if the token was successfully obtained and stored, false + * otherwise. + */ + bool setupClientCredentials(); +}; + +} // namespace Crest + +#endif // CRESTAPI_AUTH_H diff --git a/CrestApi/CrestBaseResponse.h b/CrestApi/CrestBaseResponse.h index 8cf2e760aff868d28a515de4e040fefdd43e552c..281e87213c5aaf086b86a34a7eeebe5006a29c80 100644 --- a/CrestApi/CrestBaseResponse.h +++ b/CrestApi/CrestBaseResponse.h @@ -31,6 +31,7 @@ class CrestBaseResponse { std::optional<RespPage> getRespPage() const { return page; } std::optional<GenericMap> getFilter() const { return filter; } + void setRespPage(const RespPage &page) { this->page = page; } void setDataType(const std::string &dtype) { datatype = dtype; } json toJson() const; diff --git a/CrestApi/CrestRequest.h b/CrestApi/CrestRequest.h index a6cbd67749f8f6031b189d5b3d3307cc6ad87cce..401b60571a97abd88d53dd13030375f8e27b0fc5 100644 --- a/CrestApi/CrestRequest.h +++ b/CrestApi/CrestRequest.h @@ -5,6 +5,7 @@ #ifndef CRESTAPI_REQUEST_H #define CRESTAPI_REQUEST_H +#include <CrestApi/CrestAuth.h> #include <curl/curl.h> #include <cstdint> @@ -49,6 +50,8 @@ class CrestRequest { std::string m_port; std::string m_url; + CrestAuth m_auth; + char *m_CREST_PROXY = NULL; const char *m_CREST_PROXY_VAR = "SOCKS_PROXY"; @@ -79,6 +82,35 @@ class CrestRequest { // Getters std::string getUrl(); + /** + * Initializes authentication and authorization for CREST requests. + * + * This method reads the specified execution mode (e.g., "JWT", + * "CLIENT_CREDENTIALS") and sets up access credentials accordingly. It: + * - Requests an access token via an exchange token if mode="JWT" + * - Requests an access token via client id and secret if + * mode="CLIENT_CREDENTIALS" + * + * If the mode is nullptr (i.e. environment variable not set), no special + * behavior is applied. + * + * @param mode - A C-style string indicating the execution mode (e.g., from + * the CREST_AUTH_MODE environment variable). If nullptr, the default behavior + * is applied. + * + * @throws std::runtime_error if authentication fails critically (e.g., + * missing token file or failed token exchange). + */ + void initAuth(const char *mode); + + /** + * Creates an Authorization header using the current access token from + * CrestAuth. + * @return A curl_slist pointer containing the Authorization header, or + * nullptr if no token is available. + */ + struct curl_slist *createAuthHeader() const; + /** * General auxillary method to make request to the CREST Server. This method * is used by other methods realizing the requests with the concrete kinds of diff --git a/CrestApi/JwtUtils.h b/CrestApi/JwtUtils.h new file mode 100644 index 0000000000000000000000000000000000000000..2a15a6449104576758985025c4738af4cd126cd6 --- /dev/null +++ b/CrestApi/JwtUtils.h @@ -0,0 +1,38 @@ +#ifndef JWT_UTILS_H +#define JWT_UTILS_H + +#include <ctime> +#include <string> + +namespace Crest { + +/** + * Utility class for decoding and inspecting JWT (JSON Web Token) claims. + */ +class JwtUtils { + public: + /** + * Extracts the `exp` (expiration) claim from a JWT access token. + * Optionally subtracts a grace period to account for clock skew. + * + * @param jwt A JWT string (in the format header.payload.signature). + * @param graceSeconds Optional number of seconds to subtract from the + * extracted expiration time. + * @return The expiration timestamp as time_t (Unix time). + * @throws std::runtime_error if the token is malformed or the exp claim is + * missing. + */ + static std::time_t extractExp(const std::string& jwt, int graceSeconds = 0); + + private: + /** + * Decodes a Base64URL-encoded string (used in JWT header and payload). + * + * @param input The Base64URL-encoded string. + * @return The decoded string. + */ + static std::string base64urlDecode(const std::string& input); +}; +} // namespace Crest + +#endif // JWT_UTILS_H diff --git a/CrestApi/RespPage.h b/CrestApi/RespPage.h index c27c131929f1c94f5388985448a251ef22978af8..c3daa49cc90a429c3c0f3658e811c207065976bb 100644 --- a/CrestApi/RespPage.h +++ b/CrestApi/RespPage.h @@ -23,11 +23,27 @@ class RespPage { int number; public: + RespPage() : size(0), totalElements(0), totalPages(0), number(0) {} + + RespPage(int size, int64_t totalElements, int totalPages, int number) + : size(size), + totalElements(totalElements), + totalPages(totalPages), + number(number) {} + int getSize() const { return size; } int64_t getTotalElements() const { return totalElements; } int getTotalPages() const { return totalPages; } int getNumber() const { return number; } + // Define setters + void setSize(int size) { this->size = size; } + void setTotalElements(int64_t totalElements) { + this->totalElements = totalElements; + } + void setTotalPages(int totalPages) { this->totalPages = totalPages; } + void setNumber(int number) { this->number = number; } + json toJson() const; static RespPage fromJson(const json &j); }; diff --git a/CrestApi/RunLumiDto.h b/CrestApi/RunLumiDto.h new file mode 100644 index 0000000000000000000000000000000000000000..5e78f72664e168aed914aa11531b77fc58621a5f --- /dev/null +++ b/CrestApi/RunLumiDto.h @@ -0,0 +1,59 @@ +/* + Copyright (C) 2019-2024 CERN for the benefit of the ATLAS collaboration + */ + +#ifndef CREST_RUN_LUMI_DTO_HPP +#define CREST_RUN_LUMI_DTO_HPP + +#include <CrestApi/CrestException.h> + +#include <nlohmann/json.hpp> +#include <optional> +#include <string> + +using json = nlohmann::json; + +namespace Crest { + +/** + * @brief The RunLumiDto class + * It contains CTP information of run,lb,start and end time of each lumi block. + * + * @param runNumber The run number. + * @param lb The lumi block number. + * @param starttime The start time of the lumi block, in milliseconds since + * epoch. + * @param endtime The end time of the lumi block, in milliseconds since epoch. + */ + +class RunLumiDto { + private: + uint64_t runNumber; + uint64_t lb; + uint64_t starttime; + uint64_t endtime; + + public: + // Ctor + RunLumiDto(uint64_t runNumber, uint64_t lb, uint64_t starttime, + uint64_t endtime) + : runNumber(runNumber), lb(lb), starttime(starttime), endtime(endtime) {}; + // Default Ctor + RunLumiDto(); + const uint64_t &getRunNumber() const { return runNumber; } + const uint64_t &getLumiBlock() const { return lb; } + const uint64_t &getStartTime() const { return starttime; } + const uint64_t &getEndTime() const { return endtime; } + // Define setters + void setRunNumber(uint64_t runNumber) { this->runNumber = runNumber; } + void setLumiBlock(uint64_t lb) { this->lb = lb; } + void setStartTime(uint64_t starttime) { this->starttime = starttime; } + void setEndTime(uint64_t endtime) { this->endtime = endtime; } + + json toJson() const; + static RunLumiDto fromJson(const json &j); +}; + +} // namespace Crest + +#endif // CREST_RUN_LUMI_DTO_HPP diff --git a/CrestApi/RunLumiSetDto.h b/CrestApi/RunLumiSetDto.h new file mode 100644 index 0000000000000000000000000000000000000000..186217adb9e2bac6fea67bea57a9880a2b91ec83 --- /dev/null +++ b/CrestApi/RunLumiSetDto.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2019-2024 CERN for the benefit of the ATLAS collaboration + */ + +#ifndef CREST_RUN_LUMI_SET_DTO_HPP +#define CREST_RUN_LUMI_SET_DTO_HPP + +#include <CrestApi/CrestBaseResponse.h> +#include <CrestApi/CrestDetail.h> +#include <CrestApi/CrestException.h> +#include <CrestApi/RunLumiDto.h> + +#include <string> +#include <vector> + +#include "nlohmann/json.hpp" + +using json = nlohmann::json; + +namespace Crest { + +class RunLumiSetDto : public CrestBaseResponse { + private: + inline static const std::string s_dtoType = "RunLumiSetDto"; + std::vector<RunLumiDto> resources; + + public: + RunLumiSetDto(const std::vector<RunLumiDto> &resources) + : resources(resources) {} + RunLumiSetDto() : resources() {} + const std::string &getFormat() const { return s_dtoType; } + + size_t size() const { return resources.size(); } + + void add(const RunLumiDto &rl) { resources.push_back(rl); } + + const std::vector<RunLumiDto> &getResources() const { return resources; } + + json toJson() const; + static RunLumiSetDto fromJson(const json &j); +}; + +} // namespace Crest + +#endif // CREST_RUN_LUMI_SET_DTO_HPP diff --git a/README.md b/README.md index c7eb90c290a415a83a7b77fcdd7ad47b1b923a8b..e846005d4a1db05fd7d3e8d9cfa1812953010e7f 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,22 @@ cmake -DCMAKE_INSTALL_PREFIX=$HOME/local_lib .. make make install ``` + +## Authorization +Currently, the API supports two types of authorization workflows: +* `Client credentials`: the client is authenticated using a client id and secret. +```shell +export CREST_CLIENT_ID=<client id> +export CREST_CLIENT_SECRET=<secret> +export CREST_AUTH_MODE=CLIENT_CREDENTIALS +``` +* `Token exchange`: the client is authenticated using an exchange token. To obtain the token excute `auth-get-user-token`. +```shell +auth-get-user-token -c crest-client -o ~/.client-exchange-token.json +export CREST_AUTH_MODE=JWT +``` +Make sure to use `https` in `export CREST_API_URL=https://...` + ## Testing The simple examples are in the `doc` and `test` directory. The executables will be created in the `build` directory. @@ -64,4 +80,4 @@ The simple examples are in the `doc` and `test` directory. The executables will You can validate your installation for example using: ```shell ./crest_example_server http://crest-j23.cern.ch:8080/api-v5.0 -``` \ No newline at end of file +``` diff --git a/acts/CMakeLists.txt b/acts/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..5f8152e3c34df8773b2c63f75e874ce26ce0eb73 --- /dev/null +++ b/acts/CMakeLists.txt @@ -0,0 +1,54 @@ +# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration +# Set the name of the package. +cmake_minimum_required(VERSION 3.12) +project(CrestResolver VERSION 6.1 LANGUAGES CXX) + +# Find the necessary externals. +find_package(CrestApi REQUIRED) + + +# Set the C++ standard to use. +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF CACHE BOOL + "Whether to enable compiler-specific C++ extensions") + +# Set the default library type to build. +option(BUILD_SHARED_LIBS + "Flag for building shared/static libraries" TRUE) + +# Use the standard GNU installation directories. +include(GNUInstallDirs) +set(CMAKE_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/CrestResolver") + +# Turn on all warnings with GCC and Clang. +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +endif() + +# Declare the header and source file names. +set(HEADERS + "./CrestFileResolver.h" +) + +set(SOURCES + "./CrestFileResolver.cxx" +) + +# Set up the build of the main library of the project. +add_library(CrestResolverLib ${SOURCES} ${HEADERS}) +target_link_libraries(CrestResolverLib + PUBLIC + CURL::libcurl + Boost::boost + nlohmann_json::nlohmann_json + CrestApi::CrestApiLib + PRIVATE + OpenSSL::SSL +) +target_include_directories(CrestResolverLib + PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> +) + diff --git a/acts/CrestFileResolver.cxx b/acts/CrestFileResolver.cxx new file mode 100644 index 0000000000000000000000000000000000000000..a625d148cac5c5e503764aaadfe1ae92a8716171 --- /dev/null +++ b/acts/CrestFileResolver.cxx @@ -0,0 +1,80 @@ + +/** + * @file CrestApi/test/CrestApi_test.cxx + * @brief Some tests for server methods. + */ + +#include "CrestFileResolver.h" + +using namespace Crest; + +std::string CrestFileResolver::resolveFilePath(const std::string &geometry_gtag, + const std::string &label, + const std::string &file_name) { + // Here you can implement the logic to resolve the file path. + // Use crestapi to resolve mappings and detect the tag. + std::cout << "Load mappings for " << geometry_gtag << std::endl; + GlobalTagMapSetDto mapset = + m_crestApi.findGlobalTagMap(geometry_gtag, "Trace"); + std::cout << "test: listTagsInGlobalTag (Trace) =" << std::endl; + std::cout << mapset.toJson().dump(4) << std::endl; + + if (mapset.getResources().size() < 1) { + std::cerr << "No mapping found for the global tag: " << geometry_gtag + << std::endl; + return ""; + } + // Get the mapping (geometry tag name) + GlobalTagMapDto map = mapset.getResources()[0]; + std::cout << "Found map " << map.toJson().dump(4) << std::endl; + std::cout << "Compare label " << this->mat_label << " with " << map.getLabel() + << std::endl; + // Check that the label is correct + if (this->mat_label != map.getLabel()) { + std::cerr << "Label mismatch: expected " << mat_label << ", got " + << map.getLabel() << std::endl; + return ""; + } + std::string mat_tag = map.getTagName(); + std::cout << "Load iovs for tag " << mat_tag << std::endl; + // Now get the IOV. + IovSetDto iovset = + m_crestApi.selectIovs(mat_tag, 0, 10, 0, 10, 0, "id.since:ASC"); + if (iovset.getResources().size() < 1) { + std::cerr << "No IOV found for the tag: " << mat_tag << std::endl; + return ""; + } + // Get the IOV + IovDto iov = iovset.getResources()[0]; + std::cout << "Found IOV " << iov.toJson().dump(4) << std::endl; + // Get the payload using the iov hash + std::string hash = iov.getPayloadHash(); + std::string payload = m_crestApi.getPayload(hash); + // Dump the payload to a file + std::string file_path = m_file_path + file_name; + // Write the payload to the file + std::cout << "Dump payload to file " << file_path << std::endl; + dumpToFile(file_path, payload); + return file_path; +} + +void CrestFileResolver::dumpToFile(const std::string &filePath, + const std::string &content) { + // Open the file in write mode + std::ofstream outFile(filePath); + + // Check if the file was successfully opened + if (!outFile) { + std::cerr << "Error: Could not open file " << filePath << " for writing." + << std::endl; + return; + } + + // Write the content to the file + outFile << content; + + // Close the file + outFile.close(); + + std::cout << "Content successfully written to " << filePath << std::endl; +} diff --git a/acts/CrestFileResolver.h b/acts/CrestFileResolver.h new file mode 100644 index 0000000000000000000000000000000000000000..128bf0e2285ff465ee913f83cf9d316d7460144f --- /dev/null +++ b/acts/CrestFileResolver.h @@ -0,0 +1,68 @@ + +/** + * @file CrestApi/test/CrestApi_test.cxx + * @brief Some tests for server methods. + */ +#ifndef CRESTAPI_CRESTRESOLVER_H +#define CRESTAPI_CRESTRESOLVER_H + +#include <fstream> +#include <iostream> +#include <string> + +#include "CrestApi/CrestApi.h" +#include "CrestApi/GlobalTagMapDto.h" +#include "CrestApi/GlobalTagMapSetDto.h" +#include "CrestApi/IovSetDto.h" +#include "CrestApi/StringUtils.h" + +using namespace Crest; + +/** + * Class to resolve a geometry tracking file. + * These files are linked to a geometry global tag, and contain only one IOV + * (since=0). + */ +class CrestFileResolver { + private: + std::string m_crest_server; + CrestApi m_crestApi; + // This should be somehow linked to the type of geometry tag (ITK, ...) + std::string mat_label = "ITK"; + // Base directoy where the file will be dumped. + std::string m_file_path; + + public: + CrestFileResolver(const std::string &crest_server) + : m_crest_server(crest_server), + m_crestApi(crest_server), + m_file_path("/tmp/crest_test/") { + // Check CrestApi version. + if (m_crestApi.getCrestVersion() != "v6.0") { + std::cerr << "CrestApi version mismatch!" << std::endl; + } + } + + CrestFileResolver(const std::string &crest_server, + const std::string &file_path, const std::string &label) + : m_crest_server(crest_server), + m_crestApi(crest_server), + mat_label(label), + m_file_path(file_path) { + // Check CrestApi version + if (m_crestApi.getCrestVersion() != "v6.0") { + std::cerr << "CrestApi version mismatch!" << std::endl; + } + } + // Destructor + ~CrestFileResolver() = default; + + // Resolve the file path for a given geometry global tag. + std::string resolveFilePath(const std::string &geometry_gtag, + const std::string &label, + const std::string &file_name); + // Dump the payload to a file. + void dumpToFile(const std::string &filePath, const std::string &content); +}; + +#endif // CRESTAPI_CRESTRESOLVER_H \ No newline at end of file diff --git a/examples/crest_example_fs.cxx b/examples/crest_example_fs.cxx index 63492ccd7cd8a2801e0a2f1732e06c2fe9b77f11..c37fe8cb641d483feda988ce6674ace94f8fcbd6 100644 --- a/examples/crest_example_fs.cxx +++ b/examples/crest_example_fs.cxx @@ -41,7 +41,7 @@ void testCreateGlobalTag_FS(const std::string &tagname) { std::cout << std::endl << "test file system: createGlobalTag" << std::endl; CrestApiFs myCrestApi = CrestApiFs(true, "/tmp/crest"); - /* + /* nlohmann::json js = { {"name", tagname}, @@ -107,14 +107,14 @@ void testCreateTag_FS(const std::string &tagname) { */ // the tag innitialization with the constructor: - + TagDto dto(tagname, "time", "test"); dto.setSynchronization("ALL"); dto.setObjectType("JSON"); dto.setStatus("UNLOCKED"); dto.setLastValidatedTime(0.0); dto.setEndOfValidity(0.0); - + try { myCrestApi.createTag(dto); std::cout << std::endl << "test: createTag FS (success)" << std::endl; @@ -272,16 +272,16 @@ void testStorePayloads_FS(const std::string &tagname) { TagDto dto = TagDto(); dto = dto.fromJson(tag_js); */ - + // the tag innitialization with the constructor: - + TagDto dto(tagname, "time", "test"); dto.setSynchronization("ALL"); dto.setObjectType("JSON"); dto.setStatus("UNLOCKED"); dto.setLastValidatedTime(0.0); dto.setEndOfValidity(0.0); - + try { myCrestApi.createTag(dto); std::cout << std::endl @@ -298,7 +298,7 @@ void testStorePayloads_FS(const std::string &tagname) { /* // OLD variant, StoreSetDto defined via JSON - + // Your JSON string std::string jsonString = R"( { @@ -325,7 +325,7 @@ void testStorePayloads_FS(const std::string &tagname) { */ // New version: - + StoreSetDto storeSetDto; StoreDto dto1(1000, "", "Sample data"); @@ -349,7 +349,7 @@ void testStorePayloads_FS(const std::string &tagname) { } /* - + // Your JSON string std::string jsonString2 = R"( { @@ -376,7 +376,7 @@ void testStorePayloads_FS(const std::string &tagname) { */ // New version: - + StoreSetDto storeSetDto2; StoreDto dto3(3000, "", "Sample data 3"); @@ -550,6 +550,34 @@ void testGetPayload_FS(const std::string &hash) { } } +void testGetPayload_FS_2(const std::string &tagname) { + std::cout << std::endl << "test: getPayload 2" << std::endl; + CrestApiFs myCrestApi = CrestApiFs(true, "/tmp/crest"); + + try { + IovSetDto dto = + myCrestApi.selectIovs(tagname, 0, -1, 0, 1000, 0, "id.since:ASC"); + std::cout << std::endl + << "test: listIovs (result) = " << std::endl + << dto.toJson().dump(4) << std::endl; + std::vector<IovDto> iovs = dto.getResources(); + for (auto iov : iovs) { + std::cout << "iov.since = " << iov.getSince() << std::endl; + std::cout << "iov.payloadHash = " << iov.getPayloadHash() << std::endl; + std::string payload = myCrestApi.getPayload(iov.getPayloadHash()); + std::cout << "payload = " << payload.substr(0, 100) << std::endl; + PayloadDto payloadDto = myCrestApi.getPayloadMeta(iov.getPayloadHash()); + std::cout << "payloadDto = " << payloadDto.toJson().dump(4) << std::endl; + std::cout << " size = " << payloadDto.size() << std::endl; + std::string streamer = myCrestApi.getStreamerInfo(iov.getPayloadHash()); + std::cout << "streamerInfo = " << streamer << std::endl; + } + } catch (const std::exception &e) { + std::cout << std::endl << "test: getPayload 2 (failed)" << std::endl; + std::cout << e.what() << std::endl; + } +} + void testGetPayloadMeta_FS(const std::string &hash) { std::cout << std::endl << "test: getPayloadMeta FS" << std::endl; @@ -766,7 +794,7 @@ void createNTags_FS(const std::string &tagname, int n) { */ // the tag innitialization with the constructor: - + TagDto dto(tg, "time", "test"); dto.setSynchronization("ALL"); dto.setObjectType("JSON"); @@ -912,14 +940,14 @@ void createNGlobalTags_FS(const std::string &tagname, int n) { GlobalTagDto dto = GlobalTagDto(); dto = dto.fromJson(js); */ - + // global tag innitialization with the constructor: GlobalTagDto dto(tg, "test", "1", "M"); dto.setValidity(0); dto.setType("N"); dto.setScenario("test"); - + try { myCrestApi.createGlobalTag(dto); std::cout << std::endl << "global tag " << tg << " created" << std::endl; @@ -1111,6 +1139,7 @@ int main() { testFindTag_FS(tagname); testStorePayloads_FS(tagname); testGetSize_FS(tagname); + testGetPayload_FS_2(tagname); */ /* diff --git a/examples/crest_example_server.cxx b/examples/crest_example_server.cxx index 4e84887c751bbf43ffa8f137afaacc7c74038cb5..f0446b2904feb53ea4c4d11d1f46e60d6ad2cb57 100644 --- a/examples/crest_example_server.cxx +++ b/examples/crest_example_server.cxx @@ -371,7 +371,7 @@ void testStorePayloads(const std::string &tagname) { */ // New version: - + StoreSetDto storeSetDto; StoreDto dto1(1000, "", "Sample data"); @@ -471,6 +471,8 @@ void testGetPayload(const std::string &tagname) { PayloadDto payloadDto = myCrestApi.getPayloadMeta(iov.getPayloadHash()); std::cout << "payloadDto = " << payloadDto.toJson().dump(4) << std::endl; std::cout << " size = " << payloadDto.size() << std::endl; + std::string streamer = myCrestApi.getStreamerInfo(iov.getPayloadHash()); + std::cout << "streamerInfo = " << streamer << std::endl; } } catch (const std::exception &e) { std::cout << std::endl << "test: listIovs (failed)" << std::endl; @@ -855,7 +857,7 @@ int main(int argc, char *argv[]) { testGetSize(tagname); testRemoveTag(tagname); - testFindExistingTagMeta("SCTDAQConfigChipSlim-HEAD"); + // testFindExistingTagMeta("SCTDAQConfigChipSlim-HEAD"); // testGetVersion(); diff --git a/src/CrestApi.cxx b/src/CrestApi.cxx index 2aad225d4b21a434f80ec37efc08685d4a91d786..b403d6e1edb84cc9a5d41878bb122ba9316bab49 100644 --- a/src/CrestApi.cxx +++ b/src/CrestApi.cxx @@ -36,6 +36,7 @@ CrestApi::CrestApi(const std::string &host, const std::string &port, m_request.setHost(host); m_request.setPort(port); m_request.setPrefix(protocol); + m_request.initAuth(std::getenv("CREST_AUTH_MODE")); } /** @@ -60,6 +61,7 @@ CrestApi::CrestApi(std::string_view url, std::string_view apipath, m_request.setHost(parsedUrl.host.data()); m_request.setPort(std::to_string(*parsedUrl.port)); m_request.setPrefix(parsedUrl.protocol.data()); + m_request.initAuth(std::getenv("CREST_AUTH_MODE")); if (parsedUrl.apipath == "") { m_PATH = urlPaths[UrlPathIndex::CREST_ROOT]; @@ -481,6 +483,21 @@ void CrestApi::removeGlobalTagMap(const std::string &name, std::string retv; nlohmann::json js = nullptr; retv = m_request.performRequest(current_path, Action::DELETE, js); + + nlohmann::json response = StringUtils::getJson(retv); + GlobalTagMapSetDto dto = GlobalTagMapSetDto::fromJson(response); + if (dto.size() != 0) { + GlobalTagMapDto gtmap = dto.getResources()[0]; + if (name != gtmap.getGlobalTagName() || tagname != gtmap.getTagName() || + label != gtmap.getLabel()) { + throw CrestException( + "ERROR in CrestApi::removeGlobalTagMap Cannot delete global tag " + "map."); + } + } else { + throw CrestException( + "ERROR in CrestApi::removeGlobalTagMap Cannot delete global tag map."); + } } //============================================================================================================== @@ -761,4 +778,57 @@ void CrestApi::checkHash(const std::string &hash, const std::string &str, return; } +std::string CrestApi::getStreamerInfo(const std::string &hash) { + std::string current_path = m_PATH; + current_path += urlPaths[UrlPathIndex::PAYLOADS]; + current_path += urlPaths[UrlPathIndex::DATA]; + current_path += "?format=STREAMER&hash="; + current_path += hash; + std::string retv; + nlohmann::json js = nullptr; + retv = m_request.performRequest(current_path, Action::GET, js); + return retv; +} + +RunLumiSetDto CrestApi::listRunInfo(const std::string &since, + const std::string &until, + const std::string format, + const std::string mode, int size, int page, + const std::string &sort) { + std::string current_path = m_PATH; + current_path += urlPaths[UrlPathIndex::RUNINFO]; + current_path += "?since="; + current_path += since; + current_path += "&until="; + current_path += until; + current_path += "&format="; + current_path += format; + current_path += "&mode="; + current_path += mode; + + if (size != -1) { + current_path += "&size="; + current_path += std::to_string(size); + } + if (page != -1) { + current_path += "&page="; + current_path += std::to_string(page); + } + if (sort != "") { + current_path += "&sort="; + current_path += sort; + } + + std::string retv; + + nlohmann::json js = nullptr; + retv = m_request.performRequest(current_path, Action::GET, js); + nlohmann::json response = StringUtils::getJson(retv); + + RunLumiSetDto dto = RunLumiSetDto::fromJson(response); + + return dto; +} +//============================================================================================================== + } // namespace Crest diff --git a/src/CrestApiFs.cxx b/src/CrestApiFs.cxx index 476505ba9c21f2b4280b7f3e7867433a7c976520..ba0922c7f1272e750e492bb4b4dff3cfed733b20 100644 --- a/src/CrestApiFs.cxx +++ b/src/CrestApiFs.cxx @@ -160,6 +160,7 @@ GlobalTagSetDto CrestApiFs::listGlobalTags(const std::string &name, int size, "ERROR in CrestApiFs::listTags: wrong sort parameter." + sort); } + int totalItems = 0; // total global tag number try { std::vector<std::string> taglist = nameList(folder, ascending); std::vector<std::string> clearedTaglist; @@ -178,6 +179,8 @@ GlobalTagSetDto CrestApiFs::listGlobalTags(const std::string &name, int size, } } + totalItems = clearedTaglist.size(); // total global tag number + taglist = getVectorPage(clearedTaglist, size, page); for (const std::string &tag : taglist) { std::string file_name = folder + "/" + tag + "/" + s_FS_GLOBALTAG_FILE; @@ -190,6 +193,11 @@ GlobalTagSetDto CrestApiFs::listGlobalTags(const std::string &name, int size, "ERROR in CrestApiFs::listGlobalTags: cannot get the tag list."); } + // add RespPage: + int tPages = std::ceil((double)totalItems / size); + RespPage respp(tagSet.size(), totalItems, tPages, page); + tagSet.setRespPage(respp); + return tagSet; } @@ -268,6 +276,7 @@ TagSetDto CrestApiFs::listTags(const std::string &name, int size, int page, "ERROR in CrestApiFs::listTags: wrong sort parameter." + sort); } + int totalItems = 0; // total tag number try { std::vector<std::string> taglist = nameList(folder, ascending); std::vector<std::string> clearedTaglist; @@ -286,6 +295,8 @@ TagSetDto CrestApiFs::listTags(const std::string &name, int size, int page, } } + totalItems = clearedTaglist.size(); // total tag number + taglist = getVectorPage(clearedTaglist, size, page); for (const std::string &tag : taglist) { std::string file_name = folder + "/" + tag + "/" + s_FS_TAG_FILE; @@ -298,6 +309,11 @@ TagSetDto CrestApiFs::listTags(const std::string &name, int size, int page, "ERROR in CrestApiFs::listTags: cannot get the tag list."); } + // add RespPage: + int tPages = std::ceil((double)totalItems / size); + RespPage respp(tagSet.size(), totalItems, tPages, page); + tagSet.setRespPage(respp); + return tagSet; } @@ -477,6 +493,13 @@ GlobalTagMapSetDto CrestApiFs::findGlobalTagMap( GlobalTagMapSetDto dto; dto = GlobalTagMapSetDto::fromFsJson(js); + + // add RespPage: + int totalItems = dto.size(); + RespPage respp(totalItems, totalItems, 1, + 0); // full global tag map in one response + dto.setRespPage(respp); + return dto; } @@ -489,10 +512,11 @@ IovSetDto CrestApiFs::selectIovs( IovSetDto dto; nlohmann::json js = nlohmann::json::array(); + int niovs = 0; try { nlohmann::json iovList = findAllIovs(name); - int niovs = iovList.size(); + niovs = iovList.size(); for (int i = 0; i < niovs; i++) { if (iovList[i].find("since") != iovList[i].end()) { @@ -529,6 +553,11 @@ IovSetDto CrestApiFs::selectIovs( nlohmann::json ext = getPage(sorted, size, page); dto = IovSetDto::fromFsJson(ext); + // add RespPage: + int tPages = std::ceil((double)niovs / size); + RespPage respp(dto.size(), niovs, tPages, page); + dto.setRespPage(respp); + return dto; } @@ -708,7 +737,6 @@ void CrestApiFs::storePayloadDump(const std::string &tag, uint64_t since, {"objectType", objectType}, {"version", version}, {"size", js.size()}, - {"streamerInfo", streamerInfo}, {"compressionType", compressionType}, {"insertionTime", getDateAndTime()}}; @@ -719,6 +747,9 @@ void CrestApiFs::storePayloadDump(const std::string &tag, uint64_t since, outFile << jsn.dump(); outFile.close(); + // streamer info: + createStreamerInfo(hashCode, streamerInfo); + // check if data exists if (m_data.find(tag) == m_data.end()) { @@ -815,6 +846,56 @@ PayloadDto CrestApiFs::getPayloadMeta(const std::string &hash) { return dto; } +std::string CrestApiFs::getStreamerInfo(const std::string &hash) { + std::string workDir = m_data_folder; + workDir += '/'; + workDir += getFirstLetters(hash); + workDir += '/'; + workDir += hash; + std::string filePath = workDir + "/streamer.json"; + std::string res = ""; + + try { + if (std::filesystem::exists(filePath)) { + res = getFileString(filePath); + } else { + throw CrestException("streamer info with hash " + hash + + " does not exist."); + } + } catch (const std::exception &e) { + std::string message = e.what(); + throw CrestException( + "ERROR in CrestApiFs::getStreamerInfo cannot get the " + "streamer info form file storage, " + + message); + } + + return res; +} + +void CrestApiFs::createStreamerInfo(const std::string &hash, + const std::string streamerInfo) { + std::string workDir = m_data_folder; + workDir += '/'; + workDir += getFirstLetters(hash); + workDir += '/'; + workDir += hash; + std::string filePath = workDir + "/streamer.json"; + + if (m_isRewrite) { + if (std::filesystem::exists(std::filesystem::path(filePath))) { + std::filesystem::remove(std::filesystem::path(filePath)); + } + + std::ofstream outFile; + + outFile.open(filePath.c_str()); + outFile << streamerInfo; + outFile.close(); + } +} +// + nlohmann::json CrestApiFs::getPage(nlohmann::json data, int size, int page) { nlohmann::json js = nlohmann::json::array(); int dataSize = data.size(); @@ -989,4 +1070,11 @@ std::string CrestApiFs::getCrestVersion() { "CREST server version for file storage."); } +PayloadTagInfoSetDto CrestApiFs::listPayloadTagInfo(const std::string &tagname, + int size, int page, + const std::string &sort) { + checkFsException("CrestApiFs::listPayloadTagInfo"); + return PayloadTagInfoSetDto(); +} + } // namespace Crest diff --git a/src/CrestAuth.cxx b/src/CrestAuth.cxx new file mode 100644 index 0000000000000000000000000000000000000000..98900ec5c219f36856d37dfda229bf423f340365 --- /dev/null +++ b/src/CrestAuth.cxx @@ -0,0 +1,263 @@ +// CrestAuth.cxx +#include <CrestApi/CrestAuth.h> +#include <CrestApi/CrestLogger.h> +#include <CrestApi/JwtUtils.h> + +#include <fstream> +#include <iostream> +#include <sstream> + +namespace Crest { + +CrestAuth::CrestAuth() + : m_tokenFilePath(std::getenv("HOME") + + std::string("/.client-exchange-token.json")), + m_tokenEndpoint( + "https://auth.cern.ch/auth/realms/cern/protocol/openid-connect/" + "token"), + m_apiEndpoint("https://auth.cern.ch/auth/realms/cern/api-access/token"), + m_clientId("crest-client"), + m_audience("crest-server"), + m_exchangeTokenExpiresAt(0) {} + +bool CrestAuth::loadTokens() { + std::ifstream file(m_tokenFilePath); + if (!file.is_open()) { + Logger::log(LogLevel::ERROR, + "Failed to open token file: " + m_tokenFilePath); + return false; + } + + std::string content((std::istreambuf_iterator<char>(file)), + std::istreambuf_iterator<char>()); + if (content.empty()) { + Logger::log(LogLevel::ERROR, "Token file is empty"); + return false; + } + + file.clear(); + file.seekg(0); + + nlohmann::json js; + try { + file >> js; + } catch (const std::exception& e) { + Logger::log(LogLevel::ERROR, + "Failed to parse token JSON: " + (std::string)e.what()); + return false; + } + + m_exchangeToken = js.value("access_token", ""); + m_refreshToken = js.value("refresh_token", ""); + try { + m_exchangeTokenExpiresAt = JwtUtils::extractExp(m_exchangeToken, 30); + } catch (const std::exception& e) { + Logger::log(LogLevel::ERROR, + "Failed to extract 'exp' from JWT: " + (std::string)e.what()); + m_exchangeTokenExpiresAt = std::time(nullptr) + 300; // fallback + } + + Logger::log(LogLevel::DEBUG, "Loaded exchange and refresh tokens from file"); + return true; +} + +bool CrestAuth::saveTokens(const nlohmann::json& tokenJson) const { + std::ofstream file(m_tokenFilePath); + if (!file.is_open()) { + Logger::log(LogLevel::ERROR, + "Failed to open token file for writing: " + m_tokenFilePath); + return false; + } + + file << tokenJson.dump(); + Logger::log(LogLevel::DEBUG, "Saved exchanged token to file"); + return true; +} + +bool CrestAuth::isExchangeTokenExpired() const { + return std::time(nullptr) >= m_exchangeTokenExpiresAt; +} + +std::string CrestAuth::getAccessToken() const { + return m_finalAccessToken; +} + +bool CrestAuth::refreshExchangeToken() { + std::ostringstream oss; + oss << "grant_type=refresh_token" + << "&refresh_token=" << m_refreshToken << "&client_id=" << m_clientId; + + std::string response = postRequest(m_tokenEndpoint, oss.str()); + if (response.empty()) + return false; + + auto js = nlohmann::json::parse(response, nullptr, false); + if (js.is_discarded() || js.contains("error")) { + Logger::log(LogLevel::ERROR, + "Failed to refresh exchange token: " + js.dump()); + return false; + } + + m_exchangeToken = js["access_token"]; + m_refreshToken = js.value("refresh_token", m_refreshToken); + try { + m_exchangeTokenExpiresAt = JwtUtils::extractExp(m_exchangeToken, 30); + } catch (const std::exception& e) { + Logger::log(LogLevel::ERROR, + "Failed to extract 'exp' from JWT: " + (std::string)e.what()); + m_exchangeTokenExpiresAt = std::time(nullptr) + 300; // fallback + } + Logger::log(LogLevel::DEBUG, "Successfully refreshed exchange token"); + return saveTokens(js); +} + +bool CrestAuth::exchangeTokenForAccess() { + CURL* curl = curl_easy_init(); + if (!curl) { + Logger::log(LogLevel::ERROR, "Failed to initialize CURL"); + return false; + } + + if (m_exchangeToken.empty()) { + Logger::log(LogLevel::ERROR, "Exchange token is empty"); + curl_easy_cleanup(curl); + return false; + } + + std::ostringstream oss; + oss << "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" + << "&subject_token=" << m_exchangeToken + << "&subject_token_type=urn:ietf:params:oauth:token-type:access_token" + << "&client_id=" << m_clientId << "&audience=" << m_audience; + + std::string response = postRequest(m_tokenEndpoint, oss.str()); + curl_easy_cleanup(curl); + + if (response.empty()) + return false; + + auto js = nlohmann::json::parse(response, nullptr, false); + if (js.is_discarded() || !js.contains("access_token")) { + Logger::log(LogLevel::ERROR, "Token exchange failed: " + js.dump(2)); + return false; + } + + m_finalAccessToken = js["access_token"]; + + Logger::log(LogLevel::DEBUG, "Token exchanged successfully"); + + return true; +} + +std::string CrestAuth::postRequest(const std::string& url, + const std::string& postFields) const { + CURL* curl = curl_easy_init(); + if (!curl) + return ""; + + std::string response; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postFields.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + + struct curl_slist* headers = nullptr; + headers = curl_slist_append( + headers, "Content-Type: application/x-www-form-urlencoded"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + + CURLcode res = curl_easy_perform(curl); + if (res != CURLE_OK) { + std::string errMsg = curl_easy_strerror(res); + Logger::log(LogLevel::ERROR, "curl_easy_perform failed: " + errMsg); + } + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + return response; +} + +size_t CrestAuth::WriteCallback(void* contents, size_t size, size_t nmemb, + void* userp) { + size_t totalSize = size * nmemb; + std::string* out = static_cast<std::string*>(userp); + out->append(static_cast<char*>(contents), totalSize); + return totalSize; +} + +bool CrestAuth::setupExchangeCredentials() { + Logger::log(LogLevel::DEBUG, + "Setting up authorization - requesting access token..."); + + bool tokensLoaded = loadTokens(); + if (!tokensLoaded) { + Logger::log(LogLevel::ERROR, + "No valid token file found.\nExecute: auth-get-user-token -c " + "crest-client -o ~/.client-exchange-token.json"); + return false; + } + + Logger::log(LogLevel::DEBUG, "Checking if exchange token is expired..."); + if (isExchangeTokenExpired()) { + Logger::log(LogLevel::DEBUG, + "Exchange token is expired. Attempting refresh..."); + if (!refreshExchangeToken()) { + Logger::log(LogLevel::ERROR, + "Exchange token is expired and refresh failed.\nExecute: " + "auth-get-user-token -c crest-client -o " + "~/.client-exchange-token.json"); + return false; + } + } + + if (!exchangeTokenForAccess()) { + Logger::log(LogLevel::ERROR, "Failed to obtain access token from exchange"); + return false; + } + + Logger::log(LogLevel::DEBUG, "Setup complete — access token obtained"); + return true; +} + +bool CrestAuth::setupClientCredentials() { + const char* clientId = std::getenv("CREST_CLIENT_ID"); + const char* clientSecret = std::getenv("CREST_CLIENT_SECRET"); + + if (!clientId || !clientSecret) { + Logger::log( + LogLevel::ERROR, + "Missing CREST_CLIENT_ID or CREST_CLIENT_SECRET environment variable."); + return false; + } + + std::ostringstream oss; + oss << "grant_type=client_credentials" + << "&client_id=" << clientId << "&client_secret=" << clientSecret + << "&subject_token_type=urn:ietf:params:oauth:token-type:access_token" + << "scope=openid" + << "&audience=" << m_audience; + + std::string response = postRequest(m_apiEndpoint, oss.str()); + if (response.empty()) { + Logger::log(LogLevel::ERROR, + "No response received from client credentials request."); + return false; + } + + auto js = nlohmann::json::parse(response, nullptr, false); + if (js.is_discarded() || !js.contains("access_token")) { + Logger::log(LogLevel::ERROR, + "Failed to obtain access token using client credentials: " + + js.dump(2)); + return false; + } + + m_finalAccessToken = js["access_token"]; + + Logger::log(LogLevel::DEBUG, + "Access token acquired via client credentials flow"); + + return true; +} +} // namespace Crest diff --git a/src/CrestRequest.cxx b/src/CrestRequest.cxx index 9d8e9ceb9f000ba6cd95e72a104edaaf92857aeb..cbae7d40ae5f60771599414b7ab37da12c75b78c 100644 --- a/src/CrestRequest.cxx +++ b/src/CrestRequest.cxx @@ -77,6 +77,39 @@ std::string CrestRequest::getUrl() { return m_url; } +void CrestRequest::initAuth(const char *mode) { + if (!mode) { + Logger::log(LogLevel::WARNING, + "CREST_AUTH_MODE is unset — skipping client authorization"); + return; + } + + std::string modeStr = mode; + Logger::log(LogLevel::DEBUG, "CREST_AUTH_MODE is set to: " + modeStr); + + if (modeStr == "JWT") { + Logger::log(LogLevel::DEBUG, "Authorizing via JWT"); + m_auth.setupExchangeCredentials(); + } else if (modeStr == "CLIENT_CREDENTIALS") { + Logger::log(LogLevel::DEBUG, "Authorizing via client id and secret"); + m_auth.setupClientCredentials(); + } else { + Logger::log(LogLevel::DEBUG, + "Valid values of CREST_AUTH_MODE: JWT, CLIENT_CREDENTIALS — " + "skipping client authorization"); + } +} + +struct curl_slist *CrestRequest::createAuthHeader() const { + struct curl_slist *headers = nullptr; + std::string token = m_auth.getAccessToken(); + if (!token.empty()) { + std::string authHeader = "Authorization: Bearer " + token; + headers = curl_slist_append(headers, authHeader.c_str()); + } + return headers; +} + size_t WriteCallback(void *contents, size_t size, size_t nmemb, std::vector<char> *output) { size_t total_size = size * nmemb; @@ -124,7 +157,10 @@ std::string CrestRequest::performRequest(const std::string ¤t_path, /* get a curl handle */ curl = curl_easy_init(); std::string stt; - struct curl_slist *headers = NULL; + struct curl_slist *headers = createAuthHeader(); + if (headers) { + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + } if (curl) { std::string url = Crest::StringUtils::appendEndpoint(m_url, current_path); std::string s; @@ -147,7 +183,7 @@ std::string CrestRequest::performRequest(const std::string ¤t_path, // -k analogue: curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); - + // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); if (js.is_null()) { if (action == Action::DELETE) curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); @@ -210,7 +246,10 @@ std::vector<char> CrestRequest::getPayloadRequest( curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); - + struct curl_slist *headers = createAuthHeader(); + if (headers) { + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + } if (curl) { std::string url = Crest::StringUtils::appendEndpoint(m_url, current_path); std::string response; @@ -252,6 +291,7 @@ std::vector<char> CrestRequest::getPayloadRequest( long response_code; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + curl_slist_free_all(headers); curl_easy_cleanup(curl); // error checking in the server response: @@ -275,8 +315,10 @@ std::string CrestRequest::uploadPayload( curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); - struct curl_slist *headers = NULL; - + struct curl_slist *headers = createAuthHeader(); + if (headers) { + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + } if (curl) { std::string url = Crest::StringUtils::appendEndpoint(m_url, current_path); std::string response; diff --git a/src/JwtUtils.cxx b/src/JwtUtils.cxx new file mode 100644 index 0000000000000000000000000000000000000000..f8e2f05d98db3ed69f18b6e1c0282d441205b0c2 --- /dev/null +++ b/src/JwtUtils.cxx @@ -0,0 +1,77 @@ +#include <CrestApi/JwtUtils.h> + +#include <algorithm> +#include <nlohmann/json.hpp> +#include <sstream> +#include <stdexcept> + +namespace Crest { + +std::string JwtUtils::base64urlDecode(const std::string& input) { + std::string base64 = input; + std::replace(base64.begin(), base64.end(), '-', '+'); + std::replace(base64.begin(), base64.end(), '_', '/'); + while (base64.length() % 4 != 0) + base64 += '='; + + static constexpr unsigned char kDecodeTable[256] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 64, 64, 64, 0, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64}; + + std::string output; + size_t i = 0; + while (i < base64.length()) { + uint32_t val = 0; + int valb = -8; + for (int j = 0; j < 4 && i < base64.length(); ++j) { + unsigned char c = base64[i++]; + if (kDecodeTable[c] == 64) + throw std::runtime_error("Invalid base64url character"); + val = (val << 6) + kDecodeTable[c]; + valb += 6; + } + while (valb >= 0) { + output.push_back(static_cast<char>((val >> valb) & 0xFF)); + valb -= 8; + } + } + return output; +} + +std::time_t JwtUtils::extractExp(const std::string& jwt, int graceSeconds) { + size_t dot1 = jwt.find('.'); + size_t dot2 = jwt.find('.', dot1 + 1); + if (dot1 == std::string::npos || dot2 == std::string::npos) { + throw std::runtime_error("Invalid JWT format"); + } + + std::string payloadEncoded = jwt.substr(dot1 + 1, dot2 - dot1 - 1); + std::string payloadJson = base64urlDecode(payloadEncoded); + + auto js = nlohmann::json::parse(payloadJson, nullptr, false); + if (js.is_discarded()) { + throw std::runtime_error("Failed to parse JWT payload"); + } + + if (!js.contains("exp") || !js["exp"].is_number()) { + throw std::runtime_error("JWT payload does not contain valid 'exp'"); + } + + std::time_t rawExp = js["exp"]; + return rawExp - graceSeconds; +} + +} // namespace Crest diff --git a/src/RunLumiDto.cxx b/src/RunLumiDto.cxx new file mode 100644 index 0000000000000000000000000000000000000000..2b1e5d6e5a69432fb4495a0a6972a74acdd588b8 --- /dev/null +++ b/src/RunLumiDto.cxx @@ -0,0 +1,29 @@ +/* + Copyright (C) 2020-2024 CERN for the benefit of the ATLAS collaboration +*/ + +#include <CrestApi/RunLumiDto.h> + +namespace Crest { + +RunLumiDto::RunLumiDto() : runNumber(0), lb(0), starttime(0), endtime(0) {} + +json RunLumiDto::toJson() const { + json rl = {}; + rl["runNumber"] = runNumber; + rl["lb"] = lb; + rl["starttime"] = starttime; + rl["endtime"] = endtime; + return rl; +} + +RunLumiDto RunLumiDto::fromJson(const json &j) { + RunLumiDto rl; + rl.runNumber = j.value<uint64_t>("runNumber", 0); + rl.lb = j.value<uint64_t>("lb", 0); + rl.starttime = j.value<uint64_t>("starttime", 0); + rl.endtime = j.value<uint64_t>("endtime", 0); + return rl; +} + +} // namespace Crest diff --git a/src/RunLumiSetDto.cxx b/src/RunLumiSetDto.cxx new file mode 100644 index 0000000000000000000000000000000000000000..a6499b1f89eaaeed04c9ca5d53b6a6d616dca1c1 --- /dev/null +++ b/src/RunLumiSetDto.cxx @@ -0,0 +1,30 @@ +/* + Copyright (C) 2020-2024 CERN for the benefit of the ATLAS collaboration +*/ + +#include <CrestApi/RunLumiSetDto.h> + +namespace Crest { + +json RunLumiSetDto::toJson() const { + json baseJson = CrestBaseResponse::toJson(); + json jsonResources = json::array(); + for (const auto &resource : resources) { + jsonResources.push_back(((RunLumiDto)resource).toJson()); + } + baseJson["resources"] = jsonResources; + return baseJson; +} + +RunLumiSetDto RunLumiSetDto::fromJson(const json &j) { + RunLumiSetDto rlSet; + rlSet.loadFromJson(j); + json jsonResources = j.value("resources", json::array()); + for (auto it = jsonResources.begin(); it != jsonResources.end(); ++it) { + rlSet.resources.push_back(RunLumiDto::fromJson(*it)); + } + + return rlSet; +} + +} // namespace Crest diff --git a/src/StringUtils.cxx b/src/StringUtils.cxx index bcf41966357b539fd073c5dc795aed3dc0eaaf8f..f6f3e1d1a041d3e8733874ff0c38bd340ae84cf1 100644 --- a/src/StringUtils.cxx +++ b/src/StringUtils.cxx @@ -63,7 +63,7 @@ ParsedUrl Crest::StringUtils::parseUrl(std::string_view url) { // The remaining part is the apipath if (!url.empty() && url[0] == '/') { - result.apipath = ""; + result.apipath = url; } return result; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e12cbee9b3c5913d98e99752fc50b2b3b204ad55..37d9c7b88800bbf0617047ba776a3a236868a286 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,6 +8,7 @@ function(crestapi_add_test TESTNAME) Boost::unit_test_framework nlohmann_json::nlohmann_json CrestApiLib + # this is for ACTS: CrestResolverLib ) add_test( NAME ${TESTNAME} @@ -15,8 +16,10 @@ function(crestapi_add_test TESTNAME) ) endfunction() + # Declare the tests. crestapi_add_test(CrestApi_test) crestapi_add_test(CrestApiFs_test) crestapi_add_test(json_parse) -#crestapi_add_test(test-json) +## crestapi_add_test(test-json) +## crestapi_add_test(CrestFileResolver_test) diff --git a/test/CrestApi_test.cxx b/test/CrestApi_test.cxx index 9e675120322462dbfef499a6993659dd5a5a1a26..fcb0812f2ff8d08610a91a6a2def84529027223d 100644 --- a/test/CrestApi_test.cxx +++ b/test/CrestApi_test.cxx @@ -20,7 +20,7 @@ using namespace Crest; BOOST_AUTO_TEST_SUITE(CrestApiTest) -const std::string crest_server = "https://atlaf-alma9-01.cern.ch/api-v6.0"; +const std::string crest_server = "http://atlaf-alma9-02.cern.ch:8080/api-v6.0"; const std::string tagname = "test_ctest_tag_01"; const std::string global_tag = "TEST_GLOBAL_TAG_01"; diff --git a/test/CrestFileResolver_test.cxx b/test/CrestFileResolver_test.cxx new file mode 100644 index 0000000000000000000000000000000000000000..abb1b4190c679b0c7083c468089fd43123534dde --- /dev/null +++ b/test/CrestFileResolver_test.cxx @@ -0,0 +1,46 @@ + +/** + * @file CrestApi/test/CrestApi_test.cxx + * @brief Some tests for server methods. + */ + +#include "CrestApi/GlobalTagMapDto.h" +#include "CrestApi/GlobalTagMapSetDto.h" +#include "CrestApi/IovSetDto.h" +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN +#define BOOST_TEST_MODULE TEST_CRESTAPI + +#include <fstream> +#include <iostream> +#include <string> + +#include "../CrestApi/CrestApi.h" +#include "../CrestApi/StringUtils.h" +#include "../acts/CrestFileResolver.h" + +using namespace Crest; + +const std::string crest_server = "http://atlaf-alma9-02.cern.ch:8080/api-v6.0"; +const std::string global_tag = "TEST-GEOMETRY-01"; + +int main() { + std::string geoglobal_tag = "TEST-GEOMETRY-01"; + std::cout << "Global tag for geometry: " << geoglobal_tag << std::endl; + + try { + // Create an instance of CrestFileResolver + CrestFileResolver crestFileResolver(crest_server, "/tmp/crest_test/", + "/Material/TrackingGeo"); + // Resolve the file path + std::string file_path = crestFileResolver.resolveFilePath( + geoglobal_tag, "/Material/TrackingGeo", "test_file.json"); + // Print the parsed inner JSON + std::cout << "Dumped JSON from Geometry Global Tag for tracking material: " + << file_path << std::endl; + } catch (const std::exception &e) { + std::cerr << "Error parsing JSON: " << e.what() << std::endl; + } + + return 0; +} diff --git a/test/test-json.cxx b/test/test-json.cxx index bfd349542250cd9894386fb7e7121bcaa6d5a6f7..a93618a1893d3fbfaa17e03f3734497a864c44e5 100644 --- a/test/test-json.cxx +++ b/test/test-json.cxx @@ -79,10 +79,11 @@ int main() { std::cout << std::endl; // Fill a json object with data + bool test_boolean = true; json j4; j4["a_char"] = new char('c'); j4["a_string"] = "string"; - j4["a_bool"] = true; + j4["a_bool"] = test_boolean; j4["a_int"] = 42; j4["a_float"] = 3.141; j4["a_null"] = nullptr; @@ -90,6 +91,8 @@ int main() { j4["an_unsigned_long"] = 42ul; j4["an_unsigned_long_long"] = 42ull; + // Print the json object + std::cout << "Print JSON object j4:\n"; std::cout << j4 << '\n'; // Read back data using iterators diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..4dcd6489500c4b1cbcd5d4e491b2277bdb724204 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,96 @@ +# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration +# Set the name of the package. +cmake_minimum_required(VERSION 3.12) +project(CrestContainer VERSION 6.1 LANGUAGES CXX) + +# Find the necessary externals. +#find_package(CrestApi 6.1 REQUIRED) + + +# Set the C++ standard to use. +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF CACHE BOOL + "Whether to enable compiler-specific C++ extensions") + +# Set the default library type to build. +option(BUILD_SHARED_LIBS + "Flag for building shared/static libraries" TRUE) + +# Use the standard GNU installation directories. +include(GNUInstallDirs) +set(CMAKE_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/CrestContainer") + +# Turn on all warnings with GCC and Clang. +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +endif() + +# Declare the header and source file names. +set(HEADERS + "./CrestContainer.h" +) + +set(SOURCES + "./CrestContainer.cxx" +) +# Use RPATH for build tree +set(CMAKE_SKIP_BUILD_RPATH FALSE) +# Don't use RPATH for install tree when building +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +# Add the automatically determined parts of the RPATH +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + +# Set up the build of the main library of the project. +add_library(CrestContainerLib ${SOURCES} ${HEADERS}) +target_link_libraries(CrestContainerLib + PUBLIC + CURL::libcurl + Boost::boost + nlohmann_json::nlohmann_json + CrestApiLib + PRIVATE + OpenSSL::SSL +) +target_include_directories(CrestContainerLib + PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> +) + +set_target_properties(CrestContainerLib PROPERTIES + PUBLIC_HEADER "${HEADERS}" + VERSION "${PROJECT_VERSION}" +) + +# Install the library. +install(TARGETS CrestContainerLib + EXPORT CrestContainerTargets + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/CrestContainer" +) + +# Export the project. +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CrestContainerConfigVersion.cmake" + VERSION "${PROJECT_VERSION}" + COMPATIBILITY AnyNewerVersion +) +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CrestContainerConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CrestContainerConfig.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_CMAKEDIR}" + PATH_VARS CMAKE_INSTALL_INCLUDEDIR CMAKE_INSTALL_LIBDIR +) +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CrestContainerConfigVersion.cmake" + "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CrestContainerConfig.cmake" + DESTINATION "${CMAKE_INSTALL_CMAKEDIR}" +) +install(EXPORT CrestContainerTargets + NAMESPACE CrestApi:: + DESTINATION "${CMAKE_INSTALL_CMAKEDIR}" +) diff --git a/tools/CrestContainer.cxx b/tools/CrestContainer.cxx index 2e31ab0a4dd483c35824c14a1e1c3c1644a5de35..747c29d7b57b0e7d001f945b50a0b0a84427c695 100644 --- a/tools/CrestContainer.cxx +++ b/tools/CrestContainer.cxx @@ -4,6 +4,7 @@ #include "CrestContainer.h" +#include <CrestApi/CrestCondException.h> #include <stdarg.h> #include <boost/algorithm/string.hpp> @@ -13,33 +14,15 @@ #include <fstream> #include <iomanip> -#include "CrestApi/CrestCondException.h" - using json = nlohmann::json; -Crest::CrestContainer::CrestContainer() : m_modeId(ModeId::Standard) { - initStringToType(); +Crest::CrestContainer::CrestContainer(Crest::ModeId mode) : m_modeId(mode) { + ; } Crest::CrestContainer::~CrestContainer() { flush(); } -void Crest::CrestContainer::initStringToType() { - // Loop over key value pairs in s_typeToString map and fill s_StringToType - for (const auto &[key, value] : s_typeToString) { - s_StringToType[value] = key; - } -} - -void Crest::CrestContainer::addStreamerInfoField(const std::string &key, - const std::string &value) { - m_streamer_info_data[key] = value; -} - -void Crest::CrestContainer::cleanStreamerInfo() { - m_streamer_info_data.clear(); -} - bool Crest::CrestContainer::isVectorPayload() { return m_isVectorPayload; } @@ -49,11 +32,21 @@ void Crest::CrestContainer::setVectorPayload(bool isVectorPayload) { Crest::TypeId Crest::CrestContainer::stringToTypeId( const std::string &type) const { - auto it = s_StringToType.find(type); - if (it == s_StringToType.end()) { - throw CommonCrestException("The type of parameter is not defined."); + for (auto &t : s_typeToString) { + if (t.second.compare(type) == 0) + return t.first; } - return it->second; + throw CommonCrestException("The type of parameter is not defined."); +} + +bool Crest::CrestContainer::compareStrTimestamp(std::string as, + std::string bs) { + auto a = std::stol(as); + auto b = std::stol(bs); + + // first parameter should be greater than + // second one (for decreasing order) + return a < b; } // Function to convert an integer to a TypeId @@ -64,8 +57,8 @@ Crest::TypeId Crest::CrestContainer::intToTypeId(int value) const { return static_cast<Crest::TypeId>(value); } -std::string Crest::CrestContainer::base64Encode(const uint8_t *data, - unsigned int len) { +std::string Crest::CrestContainer::base64_encode(const uint8_t *data, + unsigned int len) { using namespace boost::archive::iterators; typedef base64_from_binary<transform_width<const char *, 6, 8>> base64_encoder; @@ -81,7 +74,7 @@ std::string Crest::CrestContainer::base64Encode(const uint8_t *data, } // Decode base64 data to binary -std::vector<unsigned char> Crest::CrestContainer::base64Decode( +std::vector<unsigned char> Crest::CrestContainer::base64_decode( const std::string &encodedData) { using namespace boost::archive::iterators; typedef transform_width<binary_from_base64<std::string::const_iterator>, 8, 6> @@ -160,10 +153,8 @@ void Crest::CrestContainer::addRecord(const char *name, int number, ...) { case TypeId::String4k: case TypeId::String64k: case TypeId::String16M: - case TypeId::String128M: case TypeId::Blob64k: case TypeId::Blob16M: - case TypeId::Blob128M: m_row[name] = std::string(va_arg(ap, const char *)); break; default: @@ -179,22 +170,21 @@ void Crest::CrestContainer::addData(const char *channel_id) { void Crest::CrestContainer::addExternalData(const char *channel_id, const nlohmann::json &data) { - - validatePayloadSize(data); + // std::cerr<<"addExternalData channel_id="<<channel_id<<" + // json="<<data.dump()<<std::endl; nlohmann::json arr_data = m_isVectorPayload ? nlohmann::json::array() : nlohmann::json(); - - for (const auto &data_row : data) // m_vector_data) - { - auto row_arr_data = createRowArray(data_row); - - if (m_isVectorPayload) { + if (m_isVectorPayload) { + for (const auto &data_row : data) // m_vector_data) + { + auto row_arr_data = createRowArray(data_row); arr_data.push_back(row_arr_data); - } else { - arr_data = row_arr_data; } + m_payload[channel_id] = arr_data; + } else { + auto row_arr_data = createRowArray(data); + m_payload[channel_id] = row_arr_data; } - m_payload[channel_id] = arr_data; m_vector_data.clear(); m_row.clear(); } @@ -203,12 +193,12 @@ void Crest::CrestContainer::validatePayloadSize( const nlohmann::json &data) const { if (!data.is_array()) throw CommonCrestException( - "The format of data is wrong. It should be the " - "vector (size 1 for non vector payload)"); + "The format of data is wrong. It should be the vector (size 1 for non " + "vector payload)"); if (!m_isVectorPayload && m_vector_data.size() > 1) { throw CommonCrestException( - "The payload is not a vector, but the size " - "seems to be larger than 1...."); + "The payload is not a vector, but the size seems to be larger than " + "1...."); } } @@ -228,13 +218,28 @@ nlohmann::json Crest::CrestContainer::createRowArray( void Crest::CrestContainer::addIov(const uint64_t since) { m_iov_data["since"] = since; m_iov_data["data"] = m_payload; - m_full_data[std::to_string(since)] = m_payload; + m_full_data[since] = m_payload; m_payload.clear(); } void Crest::CrestContainer::selectIov(const uint64_t since) { + // std::cerr<<"Crest::CrestContainer::selectIov start"<<std::endl; m_iov_data["since"] = since; - m_iov_data["data"] = m_full_data[std::to_string(since)]; + if (m_full_data.contains(since)) { + m_iov_data["data"] = m_full_data[since]; + m_payload = m_full_data[since]; + } else { + m_iov_data["data"] = {}; + m_payload = {}; + } + // std::cerr<<"Crest::CrestContainer::selectIov end"<<std::endl; +} + +uint64_t Crest::CrestContainer::getFirstTime() { + if (m_full_data.size() == 0) { + throw CommonCrestException("No IOV in CrestContainer"); + } + return m_full_data.begin()->first; } std::vector<std::string> Crest::CrestContainer::channelIds() { @@ -242,6 +247,7 @@ std::vector<std::string> Crest::CrestContainer::channelIds() { for (auto &x : m_iov_data["data"].items()) { chs.push_back(x.key()); } + sort(chs.begin(), chs.end(), Crest::CrestContainer::compareStrTimestamp); return chs; } const std::vector<std::pair<std::string, Crest::TypeId>> & @@ -249,7 +255,7 @@ Crest::CrestContainer::getMPayloadSpec() { return m_payload_spec; } -const nlohmann::json &Crest::CrestContainer::getPayloadChannel( +const nlohmann::json Crest::CrestContainer::getPayloadChannel( const char *channel_id) { if (m_payload.empty()) { m_payload = m_iov_data["data"]; @@ -297,8 +303,7 @@ void Crest::CrestContainer::putRow2Vector() { const nlohmann::json &Crest::CrestContainer::getRow() { if (m_isVectorPayload) return m_vector_data; - m_vector_data.push_back(m_row); - return m_vector_data; + return m_row; } const nlohmann::json &Crest::CrestContainer::getPayload() { @@ -336,27 +341,6 @@ std::string Crest::CrestContainer::getJsonIovData() { return m_iov_data.dump(); } -nlohmann::json Crest::CrestContainer::getStoreSetDto() { - nlohmann::json j; - nlohmann::json resources = nlohmann::json::array(); - - nlohmann::json storeDto; - // Loop over the iov list container to add the data to the storeDto - // Iterating over the keys of m_full_data - for (auto &[key, data] : m_full_data.items()) { - storeDto["since"] = (uint64_t)std::stoull(key); - storeDto["data"] = data.dump(); - storeDto["streamerInfo"] = m_streamer_info_data.dump(); - resources.push_back(storeDto); - } - - j["resources"] = resources; - j["format"] = "StoreSetDto"; - j["datatype"] = "data"; - j["size"] = resources.size(); - return j; -} - json Crest::CrestContainer::getPayloadSpec() { json pspec_data = json::array(); for (auto &column : m_payload_spec) { @@ -523,17 +507,120 @@ void Crest::CrestContainer::parseOldFormat(std::string &colName, } default: { throw std::runtime_error("UNTREATED TYPE!"); - break; } } + } catch (json::exception &e) { std::cerr << e.what() << std::endl; throw std::runtime_error(e.what()); } } - -void Crest::CrestContainer::fromJson(const uint64_t since, - const nlohmann::json &j_in) { +void Crest::CrestContainer::parseData(const nlohmann::json &values) { + if (values.is_array() && values.size() == m_payload_spec.size()) { + for (size_t i = 0; i < values.size(); ++i) { + const auto &spec = m_payload_spec[i]; + std::string colName = spec.first; + TypeId colType = spec.second; + if (values[i].is_string() && + (colType == TypeId::UChar || colType == TypeId::Bool || + colType == TypeId::Int16 || colType == TypeId::UInt16 || + colType == TypeId::Int32 || colType == TypeId::UInt32 || + colType == TypeId::Int64 || colType == TypeId::UInt63 || + colType == TypeId::Float || colType == TypeId::Double)) { + parseOldFormat(colName, colType, values[i]); + continue; + } + if (values[i].is_null()) { + m_row[colName] = nullptr; + continue; + } + switch (colType) { + case TypeId::Bool: + m_row[colName] = values[i].get<bool>(); + break; + case TypeId::UChar: + m_row[colName] = values[i].get<unsigned char>(); + break; + case TypeId::Int16: + case TypeId::Int32: + m_row[colName] = values[i].get<int>(); + break; + case TypeId::UInt16: + case TypeId::UInt32: + m_row[colName] = values[i].get<unsigned int>(); + break; + case TypeId::UInt63: + m_row[colName] = values[i].get<uint64_t>(); + break; + case TypeId::Int64: + m_row[colName] = values[i].get<int64_t>(); + break; + case TypeId::Float: + case TypeId::Double: + m_row[colName] = values[i].get<double>(); + break; + case TypeId::String255: + case TypeId::String4k: + case TypeId::String64k: + case TypeId::String16M: + case TypeId::String128M: + m_row[colName] = values[i].get<std::string>(); + break; + case TypeId::Blob128M: + case TypeId::Blob64k: + case TypeId::Blob16M: + m_row[colName] = values[i].get<std::string>(); + break; + default: + throw CommonCrestException( + "CrestContainer::parseData: Unsupported column type."); + } + } + } else { + if (!values.is_array()) + std::cerr << "CrestContainer::parseData values is not array" << std::endl; + else + std::cerr << "CrestContainer::parseData values number=" << values.size() + << " m_payload_spec.size()=" << m_payload_spec.size() + << std::endl; + throw CommonCrestException( + "CrestContainer::parseData: Mismatch in number of values."); + } +} +std::vector<uint64_t> Crest::CrestContainer::fromJson( + uint64_t since, const nlohmann::json &j_in) { + if (m_modeId == Crest::ModeId::Standard) { + std::cerr << "CrestContainer::fromJson mode=Standard" << std::endl; + nlohmann::json j = j_in; + if (j.is_string()) { + std::istringstream ss(to_string(j)); + std::string st; + ss >> std::quoted(st); + j = json::parse(st); + } + // Accessing the "data" object + if (j.contains("data") && j["data"].is_object()) { + readCommonType(since, j_in); + } else if (j.is_object()) { + readCommonType(since, j); + } else { + std::cerr << "CrestContainer::fromJson json:" << j << std::endl; + throw CommonCrestException( + "CrestContainer::fromJson: JSON is not a JSON object."); + } + std::vector<uint64_t> ret; + ret.push_back(since); + return ret; + } else if (m_modeId == Crest::ModeId::DCS_FULL) { + std::cerr << "CrestContainer::fromJson mode=DCS_FULL" << std::endl; + return readDcsFullType(j_in); + } else { + throw CommonCrestException( + "CrestContainer::fromJson: Unsupported type of payload."); + } +} +std::vector<uint64_t> Crest::CrestContainer::readDcsFullType( + const nlohmann::json &j_in) { nlohmann::json j = j_in; if (j.is_string()) { std::istringstream ss(to_string(j)); @@ -541,96 +628,119 @@ void Crest::CrestContainer::fromJson(const uint64_t since, ss >> std::quoted(st); j = json::parse(st); } - if (m_modeId != ModeId::Standard) - throw CommonCrestException("Unsupported type of payload."); - // Accessing the "data" object + // std::cerr<<"readDcsFullType: "<<j<<std::endl; + nlohmann::json dcs_data; if (j.contains("data") && j["data"].is_object()) { - const auto &data = j["data"]; - - // Loop over each channel in the data - for (const auto &channel : data.items()) { - std::string channelKey = channel.key(); - const auto &data_ch = channel.value(); - nlohmann::json vecJson(json::value_t::array); - if (m_isVectorPayload) - vecJson = data_ch; + // std::cerr<<"readDcsFullType: "<<j<<std::endl; + dcs_data = j["data"]; + } else + dcs_data = j; + std::vector<uint64_t> ret; + if (j.is_object()) { + for (const auto &[key, value] : dcs_data.items()) { + uint64_t since = std::stoull(key); + // std::cerr<<"read since="<<since<<std::endl; + readCommonType(since, value); + ret.push_back(since); + /*if(data.contains("since")) + since=data["since"].template get<std::int64_t>(); else - vecJson.push_back(data_ch); - - for (const auto &values : vecJson) { - // TODO: this will not work for vector payloads - // Check if the number of values in the array matches the expected - // number of columns - if (values.is_array() && values.size() == m_payload_spec.size()) { - for (size_t i = 0; i < values.size(); ++i) { - const auto &spec = m_payload_spec[i]; - std::string colName = spec.first; - TypeId colType = spec.second; - if (values[i].is_string() && - (colType == TypeId::Bool || colType == TypeId::Int16 || - colType == TypeId::UInt16 || colType == TypeId::Int32 || - colType == TypeId::UInt32 || colType == TypeId::Int64 || - colType == TypeId::UInt63 || colType == TypeId::Float || - colType == TypeId::Double)) { - parseOldFormat(colName, colType, values[i]); - continue; - } - switch (colType) { - case TypeId::Bool: - m_row[colName] = values[i].get<bool>(); - break; - case TypeId::UChar: - m_row[colName] = values[i].get<unsigned char>(); - break; - case TypeId::Int16: - case TypeId::Int32: - m_row[colName] = values[i].get<int>(); - break; - case TypeId::UInt16: - case TypeId::UInt32: - m_row[colName] = values[i].get<unsigned int>(); - break; - case TypeId::UInt63: - m_row[colName] = values[i].get<uint64_t>(); - break; - case TypeId::Int64: - m_row[colName] = values[i].get<int64_t>(); - break; - case TypeId::Float: - case TypeId::Double: - m_row[colName] = values[i].get<double>(); - break; - case TypeId::String255: - case TypeId::String4k: - case TypeId::String64k: - case TypeId::String16M: - case TypeId::String128M: - m_row[colName] = values[i].get<std::string>(); - break; - case TypeId::Blob128M: - case TypeId::Blob64k: - case TypeId::Blob16M: - m_row[colName] = values[i].get<std::string>(); - break; - default: - throw CommonCrestException("Unsupported column type."); - } - } - } else { - std::cerr << "CrestContainer::fromJson: Mismatch in number of values " - "for channel " - << channelKey << std::endl; - } - if (m_isVectorPayload) - putRow2Vector(); - } - addData(channelKey.c_str()); + throw CommonCrestException("CrestContainer::readDcsFullType: Wrong + payload format."); readCommonType(since,data);*/ } - } else { - std::cerr << "CrestContainer::fromJson: JSON does not contain a 'data' " - "object or it is not a JSON object." - << std::endl; - std::cerr << "CrestContainer::fromJson json:" << j << std::endl; + } + return ret; +} +void Crest::CrestContainer::readCommonType(uint64_t since, + const nlohmann::json &j_in) { + // std::cerr<<"readCommonType j_in="<<j_in.dump()<<std::endl; + // Loop over each channel in the data + for (const auto &channel : j_in.items()) { + std::string channelKey = channel.key(); + const auto &data_ch = channel.value(); + nlohmann::json vecJson(json::value_t::array); + if (m_isVectorPayload) + vecJson = data_ch; + else + vecJson.push_back(data_ch); + + for (const auto &values : vecJson) { + parseData(values); + if (m_isVectorPayload) + putRow2Vector(); + } + addData(channelKey.c_str()); } addIov(since); + // std::cout << " m_iov_data: " << m_iov_data.dump(2) << std::endl; + // std::cout << "CondContainer: from json method has filled internal + // attributes" << std::endl; +} + +nlohmann::json Crest::CrestContainer::getStoreSetDto(uint64_t period) { + nlohmann::json j; + nlohmann::json resources = nlohmann::json::array(); + if (m_modeId == Crest::ModeId::Standard) { + nlohmann::json storeDto; + // Loop over the iov list container to add the data to the storeDto + for (const auto &[key, value] : m_full_data) { + storeDto["since"] = key; + storeDto["data"] = value.dump(); + storeDto["streamerInfo"] = ""; // m_streamer_info_data.dump(); + resources.push_back(storeDto); + } + } else if (m_modeId == Crest::ModeId::DCS_FULL) { + uint64_t since = getFirstTime(); + // std::cerr<<"Number of IOV="<<m_full_data.size()<<std::endl; + nlohmann::json storeDto; + nlohmann::json dcs_payload; + nlohmann::json curPayload = m_full_data[since]; + // std::cerr<<"Number of chs="<<curPayload.size()<<std::endl; + // for (std::pair<uint64_t, uint64_t> pair : m_iovs) { + for (const auto &[key, value] : m_full_data) { + curPayload.update(value); + dcs_payload[std::to_string(key)] = curPayload; + if (period > 0 && (key - since) > period) { + storeDto["since"] = since; + storeDto["data"] = dcs_payload.dump(); + storeDto["streamerInfo"] = ""; + resources.push_back(storeDto); + since = key; + dcs_payload = {}; + } + } + // nlohmann::json obj={}; + // obj["iovs"]=iovs; + // obj["obj"]=dcs_payload; + if (dcs_payload.size() > 0) { + storeDto["since"] = since; + storeDto["data"] = dcs_payload.dump(); + storeDto["streamerInfo"] = ""; + resources.push_back(storeDto); + } + } + j["resources"] = resources; + j["format"] = "StoreSetDto"; + j["datatype"] = "data"; + j["size"] = resources.size(); + + return j; +} + +void Crest::CrestContainer::addChannel(const std::string &channelId, + const std::string &channelName) { + // Check if the channel is already in the map + if (m_chanMap.find(channelId) == m_chanMap.end()) { + if (channelName.c_str() == nullptr) { + m_chanMap[channelId] = ""; + } else { + m_chanMap[channelId] = channelName; + } + std::cout << "CrestContainer: added to m_chanMap " << channelId << " = " + << m_chanMap[channelId] << std::endl; + return; + } + std::cout << "CrestContainer: ChannelID " << channelId + << " found in map ... don't do anything - size is " + << m_chanMap.size() << std::endl; } diff --git a/tools/CrestContainer.h b/tools/CrestContainer.h index f5315e34384767c6852c84d78941f46591b0b749..5a8cc30aaa26f81418b8d36939433b6ee1421192 100644 --- a/tools/CrestContainer.h +++ b/tools/CrestContainer.h @@ -68,50 +68,26 @@ const static std::map<TypeId, std::string> s_typeToString = { enum class ModeId { Standard, DCS, DCS_FULL }; class CrestContainer { private: - std::map<std::string, TypeId> s_StringToType; std::vector<std::pair<std::string, TypeId>> m_payload_spec; nlohmann::json m_payload = {}; nlohmann::json m_row = {}; nlohmann::json m_iov_data = {}; - nlohmann::json m_streamer_info_data = {}; nlohmann::json m_vector_data = nlohmann::json::array(); - // nlohmann::json m_iov_list=nlohmann::json::array(); + std::map<uint64_t, nlohmann::json> m_full_data; + // Utility map to store the channel_id and the index of the channel + std::map<std::string, std::string> m_chanMap; + std::map<std::string, std::string> m_chanIdxMap; - nlohmann::json m_full_data = {}; ModeId m_modeId; bool m_isVectorPayload = false; - void initStringToType(); - void validatePayloadSize(const nlohmann::json &data) const; - nlohmann::json createRowArray(const nlohmann::json &data_row) const; - const nlohmann::json &getRow(); + void validatePayloadSize(const nlohmann::json& data) const; + nlohmann::json createRowArray(const nlohmann::json& data_row) const; + const nlohmann::json& getRow(); public: - /** - * Constructor and destructor. - * The container is used to store the payload and the iov data, helping in the - * definition of the payload structure. The payload is a json object that is - * added to the iov data. It contains a set of channels, each one with its own - * data. Once the container has been filled, it can be converted to a json - * object. When consumed, it is necessary to flush it in order to reuse it. - */ - CrestContainer(); + CrestContainer(ModeId mode = ModeId::Standard); ~CrestContainer(); - - /** - * Utility function to add a field to the streamer info. - * The streamer info is a json object that is added to the payload. - * It is used to store metadata information about the payload. - * @param key The key of the field. - * @param value The value of the field. - */ - void addStreamerInfoField(const std::string &key, const std::string &value); - - /** - * Utility function to clean the streamer info. - */ - void cleanStreamerInfo(); - /** * @brief It adds a column to the payload specification. * @param name The name of the column. @@ -121,9 +97,9 @@ class CrestContainer { * m_payload_spec. The methods with a different signature are just overloads * to make it easier to use. */ - void addColumn(const std::string &name, TypeId type); - void addColumn(const std::string &name, const char *type); - void addColumn(const std::string &name, uint32_t type); + void addColumn(const std::string& name, TypeId type); + void addColumn(const std::string& name, const char* type); + void addColumn(const std::string& name, uint32_t type); /** * @brief It sets the mode of the container. @@ -131,7 +107,7 @@ class CrestContainer { bool isVectorPayload(); void setVectorPayload(bool isVectorPayload); - Crest::TypeId stringToTypeId(const std::string &type) const; + Crest::TypeId stringToTypeId(const std::string& type) const; Crest::TypeId intToTypeId(int value) const; /** @@ -148,7 +124,12 @@ class CrestContainer { * * This method adds a null to the payload. It fills the json object m_row. */ - void addNullRecord(const char *name); + void addNullRecord(const char* name); + + /** + * @brief It adds a channel to the channels map. + */ + void addChannel(const std::string& channelId, const std::string& channelName); /** * @brief It adds a record to the payload. @@ -158,7 +139,7 @@ class CrestContainer { * * This method adds a record to the payload. It fills the json object m_row. */ - void addRecord(const char *name, int number, ...); + void addRecord(const char* name, int number, ...); /** * @brief It associate the payload row to a channel_id. * @param channel_id The channel_id to associate the payload row. @@ -166,7 +147,7 @@ class CrestContainer { * The method adds the current payload row to the json object m_payload. The * row is associated with the channel_id. */ - void addData(const char *channel_id); + void addData(const char* channel_id); /** * @brief It associate the payload row to a channel_id. * @param channel_id The channel_id to associate the payload row. @@ -176,7 +157,7 @@ class CrestContainer { * associated with the channel_id. The data is the data to be associated with * the channel_id, and they belong to a previously filled payload row. */ - void addExternalData(const char *channel_id, const nlohmann::json &data); + void addExternalData(const char* channel_id, const nlohmann::json& data); /** * @brief It adds an IOV to the json object m_iov_data. * @param since The since value of the IOV. @@ -186,15 +167,15 @@ class CrestContainer { */ void addIov(const uint64_t since); - const std::vector<std::pair<std::string, TypeId>> &getMPayloadSpec(); + const std::vector<std::pair<std::string, TypeId>>& getMPayloadSpec(); template <typename T> - T getRecord(const std::string &name) { - for (const auto &column : m_payload_spec) { + T getRecord(const std::string& name) { + for (const auto& column : m_payload_spec) { if (column.first == name) { try { return m_row.at(name).get<T>(); - } catch (nlohmann::json::exception &e) { + } catch (nlohmann::json::exception& e) { throw std::runtime_error("JSON exception for key: " + name); } } @@ -202,33 +183,33 @@ class CrestContainer { throw std::runtime_error("Column name not found or type mismatch."); } - const nlohmann::json &getPayload(); - const nlohmann::json &getIovData(); + const nlohmann::json& getPayload(); + const nlohmann::json& getIovData(); - const nlohmann::json &getPayloadChannel(const char *channel_id); + const nlohmann::json getPayloadChannel(const char* channel_id); - void setIovData(const nlohmann::json &j); - void setPayload(const nlohmann::json &j); + void setIovData(const nlohmann::json& j); + void setPayload(const nlohmann::json& j); // return the payload spec as a copy nlohmann::json getPayloadSpec(); - void setPayloadSpec(const nlohmann::json &j); + void setPayloadSpec(const nlohmann::json& j); /** * @brief It encodes the input string to base64. */ - static std::string base64Encode(const uint8_t *bytes_to_encode, - unsigned int in_len); + static std::string base64_encode(const uint8_t* bytes_to_encode, + unsigned int in_len); /** * @brief It decodes the input string from base64. */ - static std::vector<unsigned char> base64Decode( - const std::string &encodedData); + static std::vector<unsigned char> base64_decode( + const std::string& encodedData); /** * @brief It returns the index of the column with the given name. * @param name The name of the column. * It checks the payload spec array to get the index back. */ - int getColumnIndex(const std::string &name); + int getColumnIndex(const std::string& name); /** * @brief It reinitializes the containers. */ @@ -244,26 +225,31 @@ class CrestContainer { * @brief It returns the json representation of the container. */ std::string getJsonIovData(); - /** - * @brief It returns the StoreSetDto json representation of the container. - */ - nlohmann::json getStoreSetDto(); /** * @brief It creates a file with the json representation of the container. */ - void dumpJsonToFile(const nlohmann::json &j, const std::string &filename); + void dumpJsonToFile(const nlohmann::json& j, const std::string& filename); /** * @brief It reads a json file and returns the json object. */ - nlohmann::json readJsonFromFile(const std::string &filename, - const std::string &spec_filename); + nlohmann::json readJsonFromFile(const std::string& filename, + const std::string& spec_filename); /** * @brief It reads a json object to fill the containers. */ - void fromJson(const uint64_t since, const nlohmann::json &j); + std::vector<uint64_t> fromJson(uint64_t since, const nlohmann::json& j); + + void parseOldFormat(std::string& colName, TypeId& typespec, + const nlohmann::json& j); + + nlohmann::json getStoreSetDto(uint64_t period = -1); + + uint64_t getFirstTime(); - void parseOldFormat(std::string &colName, TypeId &typespec, - const nlohmann::json &j); + void parseData(const nlohmann::json& values); + std::vector<uint64_t> readDcsFullType(const nlohmann::json& j_in); + void readCommonType(uint64_t since, const nlohmann::json& j_in); + static bool compareStrTimestamp(std::string as, std::string bs); }; } // namespace Crest #endif diff --git a/tools/cmake/CrestContainerConfig.cmake.in b/tools/cmake/CrestContainerConfig.cmake.in new file mode 100644 index 0000000000000000000000000000000000000000..c1153fe281511111b1ca81980d23db981edbd1ab --- /dev/null +++ b/tools/cmake/CrestContainerConfig.cmake.in @@ -0,0 +1,33 @@ +# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration + +# Avoid hard-coding absolute paths +# This calculates ${PACKAGE_PREFIX_DIR} at run-time based on the +# relative locations of the CMAKE_INSTALL_PREFIX and this config file +# after installation +@PACKAGE_INIT@ + +# Set up useful variables. +set_and_check(CrestContainer_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@") +set_and_check(CrestContainer_LIBRARY_DIR "@PACKAGE_CMAKE_INSTALL_LIBDIR@") + +# Set the include directories that dependent projects will need +set(CrestContainerLib_INCLUDE_DIRS "${CrestContainer_INCLUDE_DIR}") +set(CrestContainer_INCLUDE_DIRS "${CrestContainer_INCLUDE_DIR}") # For backward compatibility + +# Find dependencies +include(CMakeFindDependencyMacro) +find_dependency(CURL) +find_dependency(OpenSSL) +find_dependency(nlohmann_json 3.2.0) +find_dependency(Boost) +find_dependency(CrestApi) + +# Use the cmake generated target +include("${CMAKE_CURRENT_LIST_DIR}/CrestContainerTargets.cmake") +set(CrestContainer_LIBRARIES CrestApi::CrestContainerLib) + +# Print a standard information message about the package being found. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(CrestContainer + REQUIRED_VARS CMAKE_CURRENT_LIST_FILE CrestContainer_INCLUDE_DIR + VERSION_VAR CrestContainer_VERSION )