LCOV - code coverage report
Current view: top level - model - AdminServerProvision.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 96.5 % 962 928
Test Date: 2026-04-17 17:21:26 Functions: 100.0 % 10 10

            Line data    Source code
       1              : /*
       2              :  ___________________________________________
       3              : |    _     ___                        _     |
       4              : |   | |   |__ \                      | |    |
       5              : |   | |__    ) |__ _  __ _  ___ _ __ | |_   |
       6              : |   | '_ \  / // _` |/ _` |/ _ \ '_ \| __|  |  HTTP/2 AGENT FOR MOCK TESTING
       7              : |   | | | |/ /| (_| | (_| |  __/ | | | |_   |  Version 0.0.z
       8              : |   |_| |_|____\__,_|\__, |\___|_| |_|\__|  |  https://github.com/testillano/h2agent
       9              : |                     __/ |                 |
      10              : |                    |___/                  |
      11              : |___________________________________________|
      12              : 
      13              : Licensed under the MIT License <http://opensource.org/licenses/MIT>.
      14              : SPDX-License-Identifier: MIT
      15              : Copyright (c) 2021 Eduardo Ramos
      16              : 
      17              : Permission is hereby  granted, free of charge, to any  person obtaining a copy
      18              : of this software and associated  documentation files (the "Software"), to deal
      19              : in the Software  without restriction, including without  limitation the rights
      20              : to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
      21              : copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
      22              : furnished to do so, subject to the following conditions:
      23              : 
      24              : The above copyright notice and this permission notice shall be included in all
      25              : copies or substantial portions of the Software.
      26              : 
      27              : THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
      28              : IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
      29              : FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
      30              : AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
      31              : LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      32              : OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
      33              : SOFTWARE.
      34              : */
      35              : 
      36              : #include <sstream>
      37              : #include <chrono>
      38              : #include <sys/time.h>
      39              : #include <ctime>
      40              : #include <time.h>       /* time_t, struct tm, time, localtime, strftime */
      41              : #include <string>
      42              : #include <algorithm>
      43              : //#include <fcntl.h> // non-blocking fgets call
      44              : 
      45              : #include <nlohmann/json.hpp>
      46              : #include <arashpartow/exprtk.hpp>
      47              : 
      48              : #include <ert/tracing/Logger.hpp>
      49              : #include <ert/http2comm/Http.hpp>
      50              : 
      51              : #include <AdminServerProvision.hpp>
      52              : #include <MockServerData.hpp>
      53              : #include <MockClientData.hpp>
      54              : #include <Configuration.hpp>
      55              : #include <Vault.hpp>
      56              : #include <FileManager.hpp>
      57              : #include <SocketManager.hpp>
      58              : #include <AdminData.hpp>
      59              : 
      60              : #include <functions.hpp>
      61              : 
      62              : 
      63              : typedef exprtk::expression<double>   expression_t;
      64              : typedef exprtk::parser<double>       parser_t;
      65              : 
      66              : namespace h2agent
      67              : {
      68              : namespace model
      69              : {
      70              : 
      71          342 : AdminServerProvision::AdminServerProvision() : in_state_(DEFAULT_ADMIN_PROVISION_STATE),
      72          342 :     out_state_(DEFAULT_ADMIN_PROVISION_STATE),
      73          513 :     response_delay_ms_(0), mock_server_events_data_(nullptr), mock_client_events_data_(nullptr) {;}
      74              : 
      75              : 
      76          309 : std::shared_ptr<h2agent::model::AdminSchema> AdminServerProvision::getRequestSchema() {
      77              : 
      78          309 :     if(request_schema_id_.empty()) return nullptr;
      79              : 
      80            7 :     if (admin_data_->getSchemaData().size() != 0) { // the only way to destroy schema references, is to clean whole schema data
      81            6 :         if (request_schema_) return request_schema_; // provision cache
      82            2 :         request_schema_ = admin_data_->getSchemaData().find(request_schema_id_);
      83              :     }
      84              : 
      85            3 :     LOGWARNING(
      86              :         if (!request_schema_) ert::tracing::Logger::warning(ert::tracing::Logger::asString("Missing schema '%s' referenced in provision for incoming message: VALIDATION will be IGNORED", request_schema_id_.c_str()), ERT_FILE_LOCATION);
      87              :     );
      88              : 
      89            3 :     return request_schema_;
      90              : }
      91              : 
      92          154 : std::shared_ptr<h2agent::model::AdminSchema> AdminServerProvision::getResponseSchema() {
      93              : 
      94          154 :     if(response_schema_id_.empty()) return nullptr;
      95              : 
      96            3 :     if (admin_data_->getSchemaData().size() != 0) { // the only way to destroy schema references, is to clean whole schema data
      97            2 :         if (response_schema_) return response_schema_; // provision cache
      98            1 :         response_schema_ = admin_data_->getSchemaData().find(response_schema_id_);
      99              :     }
     100              : 
     101            2 :     LOGWARNING(
     102              :         if (!response_schema_) ert::tracing::Logger::warning(ert::tracing::Logger::asString("Missing schema '%s' referenced in provision for outgoing message: VALIDATION will be IGNORED", response_schema_id_.c_str()), ERT_FILE_LOCATION);
     103              :     );
     104              : 
     105            2 :     return response_schema_;
     106              : }
     107              : 
     108          212 : bool AdminServerProvision::processSources(std::shared_ptr<Transformation> transformation,
     109              :         TypeConverter& sourceVault,
     110              :         std::map<std::string, std::string>& variables, /* Command generates "rc" */
     111              :         const std::string &requestUri,
     112              :         const std::string &requestUriPath,
     113              :         const std::map<std::string, std::string> &requestQueryParametersMap,
     114              :         const DataPart &requestBodyDataPart,
     115              :         const nghttp2::asio_http2::header_map &requestHeaders,
     116              :         bool &eraser,
     117              :         std::uint64_t generalUniqueServerSequence,
     118              :         bool usesResponseBodyAsTransformationJsonTarget, const nlohmann::json &responseBodyJson) const {
     119              : 
     120          212 :     switch (transformation->getSourceType()) {
     121            4 :     case Transformation::SourceType::RequestUri:
     122              :     {
     123            4 :         sourceVault.setString(requestUri);
     124            4 :         break;
     125              :     }
     126            2 :     case Transformation::SourceType::RequestUriPath:
     127              :     {
     128            2 :         sourceVault.setString(requestUriPath);
     129            2 :         break;
     130              :     }
     131            2 :     case Transformation::SourceType::RequestUriParam:
     132              :     {
     133            2 :         auto iter = requestQueryParametersMap.find(transformation->getSource());
     134            2 :         if (iter != requestQueryParametersMap.end()) sourceVault.setString(iter->second);
     135              :         else {
     136            1 :             LOGDEBUG(
     137              :                 std::string msg = ert::tracing::Logger::asString("Unable to extract query parameter '%s' in transformation item", transformation->getSource().c_str());
     138              :                 ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     139              :             );
     140            1 :             return false;
     141              :         }
     142            1 :         break;
     143              :     }
     144           22 :     case Transformation::SourceType::RequestBody:
     145              :     {
     146           22 :         if (requestBodyDataPart.isJson()) {
     147           19 :             std::string path = transformation->getSource(); // document path (empty or not to be whole or node)
     148           19 :             replaceVariables(path, transformation->getSourcePatterns(), variables, vault_);
     149           19 :             if (!sourceVault.setObject(requestBodyDataPart.getJson(), path)) {
     150            1 :                 LOGDEBUG(
     151              :                     std::string msg = ert::tracing::Logger::asString("Unable to extract path '%s' from request body (it is null) in transformation item", transformation->getSource().c_str());
     152              :                     ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     153              :                 );
     154            1 :                 return false;
     155              :             }
     156           19 :         }
     157              :         else {
     158            3 :             sourceVault.setString(requestBodyDataPart.str());
     159              :         }
     160           21 :         break;
     161              :     }
     162            3 :     case Transformation::SourceType::ResponseBody:
     163              :     {
     164            3 :         std::string path = transformation->getSource(); // document path (empty or not to be whole or node)
     165            3 :         replaceVariables(path, transformation->getSourcePatterns(), variables, vault_);
     166            3 :         if (!sourceVault.setObject(usesResponseBodyAsTransformationJsonTarget ? responseBodyJson:getResponseBody(), path)) {
     167            1 :             LOGDEBUG(
     168              :                 std::string msg = ert::tracing::Logger::asString("Unable to extract path '%s' from response body (it is null) in transformation item", transformation->getSource().c_str());
     169              :                 ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     170              :             );
     171            1 :             return false;
     172              :         }
     173            2 :         break;
     174            3 :     }
     175            2 :     case Transformation::SourceType::RequestHeader:
     176              :     {
     177            2 :         auto iter = requestHeaders.find(transformation->getSource());
     178            2 :         if (iter != requestHeaders.end()) sourceVault.setString(iter->second.value);
     179              :         else {
     180            1 :             LOGDEBUG(
     181              :                 std::string msg = ert::tracing::Logger::asString("Unable to extract request header '%s' in transformation item", transformation->getSource().c_str());
     182              :                 ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     183              :             );
     184            1 :             return false;
     185              :         }
     186            1 :         break;
     187              :     }
     188            2 :     case Transformation::SourceType::RequestHeaders:
     189              :     {
     190            2 :         nlohmann::json arr = nlohmann::json::array();
     191           50 :         for (const auto &h : requestHeaders) arr.push_back({{"name", h.first}, {"value", h.second.value}});
     192            2 :         sourceVault.setObject(arr, "");
     193            2 :         break;
     194            2 :     }
     195            0 :     case Transformation::SourceType::ResponseHeaders:
     196              :     {
     197              :         // Not available in server provision (no response received yet)
     198            0 :         return false;
     199              :     }
     200            8 :     case Transformation::SourceType::Eraser:
     201              :     {
     202            8 :         eraser = true;
     203            8 :         break;
     204              :     }
     205            3 :     case Transformation::SourceType::Math:
     206              :     {
     207            3 :         std::string expressionString = transformation->getSource();
     208            3 :         replaceVariables(expressionString, transformation->getSourcePatterns(), variables, vault_);
     209              : 
     210              :         /*
     211              :            We don't use builtin variables as we can parse h2agent ones which is easier to implement:
     212              : 
     213              :            typedef exprtk::symbol_table<double> symbol_table_t;
     214              :            symbol_table_t symbol_table;
     215              :            double x = 2.0;
     216              :            symbol_table.add_variable("x",x);
     217              :            expression.register_symbol_table(symbol_table);
     218              :            parser.compile("3*x",expression);
     219              :            std::cout << expression.value() << std::endl; // 3*2
     220              :         */
     221              : 
     222            3 :         expression_t   expression;
     223            3 :         parser_t       parser;
     224            3 :         parser.compile(expressionString, expression);
     225              : 
     226            3 :         double result = expression.value(); // if the result has decimals, set as float. If not, set as integer:
     227            3 :         if (result == (int)result) sourceVault.setInteger(expression.value());
     228            1 :         else sourceVault.setFloat(expression.value());
     229            3 :         break;
     230            3 :     }
     231            2 :     case Transformation::SourceType::Random:
     232              :     {
     233            2 :         int range = transformation->getSourceI2() - transformation->getSourceI1() + 1;
     234            2 :         sourceVault.setInteger(transformation->getSourceI1() + (rand() % range));
     235            2 :         break;
     236              :     }
     237            1 :     case Transformation::SourceType::RandomSet:
     238              :     {
     239            1 :         sourceVault.setStringReplacingVariables(transformation->getSourceTokenized()[rand () % transformation->getSourceTokenized().size()], transformation->getSourcePatterns(), variables, vault_); // replace variables if they exist
     240            1 :         break;
     241              :     }
     242            4 :     case Transformation::SourceType::Timestamp:
     243              :     {
     244            4 :         if (transformation->getSource() == "s") {
     245            1 :             sourceVault.setInteger(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
     246              :         }
     247            3 :         else if (transformation->getSource() == "ms") {
     248            1 :             sourceVault.setInteger(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
     249              :         }
     250            2 :         else if (transformation->getSource() == "us") {
     251            1 :             sourceVault.setInteger(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
     252              :         }
     253            1 :         else if (transformation->getSource() == "ns") {
     254            1 :             sourceVault.setInteger(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
     255              :         }
     256            4 :         break;
     257              :     }
     258            1 :     case Transformation::SourceType::Strftime:
     259              :     {
     260            1 :         std::time_t unixTime = 0;
     261            1 :         std::time (&unixTime);
     262            1 :         char buffer[100] = {0};
     263            1 :         /*size_t size = */strftime(buffer, sizeof(buffer), transformation->getSource().c_str(), localtime(&unixTime));
     264              :         //if (size > 1) { // convert TZ offset to RFC3339 format
     265              :         //    char minute[] = { buffer[size-2], buffer[size-1], '\0' };
     266              :         //    sprintf(buffer + size - 2, ":%s", minute);
     267              :         //}
     268              : 
     269            2 :         sourceVault.setStringReplacingVariables(std::string(buffer), transformation->getSourcePatterns(), variables, vault_); // replace variables if they exist
     270            1 :         break;
     271              :     }
     272            2 :     case Transformation::SourceType::Recvseq:
     273              :     {
     274            2 :         sourceVault.setUnsigned(generalUniqueServerSequence);
     275            2 :         break;
     276              :     }
     277           27 :     case Transformation::SourceType::SVar:
     278              :     {
     279           27 :         std::string varname = transformation->getSource();
     280           27 :         replaceVariables(varname, transformation->getSourcePatterns(), variables, vault_);
     281           27 :         auto iter = variables.find(varname);
     282           27 :         if (iter != variables.end()) sourceVault.setString(iter->second);
     283              :         else {
     284            6 :             LOGDEBUG(
     285              :                 std::string msg = ert::tracing::Logger::asString("Unable to extract source variable '%s' in transformation item", varname.c_str());
     286              :                 ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     287              :             );
     288            6 :             return false;
     289              :         }
     290           21 :         break;
     291           27 :     }
     292           31 :     case Transformation::SourceType::SGVar:
     293              :     {
     294           31 :         std::string varname = transformation->getSource();
     295           31 :         replaceVariables(varname, transformation->getSourcePatterns(), variables, vault_);
     296           31 :         nlohmann::json vaultValue{};
     297           31 :         bool exists = vault_->tryGet(varname, vaultValue);
     298           31 :         if (exists) {
     299           27 :             std::string path = transformation->getSource2();
     300           27 :             if (!path.empty()) replaceVariables(path, transformation->getSourcePatterns(), variables, vault_);
     301           27 :             if (!sourceVault.setObject(vaultValue, path)) {
     302            1 :                 LOGDEBUG(
     303              :                     std::string msg = ert::tracing::Logger::asString("Unable to extract path '%s' from vault entry '%s' in transformation item", path.c_str(), varname.c_str());
     304              :                     ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     305              :                 );
     306            1 :                 return false;
     307              :             }
     308           27 :         }
     309              :         else {
     310            4 :             LOGDEBUG(
     311              :                 std::string msg = ert::tracing::Logger::asString("Unable to extract source vault entry '%s' in transformation item", varname.c_str());
     312              :                 ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     313              :             );
     314            4 :             return false;
     315              :         }
     316           26 :         break;
     317           62 :     }
     318           81 :     case Transformation::SourceType::Value:
     319              :     {
     320           81 :         sourceVault.setStringReplacingVariables(transformation->getSource(), transformation->getSourcePatterns(), variables, vault_); // replace variables if they exist
     321           81 :         break;
     322              :     }
     323            3 :     case Transformation::SourceType::ServerEvent:
     324              :     {
     325              :         // transformation->getSourceTokenized() is a vector:
     326              :         //
     327              :         // requestMethod: index 0
     328              :         // requestUri:    index 1
     329              :         // eventNumber:   index 2
     330              :         // eventPath:     index 3
     331              :         // recvseq:       index 4
     332            3 :         std::string event_method = transformation->getSourceTokenized()[0];
     333            3 :         replaceVariables(event_method, transformation->getSourcePatterns(), variables, vault_);
     334            3 :         std::string event_uri = transformation->getSourceTokenized()[1];
     335            3 :         replaceVariables(event_uri, transformation->getSourcePatterns(), variables, vault_);
     336            3 :         std::string event_number = transformation->getSourceTokenized()[2];
     337            3 :         replaceVariables(event_number, transformation->getSourcePatterns(), variables, vault_);
     338            3 :         std::string event_path = transformation->getSourceTokenized()[3];
     339            3 :         replaceVariables(event_path, transformation->getSourcePatterns(), variables, vault_);
     340            3 :         std::string event_recvseq = transformation->getSourceTokenized()[4];
     341            3 :         replaceVariables(event_recvseq, transformation->getSourcePatterns(), variables, vault_);
     342              : 
     343              :         // Now, access the server data for the former selection values:
     344            3 :         nlohmann::json object;
     345            3 :         std::shared_ptr<MockEvent> mockServerRequest;
     346              : 
     347            3 :         if (!event_recvseq.empty()) {
     348              :             try {
     349            0 :                 DataKey dkey(event_method, event_uri);
     350            0 :                 mockServerRequest = mock_server_events_data_->getEventByRecvSeq(dkey, (std::uint64_t)std::stoull(event_recvseq));
     351            0 :             }
     352            0 :             catch (const std::exception&) { return false; }
     353              :         }
     354              :         else {
     355            3 :             EventKey ekey(event_method, event_uri, event_number);
     356            3 :             mockServerRequest = mock_server_events_data_->getEvent(ekey);
     357            3 :         }
     358              : 
     359            3 :         if (!mockServerRequest) {
     360            1 :             LOGDEBUG(
     361              :                 std::string msg = ert::tracing::Logger::asString("Unable to extract server event for variable '%s' in transformation item", transformation->getSource().c_str());
     362              :                 ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     363              :             );
     364            1 :             return false;
     365              :         }
     366              : 
     367            6 :         if (!sourceVault.setObject(mockServerRequest->getJson(), event_path /* document path (empty or not to be whole 'requests number' or node) */)) {
     368            1 :             ert::tracing::Logger::warning(ert::tracing::Logger::asString("Cannot extract path '%s' from server event for source '%s'", event_path.c_str(), transformation->getSource().c_str()), ERT_FILE_LOCATION);
     369            1 :             return false;
     370              :         }
     371              : 
     372            1 :         LOGDEBUG(
     373              :             std::string msg = ert::tracing::Logger::asString("Extracted object from server event: %s", sourceVault.asString().c_str());
     374              :             ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     375              :         );
     376            1 :         break;
     377           21 :     }
     378            3 :     case Transformation::SourceType::InState:
     379              :     {
     380            3 :         sourceVault.setString(getInState());
     381            3 :         break;
     382              :     }
     383            2 :     case Transformation::SourceType::STxtFile:
     384              :     {
     385            2 :         std::string path = transformation->getSource();
     386            2 :         replaceVariables(path, transformation->getSourcePatterns(), variables, vault_);
     387              : 
     388            2 :         std::string content;
     389            2 :         file_manager_->read(path, content, true/*text*/);
     390            2 :         sourceVault.setString(std::move(content));
     391            2 :         break;
     392            2 :     }
     393            2 :     case Transformation::SourceType::SBinFile:
     394              :     {
     395            2 :         std::string path = transformation->getSource();
     396            2 :         replaceVariables(path, transformation->getSourcePatterns(), variables, vault_);
     397              : 
     398            2 :         std::string content;
     399            2 :         file_manager_->read(path, content, false/*binary*/);
     400            2 :         sourceVault.setString(std::move(content));
     401            2 :         break;
     402            2 :     }
     403            2 :     case Transformation::SourceType::Command:
     404              :     {
     405            2 :         std::string command = transformation->getSource();
     406            2 :         replaceVariables(command, transformation->getSourcePatterns(), variables, vault_);
     407              : 
     408              :         static char buffer[256];
     409            2 :         std::string output{};
     410              : 
     411            2 :         FILE *fp = popen(command.c_str(), "r");
     412            2 :         variables["rc"] = "-1"; // rare case where fp could be NULL
     413            2 :         if (fp) {
     414              :             /* This makes asyncronous the command execution, but we will have broken pipe and cannot capture anything.
     415              :             // fgets is blocking (https://stackoverflow.com/questions/6055702/using-fgets-as-non-blocking-function-c/6055774#6055774)
     416              :             int fd = fileno(fp);
     417              :             int flags = fcntl(fd, F_GETFL, 0);
     418              :             flags |= O_NONBLOCK;
     419              :             fcntl(fd, F_SETFL, flags);
     420              :             */
     421              : 
     422            3 :             while(fgets(buffer, sizeof(buffer), fp))
     423              :             {
     424            1 :                 output += buffer;
     425              :             }
     426            6 :             variables["rc"] = std::to_string(WEXITSTATUS(/* status = */pclose(fp))); // rc = status >>= 8; // divide by 256
     427              :         }
     428              : 
     429            2 :         sourceVault.setString(std::move(output));
     430            2 :         break;
     431            2 :     }
     432            3 :     case Transformation::SourceType::ClientEvent:
     433              :     {
     434              :         // transformation->getSourceTokenized() is a vector:
     435              :         //
     436              :         // clientEndpointId: index 0
     437              :         // requestMethod:    index 1
     438              :         // requestUri:       index 2
     439              :         // eventNumber:      index 3
     440              :         // eventPath:        index 4
     441              :         // sendseq:          index 5
     442            3 :         std::string event_endpoint = transformation->getSourceTokenized()[0];
     443            3 :         replaceVariables(event_endpoint, transformation->getSourcePatterns(), variables, vault_);
     444            3 :         std::string event_method = transformation->getSourceTokenized()[1];
     445            3 :         replaceVariables(event_method, transformation->getSourcePatterns(), variables, vault_);
     446            3 :         std::string event_uri = transformation->getSourceTokenized()[2];
     447            3 :         replaceVariables(event_uri, transformation->getSourcePatterns(), variables, vault_);
     448            3 :         std::string event_number = transformation->getSourceTokenized()[3];
     449            3 :         replaceVariables(event_number, transformation->getSourcePatterns(), variables, vault_);
     450            3 :         std::string event_path = transformation->getSourceTokenized()[4];
     451            3 :         replaceVariables(event_path, transformation->getSourcePatterns(), variables, vault_);
     452            3 :         std::string event_sendseq = transformation->getSourceTokenized()[5];
     453            3 :         replaceVariables(event_sendseq, transformation->getSourcePatterns(), variables, vault_);
     454              : 
     455            3 :         DataKey dkey(event_endpoint, event_method, event_uri);
     456            3 :         std::shared_ptr<MockEvent> mockClientRequest;
     457              : 
     458            3 :         if (!event_sendseq.empty()) {
     459              :             try {
     460            0 :                 mockClientRequest = mock_client_events_data_->getEventBySendSeq(dkey, (std::uint64_t)std::stoull(event_sendseq));
     461              :             }
     462            0 :             catch (const std::exception&) { return false; }
     463              :         }
     464              :         else {
     465            3 :             EventKey ekey(dkey, event_number);
     466            3 :             mockClientRequest = mock_client_events_data_->getEvent(ekey);
     467            3 :         }
     468              : 
     469            3 :         if (!mockClientRequest) {
     470            1 :             LOGDEBUG(
     471              :                 std::string msg = ert::tracing::Logger::asString("Unable to extract client event for variable '%s' in transformation item", transformation->getSource().c_str());
     472              :                 ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     473              :             );
     474            1 :             return false;
     475              :         }
     476              : 
     477            6 :         if (!sourceVault.setObject(mockClientRequest->getJson(), event_path)) {
     478            1 :             ert::tracing::Logger::warning(ert::tracing::Logger::asString("Cannot extract path '%s' from client event for source '%s'", event_path.c_str(), transformation->getSource().c_str()), ERT_FILE_LOCATION);
     479            1 :             return false;
     480              :         }
     481              : 
     482            1 :         LOGDEBUG(
     483              :             std::string msg = ert::tracing::Logger::asString("Extracted object from client event: %s", sourceVault.asString().c_str());
     484              :             ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     485              :         );
     486            1 :         break;
     487           24 :     }
     488              :     // Not applicable in server context:
     489            0 :     case Transformation::SourceType::Sendseq:
     490              :     case Transformation::SourceType::ResponseHeader:
     491              :     case Transformation::SourceType::ResponseStatusCode:
     492            0 :         return false;
     493              :     }
     494              : 
     495              : 
     496          193 :     return true;
     497           36 : }
     498              : 
     499           60 : bool AdminServerProvision::processFilters(std::shared_ptr<Transformation> transformation,
     500              :         TypeConverter& sourceVault,
     501              :         const std::map<std::string, std::string>& variables,
     502              :         std::smatch &matches,
     503              :         std::string &source) const
     504              : {
     505           60 :     bool success = false;
     506           60 :     std::string targetS;
     507           60 :     std::int64_t targetI = 0;
     508           60 :     std::uint64_t targetU = 0;
     509           60 :     double targetF = 0;
     510              : 
     511              :     // all the filters except Sum/Multiply/Strftime/RegexKey/Size, require a string target
     512           60 :     if (transformation->getFilterType() != Transformation::FilterType::Sum && transformation->getFilterType() != Transformation::FilterType::Multiply && transformation->getFilterType() != Transformation::FilterType::FStrftime && transformation->getFilterType() != Transformation::FilterType::RegexKey && transformation->getFilterType() != Transformation::FilterType::Size) {
     513           39 :         source = sourceVault.getString(success);
     514           39 :         if (!success) return false;
     515              :     }
     516              : 
     517              :     // All our regex are built with 'std::regex::optimize' so they are already validated and regex functions cannot throw exception:
     518              :     //try { // std::regex exceptions
     519           60 :     switch (transformation->getFilterType()) {
     520            3 :     case Transformation::FilterType::RegexCapture:
     521              :     {
     522            3 :         if (std::regex_match(source, matches, transformation->getFilterRegex()) && matches.size() >=1) {
     523            2 :             targetS = matches.str(0);
     524            2 :             sourceVault.setString(targetS);
     525            2 :             LOGDEBUG(
     526              :                 std::stringstream ss;
     527              :                 ss << "Regex matches: Size = " << matches.size();
     528              :             for(size_t i=0; i < matches.size(); i++) {
     529              :             ss << " | [" << i << "] = " << matches.str(i);
     530              :             }
     531              :             ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
     532              :             );
     533              :         }
     534              :         else {
     535            1 :             LOGDEBUG(
     536              :                 std::string msg = ert::tracing::Logger::asString("Unable to match '%s' againt regex capture '%s' in transformation item", source.c_str(), transformation->getFilter().c_str());
     537              :                 ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     538              :             );
     539            1 :             return false;
     540              :         }
     541            2 :         break;
     542              :     }
     543            1 :     case Transformation::FilterType::RegexReplace:
     544              :     {
     545            1 :         targetS = std::regex_replace (source, transformation->getFilterRegex(), transformation->getFilter() /* fmt */);
     546            1 :         sourceVault.setString(targetS);
     547            1 :         break;
     548              :     }
     549            1 :     case Transformation::FilterType::Append:
     550              :     {
     551            1 :         std::string filter = transformation->getFilter();
     552            1 :         replaceVariables(filter, transformation->getFilterPatterns(), variables, vault_);
     553              : 
     554            1 :         targetS = source + filter;
     555            1 :         sourceVault.setString(targetS);
     556            1 :         break;
     557            1 :     }
     558            1 :     case Transformation::FilterType::Prepend:
     559              :     {
     560            1 :         std::string filter = transformation->getFilter();
     561            1 :         replaceVariables(filter, transformation->getFilterPatterns(), variables, vault_);
     562              : 
     563            1 :         targetS = filter + source;
     564            1 :         sourceVault.setString(targetS);
     565            1 :         break;
     566            1 :     }
     567            4 :     case Transformation::FilterType::Sum:
     568              :     {
     569            4 :         switch (transformation->getFilterNumberType()) {
     570            1 :         case 0: /* integer */
     571              :         {
     572            1 :             targetI = sourceVault.getInteger(success);
     573            1 :             if (success) targetI += transformation->getFilterI();
     574              :             //else return false; // should not happen (protected by schema)
     575            1 :             sourceVault.setInteger(targetI);
     576            1 :             break;
     577              :         }
     578            2 :         case 1: /* unsigned */
     579              :         {
     580            2 :             targetU = sourceVault.getUnsigned(success);
     581            2 :             if (success) targetU += transformation->getFilterU();
     582              :             //else return false; // should not happen (protected by schema)
     583            2 :             sourceVault.setUnsigned(targetU);
     584            2 :             break;
     585              :         }
     586            1 :         case 2: /* double */
     587              :         {
     588            1 :             targetF = sourceVault.getFloat(success);
     589            1 :             if (success) targetF += transformation->getFilterF();
     590              :             //else return false; // should not happen (protected by schema)
     591            1 :             sourceVault.setFloat(targetF);
     592            1 :             break;
     593              :         }
     594              :         }
     595            4 :         break;
     596              :     }
     597            3 :     case Transformation::FilterType::Multiply:
     598              :     {
     599            3 :         switch (transformation->getFilterNumberType()) {
     600            1 :         case 0: /* integer */
     601              :         {
     602            1 :             targetI = sourceVault.getInteger(success);
     603            1 :             if (success) targetI *= transformation->getFilterI();
     604              :             //else return false; // should not happen (protected by schema)
     605            1 :             sourceVault.setInteger(targetI);
     606            1 :             break;
     607              :         }
     608            1 :         case 1: /* unsigned */
     609              :         {
     610            1 :             targetU = sourceVault.getUnsigned(success);
     611            1 :             if (success) targetU *= transformation->getFilterU();
     612              :             //else return false; // should not happen (protected by schema)
     613            1 :             sourceVault.setUnsigned(targetU);
     614            1 :             break;
     615              :         }
     616            1 :         case 2: /* double */
     617              :         {
     618            1 :             targetF = sourceVault.getFloat(success);
     619            1 :             if (success) targetF *= transformation->getFilterF();
     620              :             //else return false; // should not happen (protected by schema)
     621            1 :             sourceVault.setFloat(targetF);
     622            1 :             break;
     623              :         }
     624              :         }
     625            3 :         break;
     626              :     }
     627            6 :     case Transformation::FilterType::ConditionVar: // TODO: if condition is false, source storage could be omitted to improve performance
     628              :     {
     629              :         // Get variable value for the variable name 'transformation->getFilter()':
     630            6 :         std::string varname = transformation->getFilter();
     631            6 :         bool reverse = (transformation->getFilter()[0] == '!');
     632            6 :         if (reverse) {
     633            2 :             varname.erase(0,1);
     634              :         }
     635            6 :         auto iter = variables.find(varname);
     636            6 :         bool varFound = (iter != variables.end());
     637            6 :         std::string varvalue{};
     638            6 :         if (varFound) {
     639            1 :             varvalue = iter->second;
     640            1 :             LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Variable '%s' found (local)", varname.c_str()), ERT_FILE_LOCATION));
     641              :         }
     642              :         else {
     643            5 :             nlohmann::json gvarvalue{};
     644            5 :             varFound = vault_->tryGet(varname, gvarvalue);
     645            5 :             if (varFound) varvalue = jsonToString(gvarvalue);
     646            5 :             LOGDEBUG(if (varFound) ert::tracing::Logger::debug(ert::tracing::Logger::asString("Variable '%s' found (vault)", varname.c_str()), ERT_FILE_LOCATION));
     647            5 :         }
     648              : 
     649            6 :         bool conditionVar = (varFound && !(varvalue.empty()));
     650            6 :         LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Variable value: '%s'", (varFound ? varvalue.c_str():"<undefined>")), ERT_FILE_LOCATION));
     651              : 
     652            6 :         if ((reverse && !conditionVar)||(!reverse && conditionVar)) {
     653            4 :             LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("%sConditionVar is true", (reverse ? "!":"")), ERT_FILE_LOCATION));
     654            4 :             sourceVault.setString(source);
     655              :         }
     656              :         else {
     657            2 :             LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("%sConditionVar is false", (reverse ? "!":"")), ERT_FILE_LOCATION));
     658            2 :             return false;
     659              :         }
     660            4 :         break;
     661           12 :     }
     662            5 :     case Transformation::FilterType::EqualTo:
     663              :     {
     664            5 :         std::string filter = transformation->getFilter();
     665            5 :         replaceVariables(filter, transformation->getFilterPatterns(), variables, vault_);
     666              : 
     667              :         // Get value for the comparison 'transformation->getFilter()':
     668            5 :         if (source == filter) {
     669            1 :             LOGDEBUG(ert::tracing::Logger::debug("EqualTo is true", ERT_FILE_LOCATION));
     670            1 :             sourceVault.setString(source);
     671              :         }
     672              :         else {
     673            4 :             LOGDEBUG(ert::tracing::Logger::debug("EqualTo is false", ERT_FILE_LOCATION));
     674            4 :             return false;
     675              :         }
     676            1 :         break;
     677            5 :     }
     678            2 :     case Transformation::FilterType::DifferentFrom:
     679              :     {
     680            2 :         std::string filter = transformation->getFilter();
     681            2 :         replaceVariables(filter, transformation->getFilterPatterns(), variables, vault_);
     682              : 
     683              :         // Get value for the comparison 'transformation->getFilter()':
     684            2 :         if (source != filter) {
     685            1 :             LOGDEBUG(ert::tracing::Logger::debug("DifferentFrom is true", ERT_FILE_LOCATION));
     686            1 :             sourceVault.setString(source);
     687              :         }
     688              :         else {
     689            1 :             LOGDEBUG(ert::tracing::Logger::debug("DifferentFrom is false", ERT_FILE_LOCATION));
     690            1 :             return false;
     691              :         }
     692            1 :         break;
     693            2 :     }
     694            3 :     case Transformation::FilterType::JsonConstraint:
     695              :     {
     696            3 :         nlohmann::json sobj = sourceVault.getObject(success);
     697              :         // should not happen (protected by schema)
     698              :         //if (!success) {
     699              :         //    LOGDEBUG(ert::tracing::Logger::debug("Source provided for JsonConstraint filter must be a valid json object", ERT_FILE_LOCATION));
     700              :         //    return false;
     701              :         //}
     702            3 :         std::string failReport;
     703            3 :         if (h2agent::model::jsonConstraint(sobj, transformation->getFilterObject(), failReport)) {
     704            2 :             sourceVault.setString("1");
     705              :         }
     706              :         else {
     707            2 :             sourceVault.setString(failReport);
     708              :         }
     709            3 :         break;
     710            3 :     }
     711            2 :     case Transformation::FilterType::SchemaId:
     712              :     {
     713            2 :         nlohmann::json sobj = sourceVault.getObject(success);
     714              :         // should not happen (protected by schema)
     715              :         //if (!success) {
     716              :         //    LOGDEBUG(ert::tracing::Logger::debug("Source provided for SchemaId filter must be a valid json object", ERT_FILE_LOCATION));
     717              :         //    return false;
     718              :         //}
     719            2 :         std::string failReport;
     720            2 :         auto schema = admin_data_->getSchemaData().find(transformation->getFilter()); // TODO: find a way to cache this (set the schema into transformation: but clean schemas should be detected to avoid corruption)
     721            2 :         if (schema) {
     722            2 :             if (schema->validate(sobj, failReport)) {
     723            2 :                 sourceVault.setString("1");
     724              :             }
     725              :             else {
     726            1 :                 sourceVault.setString(failReport);
     727              :             }
     728              :         }
     729              :         else {
     730            0 :             ert::tracing::Logger::warning(ert::tracing::Logger::asString("Missing schema '%s' referenced in transformation item: VALIDATION will be IGNORED", transformation->getFilter().c_str()), ERT_FILE_LOCATION);
     731              :         }
     732            2 :         break;
     733            2 :     }
     734            5 :     case Transformation::FilterType::Split:
     735              :     {
     736            5 :         std::int64_t size = transformation->getFilterI();
     737            5 :         std::uint64_t count = transformation->getFilterU();
     738            5 :         const std::string &sep = transformation->getFilter();
     739            5 :         const std::string &filler = transformation->getFilterFiller();
     740            5 :         bool numeric = (transformation->getFilterNumberType() != 0);
     741              : 
     742            5 :         std::uint64_t totalLen = static_cast<std::uint64_t>(size) * count;
     743            5 :         std::string padded = source;
     744              : 
     745              :         // Truncate (left) or pad (left) to reach totalLen
     746            5 :         if (padded.size() > totalLen) {
     747            1 :             padded = padded.substr(padded.size() - totalLen);
     748              :         }
     749            4 :         else if (padded.size() < totalLen && !filler.empty()) {
     750            1 :             std::string padding;
     751            6 :             while (padding.size() + padded.size() < totalLen) {
     752            5 :                 padding += filler;
     753              :             }
     754            1 :             if (padding.size() + padded.size() > totalLen) {
     755            0 :                 padding = padding.substr(padding.size() + padded.size() - totalLen);
     756              :             }
     757            1 :             padded = padding + padded;
     758            1 :         }
     759              : 
     760              :         // Split into groups and join
     761            5 :         targetS.clear();
     762           24 :         for (std::uint64_t i = 0; i < count; i++) {
     763           19 :             if (i > 0) targetS += sep;
     764           19 :             std::string group = padded.substr(i * size, size);
     765           19 :             if (numeric) {
     766              :                 // Strip leading zeros by converting to unsigned long
     767              :                 try {
     768           12 :                     targetS += std::to_string(std::stoull(group));
     769              :                 }
     770            0 :                 catch (...) {
     771            0 :                     targetS += group;
     772            0 :                 }
     773              :             }
     774              :             else {
     775            7 :                 targetS += group;
     776              :             }
     777           19 :         }
     778            5 :         sourceVault.setString(targetS);
     779            5 :         break;
     780            5 :     }
     781            6 :     case Transformation::FilterType::BaseConvert:
     782              :     {
     783            6 :         int baseIn = static_cast<int>(transformation->getFilterI());
     784            6 :         int baseOut = static_cast<int>(transformation->getFilterU());
     785            6 :         bool capital = (transformation->getFilterNumberType() != 0);
     786              : 
     787              :         try {
     788            6 :             unsigned long long val = std::stoull(source, nullptr, baseIn);
     789            5 :             if (baseOut == 10) {
     790            1 :                 targetS = std::to_string(val);
     791              :             }
     792              :             else {
     793            4 :                 targetS.clear();
     794            4 :                 if (val == 0) { targetS = "0"; }
     795              :                 else {
     796           11 :                     while (val > 0) {
     797            8 :                         int d = val % baseOut;
     798            8 :                         targetS += (d < 10) ? char('0' + d) : char((capital ? 'A' : 'a') + d - 10);
     799            8 :                         val /= baseOut;
     800              :                     }
     801            3 :                     std::reverse(targetS.begin(), targetS.end());
     802              :                 }
     803              :             }
     804              :         }
     805            1 :         catch (...) {
     806            1 :             targetS = source;
     807            1 :         }
     808            6 :         sourceVault.setString(targetS);
     809            6 :         break;
     810              :     }
     811            4 :     case Transformation::FilterType::FStrptime:
     812              :     {
     813              :         // Parse date string → epoch number
     814            4 :         struct tm tm{};
     815            4 :         if (strptime(source.c_str(), transformation->getFilter().c_str(), &tm) == nullptr) {
     816            1 :             ert::tracing::Logger::error(ert::tracing::Logger::asString("Strptime filter failed to parse '%s' with format '%s'", source.c_str(), transformation->getFilter().c_str()), ERT_FILE_LOCATION);
     817            1 :             return false;
     818              :         }
     819            3 :         std::int64_t epoch = static_cast<std::int64_t>(timegm(&tm));
     820            3 :         switch (transformation->getFilterNumberType()) {
     821            1 :         case 1: epoch *= 1000; break;       // ms
     822            0 :         case 2: epoch *= 1000000; break;    // us
     823            0 :         case 3: epoch *= 1000000000; break; // ns
     824              :         }
     825            3 :         sourceVault.setInteger(epoch);
     826            3 :         break;
     827              :     }
     828            4 :     case Transformation::FilterType::FStrftime:
     829              :     {
     830              :         // Format epoch number → date string
     831            4 :         std::int64_t epoch = sourceVault.getInteger(success);
     832            4 :         if (!success) {
     833            1 :             ert::tracing::Logger::error("Strftime filter requires a numeric source (epoch)", ERT_FILE_LOCATION);
     834            1 :             return false;
     835              :         }
     836            3 :         switch (transformation->getFilterNumberType()) {
     837            1 :         case 1: epoch /= 1000; break;       // ms
     838            0 :         case 2: epoch /= 1000000; break;    // us
     839            0 :         case 3: epoch /= 1000000000; break; // ns
     840              :         }
     841            3 :         time_t t = static_cast<time_t>(epoch);
     842            3 :         struct tm tm{};
     843            3 :         gmtime_r(&t, &tm);
     844              :         char buf[256];
     845            3 :         if (std::strftime(buf, sizeof(buf), transformation->getFilter().c_str(), &tm) == 0) {
     846            0 :             ert::tracing::Logger::error(ert::tracing::Logger::asString("Strftime filter failed to format epoch %ld with format '%s'", (long)t, transformation->getFilter().c_str()), ERT_FILE_LOCATION);
     847            0 :             return false;
     848              :         }
     849            3 :         sourceVault.setString(std::string(buf));
     850            3 :         break;
     851              :     }
     852            7 :     case Transformation::FilterType::RegexKey:
     853              :     {
     854            7 :         bool objSuccess = false;
     855            7 :         nlohmann::json obj = sourceVault.getObject(objSuccess); // copy: setObject below clears sourceVault
     856            7 :         if (!objSuccess || !obj.is_object()) {
     857            1 :             ert::tracing::Logger::error("RegexKey filter requires a JSON object source", ERT_FILE_LOCATION);
     858            1 :             return false;
     859              :         }
     860           12 :         for (auto it = obj.begin(); it != obj.end(); ++it) {
     861           11 :             source = it.key(); // copy key to source (lives in transform() scope for matches lifetime)
     862           11 :             if (std::regex_match(source, matches, transformation->getFilterRegex())) {
     863           10 :                 if (!sourceVault.setObject(it.value(), ""))
     864            5 :                     return false;
     865            5 :                 LOGDEBUG(
     866              :                     std::stringstream ss;
     867              :                     ss << "RegexKey filter: matched key '" << source << "'";
     868              :                     for(size_t i=1; i < matches.size(); i++) {
     869              :                         ss << " | group[" << i << "] = " << matches.str(i);
     870              :                     }
     871              :                     ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
     872              :                 );
     873            5 :                 return true;
     874              :             }
     875              :         }
     876            1 :         LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("RegexKey filter: no key matched '%s'", transformation->getFilter().c_str()), ERT_FILE_LOCATION));
     877            1 :         return false;
     878            7 :     }
     879            3 :     case Transformation::FilterType::Size:
     880              :     {
     881            3 :         bool objSuccess = false;
     882            3 :         nlohmann::json obj = sourceVault.getObject(objSuccess);
     883            3 :         if (objSuccess && (obj.is_object() || obj.is_array())) {
     884            2 :             targetS = std::to_string(obj.size());
     885              :         }
     886              :         else {
     887            1 :             source = sourceVault.getString(objSuccess);
     888            1 :             targetS = objSuccess ? std::to_string(source.size()) : "0";
     889              :         }
     890            3 :         sourceVault.setString(targetS);
     891            3 :         break;
     892            3 :     }
     893              :     }
     894              :     //}
     895              :     //catch (std::exception& e)
     896              :     //{
     897              :     //    ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
     898              :     //}
     899              : 
     900              : 
     901           43 :     return true;
     902           60 : }
     903              : 
     904          181 : bool AdminServerProvision::processTargets(std::shared_ptr<Transformation> transformation,
     905              :         TypeConverter &sourceVault,
     906              :         std::map<std::string, std::string>& variables,
     907              :         const std::smatch &matches,
     908              :         bool eraser,
     909              :         bool hasFilter,
     910              :         unsigned int &responseStatusCode,
     911              :         nlohmann::json &responseBodyJson,
     912              :         std::string &responseBodyAsString,
     913              :         nghttp2::asio_http2::header_map &responseHeaders,
     914              :         unsigned int &responseDelayMs,
     915              :         std::string &outState,
     916              :         std::string &outStateMethod,
     917              :         std::string &outStateUri,
     918              :         std::vector<std::pair<std::string, std::string>> &clientProvisionTriggers,
     919              :         bool &breakCondition) const
     920              : {
     921          181 :     bool success = false;
     922          181 :     std::string targetS;
     923          181 :     std::int64_t targetI = 0;
     924          181 :     std::uint64_t targetU = 0;
     925          181 :     double targetF = 0;
     926          181 :     bool boolean = false;
     927          181 :     nlohmann::json obj;
     928              : 
     929              : 
     930              :     try { // nlohmann::json exceptions
     931              : 
     932          181 :         std::string target = transformation->getTarget();
     933          181 :         std::string target2 = transformation->getTarget2(); // foreign outState URI
     934              : 
     935          181 :         replaceVariables(target, transformation->getTargetPatterns(), variables, vault_);
     936          181 :         if (!target2.empty()) {
     937            8 :             replaceVariables(target2, transformation->getTarget2Patterns(), variables, vault_);
     938              :         }
     939              : 
     940          181 :         switch (transformation->getTargetType()) {
     941           65 :         case Transformation::TargetType::ResponseBodyString:
     942              :         {
     943              :             // extraction
     944           65 :             targetS = sourceVault.getString(success);
     945           65 :             if (!success) return false;
     946              :             // assignment
     947           65 :             responseBodyAsString = targetS;
     948           65 :             break;
     949              :         }
     950            1 :         case Transformation::TargetType::ResponseBodyHexString:
     951              :         {
     952              :             // extraction
     953            1 :             targetS = sourceVault.getString(success);
     954            1 :             if (!success) return false;
     955              :             // assignment
     956            1 :             if (!h2agent::model::fromHexString(targetS, responseBodyAsString)) return false;
     957            1 :             break;
     958              :         }
     959           27 :         case Transformation::TargetType::ResponseBodyJson_String:
     960              :         {
     961              :             // extraction
     962           27 :             targetS = sourceVault.getString(success);
     963           27 :             if (!success) return false;
     964              :             // assignment
     965           27 :             nlohmann::json::json_pointer j_ptr(target);
     966           26 :             responseBodyJson[j_ptr] = targetS;
     967           26 :             break;
     968           26 :         }
     969            6 :         case Transformation::TargetType::ResponseBodyJson_Integer:
     970              :         {
     971              :             // extraction
     972            6 :             targetI = sourceVault.getInteger(success);
     973            6 :             if (!success) return false;
     974              :             // assignment
     975            6 :             nlohmann::json::json_pointer j_ptr(target);
     976            6 :             responseBodyJson[j_ptr] = targetI;
     977            6 :             break;
     978            6 :         }
     979            5 :         case Transformation::TargetType::ResponseBodyJson_Unsigned:
     980              :         {
     981              :             // extraction
     982            5 :             targetU = sourceVault.getUnsigned(success);
     983            5 :             if (!success) return false;
     984              :             // assignment
     985            5 :             nlohmann::json::json_pointer j_ptr(target);
     986            5 :             responseBodyJson[j_ptr] = targetU;
     987            5 :             break;
     988            5 :         }
     989            2 :         case Transformation::TargetType::ResponseBodyJson_Float:
     990              :         {
     991              :             // extraction
     992            2 :             targetF = sourceVault.getFloat(success);
     993            2 :             if (!success) return false;
     994              :             // assignment
     995            2 :             nlohmann::json::json_pointer j_ptr(target);
     996            2 :             responseBodyJson[j_ptr] = targetF;
     997            2 :             break;
     998            2 :         }
     999            2 :         case Transformation::TargetType::ResponseBodyJson_Boolean:
    1000              :         {
    1001              :             // extraction
    1002            2 :             boolean = sourceVault.getBoolean(success);
    1003            2 :             if (!success) return false;
    1004              :             // assignment
    1005            2 :             nlohmann::json::json_pointer j_ptr(target);
    1006            2 :             responseBodyJson[j_ptr] = boolean;
    1007            2 :             break;
    1008            2 :         }
    1009           18 :         case Transformation::TargetType::ResponseBodyJson_Object:
    1010              :         {
    1011              : 
    1012           18 :             if (eraser) {
    1013            2 :                 LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into json path '%s'", target.c_str()), ERT_FILE_LOCATION));
    1014            2 :                 if (target.empty()) {
    1015            1 :                     responseBodyJson.erase(responseBodyJson.begin(), responseBodyJson.end());
    1016            1 :                     return false;
    1017              :                 }
    1018              : 
    1019              :                 //erase() DOES NOT SUPPORT JSON POINTERS:
    1020              :                 //nlohmann::json::json_pointer j_ptr(target);
    1021              :                 //responseBodyJson.erase(j_ptr);
    1022              :                 //
    1023              :                 // For a path '/a/b/c' we must access to /a/b and then erase "c":
    1024            1 :                 size_t lastSlashPos = target.find_last_of("/");
    1025              :                 // lastSlashPos will never be std::string::npos here
    1026            1 :                 std::string parentPath = target.substr(0, lastSlashPos);
    1027            1 :                 std::string childKey = "";
    1028            1 :                 if (lastSlashPos + 1 < target.size()) childKey = target.substr(lastSlashPos + 1, target.size());
    1029            1 :                 nlohmann::json::json_pointer j_ptr(parentPath);
    1030            1 :                 responseBodyJson[j_ptr].erase(childKey);
    1031            1 :                 return false;
    1032            1 :             }
    1033              : 
    1034              :             // extraction will be object if possible, falling back to the rest of formats with this priority: string, integer, unsigned, float, boolean
    1035              :             // assignment for valid extraction
    1036           16 :             nlohmann::json::json_pointer j_ptr(target);
    1037              : 
    1038              :             // Native types for SOURCES:
    1039              :             //
    1040              :             // [string] request.uri, request.uri.path, request.header, randomset, strftime, var, vault, value, txtFile, binFile, command
    1041              :             // [object] request.body, response.body, serverEvent  (when target is also object, it could be promoted to string, unsigned, integer, float or boolean).
    1042              :             // [integer] random, timestamp
    1043              :             // [unsigned] recvseq
    1044              :             // [float] math.*
    1045              :             // [boolean] NONE
    1046              :             // So, depending on the target, the corresponding getter (getInteger, getString, etc.) will be used: WE DO NOT WANT FORCE CONVERSIONS:
    1047              :             // Note that there is not sources with boolean as native type, so boolean getter is never reached (so commented to avoid UT coverage fault).
    1048              :             //
    1049           16 :             switch (sourceVault.getNativeType()) {
    1050            6 :             case  TypeConverter::NativeType::Object:
    1051            6 :                 obj = sourceVault.getObject(success);
    1052            6 :                 if (success) {
    1053            6 :                     if (target.empty()) {
    1054            1 :                         responseBodyJson.merge_patch(obj); // merge origin by default for target response.body.json.object
    1055              :                     }
    1056              :                     else {
    1057            5 :                         responseBodyJson[j_ptr] = obj;
    1058              :                     }
    1059              :                 }
    1060            6 :                 break;
    1061              : 
    1062            2 :             case  TypeConverter::NativeType::String:
    1063            2 :                 targetS = sourceVault.getString(success);
    1064            2 :                 if (success) responseBodyJson[j_ptr] = targetS;
    1065            2 :                 break;
    1066              : 
    1067            5 :             case  TypeConverter::NativeType::Integer:
    1068            5 :                 targetI = sourceVault.getInteger(success);
    1069            5 :                 if (success) responseBodyJson[j_ptr] = targetI;
    1070            5 :                 break;
    1071              : 
    1072            1 :             case  TypeConverter::NativeType::Unsigned:
    1073            1 :                 targetU = sourceVault.getUnsigned(success);
    1074            1 :                 if (success) responseBodyJson[j_ptr] = targetU;
    1075            1 :                 break;
    1076              : 
    1077            1 :             case  TypeConverter::NativeType::Float:
    1078            1 :                 targetF = sourceVault.getFloat(success);
    1079            1 :                 if (success) responseBodyJson[j_ptr] = targetF;
    1080            1 :                 break;
    1081              : 
    1082              :             // Not reached at the moment:
    1083            1 :             case  TypeConverter::NativeType::Boolean:
    1084            1 :                 boolean = sourceVault.getBoolean(success);
    1085            1 :                 if (success) responseBodyJson[j_ptr] = boolean;
    1086            1 :                 break;
    1087              :             }
    1088           16 :             break;
    1089           16 :         }
    1090            3 :         case Transformation::TargetType::ResponseBodyJson_JsonString:
    1091              :         {
    1092              : 
    1093              :             // assignment for valid extraction
    1094            3 :             nlohmann::json::json_pointer j_ptr(target);
    1095              : 
    1096              :             // extraction
    1097            3 :             targetS = sourceVault.getString(success);
    1098            3 :             if (!success) return false;
    1099            3 :             if (!h2agent::model::parseJsonContent(targetS, obj))
    1100            1 :                 return false;
    1101              : 
    1102              :             // assignment
    1103            2 :             if (target.empty()) {
    1104            1 :                 responseBodyJson.merge_patch(obj); // merge origin by default for target response.body.json.object
    1105              :             }
    1106              :             else {
    1107            1 :                 responseBodyJson[j_ptr] = obj;
    1108              :             }
    1109            2 :             break;
    1110            3 :         }
    1111            1 :         case Transformation::TargetType::ResponseHeader_t:
    1112              :         {
    1113              :             // extraction
    1114            1 :             targetS = sourceVault.getString(success);
    1115            1 :             if (!success) return false;
    1116              :             // assignment
    1117            1 :             responseHeaders.emplace(target, nghttp2::asio_http2::header_value{targetS});
    1118            1 :             break;
    1119              :         }
    1120            5 :         case Transformation::TargetType::ResponseStatusCode_t:
    1121              :         {
    1122              :             // extraction
    1123            5 :             targetU = sourceVault.getUnsigned(success);
    1124            5 :             if (!success) return false;
    1125              :             // assignment
    1126            5 :             responseStatusCode = targetU;
    1127            5 :             break;
    1128              :         }
    1129            1 :         case Transformation::TargetType::ResponseDelayMs:
    1130              :         {
    1131              :             // extraction
    1132            1 :             targetU = sourceVault.getUnsigned(success);
    1133            1 :             if (!success) return false;
    1134              :             // assignment
    1135            1 :             responseDelayMs = targetU;
    1136            1 :             break;
    1137              :         }
    1138           13 :         case Transformation::TargetType::TVar:
    1139              :         {
    1140           13 :             if (hasFilter && transformation->getFilterType() == Transformation::FilterType::RegexCapture) {
    1141            1 :                 std::string varname;
    1142            1 :                 if (matches.size() >=1) { // this protection shouldn't be needed as it would be continued above on RegexCapture matching...
    1143            1 :                     variables[target] = matches.str(0); // variable "as is" stores the entire match (backward compatible)
    1144            4 :                     for(size_t i=1; i < matches.size(); i++) {
    1145            3 :                         varname = target;
    1146            3 :                         varname += ".";
    1147            3 :                         varname += std::to_string(i);
    1148            3 :                         variables[varname] = matches.str(i);
    1149            3 :                         LOGDEBUG(
    1150              :                             std::stringstream ss;
    1151              :                             ss << "Variable '" << varname << "' takes value '" << matches.str(i) << "'";
    1152              :                             ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
    1153              :                         );
    1154              :                     }
    1155              :                 }
    1156            1 :             }
    1157           12 :             else if (hasFilter && transformation->getFilterType() == Transformation::FilterType::RegexKey) {
    1158              :                 // Store the value in the target variable
    1159            1 :                 targetS = sourceVault.getString(success);
    1160            1 :                 if (!success) return false;
    1161            1 :                 variables[target] = targetS;
    1162              :                 // Store matched key (.0) and capture groups (.1, .2, ...) in variables
    1163            4 :                 for(size_t i=0; i < matches.size(); i++) {
    1164            3 :                     std::string varname = target + "." + std::to_string(i);
    1165            3 :                     variables[varname] = matches.str(i);
    1166            3 :                     LOGDEBUG(
    1167              :                         std::stringstream ss;
    1168              :                         ss << "Variable '" << varname << "' takes value '" << matches.str(i) << "'";
    1169              :                         ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
    1170              :                     );
    1171            3 :                 }
    1172              :             }
    1173              :             else {
    1174              :                 // extraction
    1175           11 :                 targetS = sourceVault.getString(success);
    1176           11 :                 if (!success) return false;
    1177              : 
    1178           11 :                 if (hasFilter) {
    1179            6 :                     if(transformation->getFilterType() == Transformation::FilterType::JsonConstraint) {
    1180            1 :                         if (targetS != "1") { // this is a fail report
    1181            1 :                             variables[target + ".fail"] = targetS;
    1182            1 :                             targetS = "";
    1183              :                         }
    1184              :                     }
    1185            5 :                     else if (transformation->getFilterType() == Transformation::FilterType::SchemaId) {
    1186            0 :                         if (targetS != "1") { // this is a fail report
    1187            0 :                             variables[target + ".fail"] = targetS;
    1188            0 :                             targetS = "";
    1189              :                         }
    1190              :                     }
    1191              :                 }
    1192              : 
    1193              :                 // assignment
    1194           11 :                 variables[target] = targetS;
    1195              :             }
    1196           13 :             break;
    1197              :         }
    1198           10 :         case Transformation::TargetType::TGVar:
    1199              :         {
    1200           10 :             std::string gvarPath = transformation->getTarget2();
    1201           10 :             if (!gvarPath.empty()) replaceVariables(gvarPath, transformation->getTarget2Patterns(), variables, vault_);
    1202              : 
    1203           10 :             if (eraser) {
    1204              :                 bool exists;
    1205            1 :                 vault_->remove(target, exists);
    1206            1 :                 LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into vault entry '%s' (%s)", target.c_str(), exists ? "removed":"missing"), ERT_FILE_LOCATION));
    1207              :             }
    1208            9 :             else if (hasFilter && transformation->getFilterType() == Transformation::FilterType::RegexCapture) {
    1209            1 :                 if (matches.size() >=1) {
    1210              :                     // Store regex captures as a JSON object: {"0": "full", "1": "group1", ...}
    1211            1 :                     nlohmann::json captureObj = nlohmann::json::object();
    1212            1 :                     captureObj["0"] = matches.str(0);
    1213            4 :                     for(size_t i=1; i < matches.size(); i++) {
    1214            3 :                         captureObj[std::to_string(i)] = matches.str(i);
    1215              :                     }
    1216            1 :                     if (gvarPath.empty()) {
    1217            1 :                         vault_->load(target, std::move(captureObj));
    1218              :                     } else {
    1219            0 :                         vault_->loadAtPath(target, gvarPath, captureObj);
    1220              :                     }
    1221            1 :                 }
    1222              :             }
    1223            8 :             else if (hasFilter && transformation->getFilterType() == Transformation::FilterType::RegexKey) {
    1224              :                 // Store value in vault (as today)
    1225            3 :                 bool objSuccess = false;
    1226            3 :                 const nlohmann::json &obj = sourceVault.getObject(objSuccess);
    1227            3 :                 if (objSuccess) {
    1228            2 :                     if (gvarPath.empty()) {
    1229            2 :                         vault_->load(target, obj);
    1230              :                     } else {
    1231            0 :                         vault_->loadAtPath(target, gvarPath, obj);
    1232              :                     }
    1233              :                 } else {
    1234            1 :                     targetS = sourceVault.getString(success);
    1235            1 :                     if (!success) return false;
    1236            1 :                     nlohmann::json val(targetS);
    1237            1 :                     if (gvarPath.empty()) {
    1238            1 :                         vault_->load(target, std::move(val));
    1239              :                     } else {
    1240            0 :                         vault_->loadAtPath(target, gvarPath, val);
    1241              :                     }
    1242            1 :                 }
    1243              :                 // Store matched key (.0) and capture groups (.1, .2, ...) in variables
    1244            8 :                 for(size_t i=0; i < matches.size(); i++) {
    1245            5 :                     std::string varname = target + "." + std::to_string(i);
    1246            5 :                     variables[varname] = matches.str(i);
    1247            5 :                     LOGDEBUG(
    1248              :                         std::stringstream ss;
    1249              :                         ss << "Variable '" << varname << "' takes value '" << matches.str(i) << "'";
    1250              :                         ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
    1251              :                     );
    1252            5 :                 }
    1253              :             }
    1254              :             else {
    1255              :                 // Try to extract as json object first, fall back to string
    1256            5 :                 bool objSuccess = false;
    1257            5 :                 const nlohmann::json &obj = sourceVault.getObject(objSuccess);
    1258            5 :                 if (objSuccess) {
    1259            1 :                     if (gvarPath.empty()) {
    1260            1 :                         vault_->load(target, obj);
    1261              :                     } else {
    1262            0 :                         vault_->loadAtPath(target, gvarPath, obj);
    1263              :                     }
    1264              :                 } else {
    1265            4 :                     targetS = sourceVault.getString(success);
    1266            4 :                     if (!success) return false;
    1267            4 :                     nlohmann::json val(targetS);
    1268            4 :                     if (gvarPath.empty()) {
    1269            2 :                         vault_->load(target, std::move(val));
    1270              :                     } else {
    1271            2 :                         vault_->loadAtPath(target, gvarPath, val);
    1272              :                     }
    1273            4 :                 }
    1274              :             }
    1275           10 :             break;
    1276           10 :         }
    1277            3 :         case Transformation::TargetType::TGVarJson_Object:
    1278              :         {
    1279            3 :             std::string gvarPath = transformation->getTarget2();
    1280            3 :             if (!gvarPath.empty()) replaceVariables(gvarPath, transformation->getTarget2Patterns(), variables, vault_);
    1281              : 
    1282            3 :             bool objSuccess = false;
    1283            3 :             const nlohmann::json &obj = sourceVault.getObject(objSuccess);
    1284            3 :             if (!objSuccess) return false;
    1285              : 
    1286            2 :             if (gvarPath.empty()) {
    1287            1 :                 vault_->load(target, obj);
    1288              :             } else {
    1289            1 :                 vault_->loadAtPath(target, gvarPath, obj);
    1290              :             }
    1291            2 :             break;
    1292            3 :         }
    1293            4 :         case Transformation::TargetType::TGVarJson_JsonString:
    1294              :         {
    1295            4 :             std::string gvarPath = transformation->getTarget2();
    1296            4 :             if (!gvarPath.empty()) replaceVariables(gvarPath, transformation->getTarget2Patterns(), variables, vault_);
    1297              : 
    1298            4 :             targetS = sourceVault.getString(success);
    1299            4 :             if (!success) return false;
    1300            4 :             if (!h2agent::model::parseJsonContent(targetS, obj))
    1301            1 :                 return false;
    1302              : 
    1303            3 :             if (gvarPath.empty()) {
    1304            2 :                 vault_->load(target, obj);
    1305              :             } else {
    1306            1 :                 vault_->loadAtPath(target, gvarPath, obj);
    1307              :             }
    1308            3 :             break;
    1309            4 :         }
    1310            2 :         case Transformation::TargetType::OutState:
    1311              :         {
    1312              :             // extraction
    1313            2 :             targetS = sourceVault.getString(success);
    1314            2 :             if (!success) return false;
    1315              :             // assignments
    1316            2 :             outState = targetS;
    1317            2 :             outStateMethod = target; // empty on regular usage
    1318            2 :             outStateUri = target2; // empty on regular usage
    1319            2 :             break;
    1320              :         }
    1321            2 :         case Transformation::TargetType::TTxtFile:
    1322              :         {
    1323              :             // extraction
    1324            2 :             targetS = sourceVault.getString(success);
    1325            2 :             if (!success) return false;
    1326              : 
    1327            2 :             if (eraser) {
    1328            1 :                 LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into text file '%s'", target.c_str()), ERT_FILE_LOCATION));
    1329            1 :                 file_manager_->empty(target/*path*/);
    1330              :             }
    1331              :             else {
    1332              :                 // assignments
    1333            1 :                 bool longTerm =(transformation->getTargetPatterns().empty()); // path is considered fixed (long term files), instead of arbitrary and dynamic (short term files)
    1334              :                 // even if @{varname} is missing (empty value) we consider the intention to allow force short term
    1335              :                 // files type.
    1336            1 :                 file_manager_->write(target/*path*/, targetS/*data*/, true/*text*/, (longTerm ? configuration_->getLongTermFilesCloseDelayUsecs():configuration_->getShortTermFilesCloseDelayUsecs()));
    1337              :             }
    1338            2 :             break;
    1339              :         }
    1340            2 :         case Transformation::TargetType::TBinFile:
    1341              :         {
    1342              :             // extraction
    1343            2 :             targetS = sourceVault.getString(success);
    1344            2 :             if (!success) return false;
    1345              : 
    1346            2 :             if (eraser) {
    1347            1 :                 LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Eraser source into binary file '%s'", target.c_str()), ERT_FILE_LOCATION));
    1348            1 :                 file_manager_->empty(target/*path*/);
    1349              :             }
    1350              :             else {
    1351              :                 // assignments
    1352            1 :                 bool longTerm =(transformation->getTargetPatterns().empty()); // path is considered fixed (long term files), instead of arbitrary and dynamic (short term files)
    1353              :                 // even if @{varname} is missing (empty value) we consider the intention to allow force short term
    1354              :                 // files type.
    1355            1 :                 file_manager_->write(target/*path*/, targetS/*data*/, false/*binary*/, (longTerm ? configuration_->getLongTermFilesCloseDelayUsecs():configuration_->getShortTermFilesCloseDelayUsecs()));
    1356              :             }
    1357            2 :             break;
    1358              :         }
    1359            1 :         case Transformation::TargetType::UDPSocket:
    1360              :         {
    1361              :             // extraction
    1362            1 :             targetS = sourceVault.getString(success);
    1363            1 :             if (!success) return false;
    1364              : 
    1365              :             // assignments
    1366              :             // Possible delay provided in 'target': <path>|<delay>
    1367            1 :             std::string path = target;
    1368            1 :             size_t lastDotPos = target.find_last_of("|");
    1369            1 :             unsigned int delayMs = atoi(target.substr(lastDotPos + 1).c_str());
    1370            1 :             path = target.substr(0, lastDotPos);
    1371              : 
    1372            1 :             LOGDEBUG(
    1373              :                 std::string msg = ert::tracing::Logger::asString("UDPSocket '%s' target, delayed %u milliseconds, in transformation item", path.c_str(), delayMs);
    1374              :                 ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
    1375              :             );
    1376              : 
    1377            1 :             socket_manager_->write(path, targetS/*data*/, delayMs * 1000 /* usecs */);
    1378            1 :             break;
    1379            1 :         }
    1380            3 :         case Transformation::TargetType::ServerEventToPurge:
    1381              :         {
    1382            3 :             if (!eraser) {
    1383            1 :                 LOGDEBUG(ert::tracing::Logger::debug("'ServerEventToPurge' target type only works with 'eraser' source type. This transformation will be ignored.", ERT_FILE_LOCATION));
    1384            2 :                 return false;
    1385              :             }
    1386              :             // transformation->getTargetTokenized() is a vector:
    1387              :             //
    1388              :             // requestMethod: index 0
    1389              :             // requestUri:    index 1
    1390              :             // eventNumber:   index 2
    1391              :             // recvseq:       index 3
    1392            2 :             std::string event_method = transformation->getTargetTokenized()[0];
    1393            2 :             replaceVariables(event_method, transformation->getTargetPatterns(), variables, vault_);
    1394            2 :             std::string event_uri = transformation->getTargetTokenized()[1];
    1395            2 :             replaceVariables(event_uri, transformation->getTargetPatterns(), variables, vault_);
    1396            2 :             std::string event_number = transformation->getTargetTokenized()[2];
    1397            2 :             replaceVariables(event_number, transformation->getTargetPatterns(), variables, vault_);
    1398            2 :             std::string event_recvseq = transformation->getTargetTokenized()[3];
    1399            2 :             replaceVariables(event_recvseq, transformation->getTargetPatterns(), variables, vault_);
    1400              : 
    1401            2 :             bool serverDataDeleted = false;
    1402              : 
    1403            2 :             if (!event_recvseq.empty()) {
    1404              :                 // Stable addressing by receive sequence:
    1405            0 :                 DataKey dkey(event_method, event_uri);
    1406            0 :                 serverDataDeleted = mock_server_events_data_->removeEventByRecvSeq(dkey, (std::uint64_t)std::stoull(event_recvseq));
    1407            0 :             }
    1408              :             else {
    1409              :                 // Positional addressing by event number:
    1410            2 :                 EventKey ekey(event_method, event_uri, event_number);
    1411            2 :                 bool success = mock_server_events_data_->clear(serverDataDeleted, ekey);
    1412              : 
    1413            2 :                 if (!success) {
    1414            1 :                     LOGDEBUG(
    1415              :                         std::string msg = ert::tracing::Logger::asString("Unexpected error while removing server data event '%s' in transformation item", transformation->getTarget().c_str());
    1416              :                         ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
    1417              :                     );
    1418            1 :                     return false;
    1419              :                 }
    1420            2 :             }
    1421              : 
    1422            1 :             LOGDEBUG(
    1423              :                 std::string msg = ert::tracing::Logger::asString("Server event '%s' removal result: %s", transformation->getTarget().c_str(), (serverDataDeleted ? "SUCCESS":"NOTHING REMOVED"));
    1424              :                 ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
    1425              :             );
    1426            1 :             break;
    1427            8 :         }
    1428            3 :         case Transformation::TargetType::ClientProvision_t:
    1429              :         {
    1430            3 :             std::string clientProvisionId = transformation->getTarget();
    1431            3 :             replaceVariables(clientProvisionId, transformation->getTargetPatterns(), variables, vault_);
    1432              : 
    1433              :             // Source acts as conditional gate: non-empty = trigger, empty/eraser = skip
    1434            4 :             targetS = eraser ? "" : sourceVault.getString(success);
    1435            3 :             if (targetS.empty()) {
    1436            1 :                 LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString(
    1437              :                     "Client provision trigger skipped (empty source condition): id='%s'", clientProvisionId.c_str()), ERT_FILE_LOCATION));
    1438            1 :                 break;
    1439              :             }
    1440              : 
    1441            2 :             std::string inState = transformation->getTarget2();
    1442            2 :             replaceVariables(inState, transformation->getTarget2Patterns(), variables, vault_);
    1443            2 :             clientProvisionTriggers.emplace_back(clientProvisionId, inState);
    1444            2 :             LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString(
    1445              :                 "Scheduled client provision trigger: id='%s', inState='%s'", clientProvisionId.c_str(), inState.c_str()), ERT_FILE_LOCATION));
    1446            2 :             break;
    1447            3 :         }
    1448            2 :         case Transformation::TargetType::Break:
    1449              :         {
    1450              :             // extraction
    1451            2 :             targetS = sourceVault.getString(success);
    1452            2 :             if (!success) return false;
    1453              :             // assignments
    1454            2 :             if (targetS.empty()) {
    1455            1 :                 LOGDEBUG(ert::tracing::Logger::debug("Break action ignored (empty string as source provided)", ERT_FILE_LOCATION));
    1456            1 :                 return false;
    1457              :             }
    1458              : 
    1459            1 :             breakCondition = true;
    1460            1 :             LOGDEBUG(ert::tracing::Logger::debug("Break action triggered: ignoring remaining transformation items", ERT_FILE_LOCATION));
    1461            1 :             return false;
    1462              :             break;
    1463              :         }
    1464              :         // this won't happen due to schema for server target types:
    1465            0 :         case Transformation::TargetType::RequestBodyString:
    1466              :         case Transformation::TargetType::RequestBodyHexString:
    1467              :         case Transformation::TargetType::RequestBodyJson_String:
    1468              :         case Transformation::TargetType::RequestBodyJson_Integer:
    1469              :         case Transformation::TargetType::RequestBodyJson_Unsigned:
    1470              :         case Transformation::TargetType::RequestBodyJson_Float:
    1471              :         case Transformation::TargetType::RequestBodyJson_Boolean:
    1472              :         case Transformation::TargetType::RequestBodyJson_Object:
    1473              :         case Transformation::TargetType::RequestBodyJson_JsonString:
    1474              :         case Transformation::TargetType::RequestHeader_t:
    1475              :         case Transformation::TargetType::RequestDelayMs:
    1476              :         case Transformation::TargetType::RequestTimeoutMs:
    1477              :         case Transformation::TargetType::ClientEventToPurge:
    1478              :         case Transformation::TargetType::RequestUri_t:
    1479              :         case Transformation::TargetType::RequestMethod_t:
    1480            0 :             break;
    1481              :         }
    1482          191 :     }
    1483            1 :     catch (std::exception& e)
    1484              :     {
    1485            1 :         ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
    1486            1 :     }
    1487              : 
    1488              : 
    1489          172 :     return true;
    1490          181 : }
    1491              : 
    1492           12 : void AdminServerProvision::executeOnFilterFail(
    1493              :         const std::vector<std::shared_ptr<Transformation>> &fallbacks,
    1494              :         const std::string &requestUri, const std::string &requestUriPath,
    1495              :         const std::map<std::string, std::string> &requestQueryParametersMap,
    1496              :         const DataPart &requestBodyDataPart, const nghttp2::asio_http2::header_map &requestHeaders,
    1497              :         std::uint64_t generalUniqueServerSequence, TypeConverter &sourceVault,
    1498              :         std::map<std::string, std::string> &variables,
    1499              :         bool usesResponseBodyAsTransformationJsonTarget,
    1500              :         unsigned int &responseStatusCode, nlohmann::json &responseBodyJson, std::string &responseBody,
    1501              :         nghttp2::asio_http2::header_map &responseHeaders, unsigned int &responseDelayMs,
    1502              :         std::string &outState, std::string &outStateMethod, std::string &outStateUri,
    1503              :         std::vector<std::pair<std::string, std::string>> &clientProvisionTriggers, bool &breakCondition) const {
    1504              : 
    1505           16 :     for (const auto &fallback : fallbacks) {
    1506            4 :         std::string fbSource{};
    1507            4 :         std::smatch fbMatches{};
    1508            4 :         bool fbEraser = false;
    1509            4 :         if (!processSources(fallback, sourceVault, variables, requestUri, requestUriPath, requestQueryParametersMap, requestBodyDataPart, requestHeaders, fbEraser, generalUniqueServerSequence, usesResponseBodyAsTransformationJsonTarget, responseBodyJson)) continue;
    1510            4 :         if (fallback->hasFilter() && (fbEraser || !processFilters(fallback, sourceVault, variables, fbMatches, fbSource))) {
    1511            1 :             executeOnFilterFail(fallback->getOnFilterFail(), requestUri, requestUriPath, requestQueryParametersMap, requestBodyDataPart, requestHeaders, generalUniqueServerSequence, sourceVault, variables, usesResponseBodyAsTransformationJsonTarget, responseStatusCode, responseBodyJson, responseBody, responseHeaders, responseDelayMs, outState, outStateMethod, outStateUri, clientProvisionTriggers, breakCondition);
    1512            1 :             continue;
    1513              :         }
    1514            3 :         processTargets(fallback, sourceVault, variables, fbMatches, fbEraser, fallback->hasFilter(), responseStatusCode, responseBodyJson, responseBody, responseHeaders, responseDelayMs, outState, outStateMethod, outStateUri, clientProvisionTriggers, breakCondition);
    1515            5 :     }
    1516           12 : }
    1517              : 
    1518          153 : void AdminServerProvision::transform( const std::string &requestUri,
    1519              :                                       const std::string &requestUriPath,
    1520              :                                       const std::map<std::string, std::string> &requestQueryParametersMap,
    1521              :                                       DataPart &requestBodyDataPart,
    1522              :                                       const nghttp2::asio_http2::header_map &requestHeaders,
    1523              :                                       std::uint64_t generalUniqueServerSequence,
    1524              : 
    1525              :                                       /* OUTPUT PARAMETERS WHICH ALREADY HAVE DEFAULT VALUES BEFORE TRANSFORMATIONS: */
    1526              :                                       unsigned int &responseStatusCode,
    1527              :                                       nghttp2::asio_http2::header_map &responseHeaders,
    1528              :                                       std::string &responseBody,
    1529              :                                       unsigned int &responseDelayMs,
    1530              :                                       std::string &outState,
    1531              :                                       std::string &outStateMethod,
    1532              :                                       std::string &outStateUri,
    1533              :                                       std::vector<std::pair<std::string, std::string>> &clientProvisionTriggers,
    1534              :                                       std::map<std::string, std::string> &variables
    1535              :                                     )
    1536              : {
    1537              :     // Default values without transformations:
    1538          153 :     responseStatusCode = getResponseCode();
    1539          153 :     responseHeaders = getResponseHeaders();
    1540          153 :     responseDelayMs = getResponseDelayMilliseconds();
    1541          153 :     outState = getOutState(); // prepare next request state, with URI path before transformed with matching algorithms
    1542          153 :     outStateMethod = "";
    1543          153 :     outStateUri = "";
    1544              : 
    1545              :     // Check if the request body must be decoded:
    1546          153 :     bool mustDecodeRequestBody = false;
    1547          153 :     if (getRequestSchema()) {
    1548            2 :         mustDecodeRequestBody = true;
    1549              :     }
    1550              :     else {
    1551          332 :         for (const auto &t : transformations_) {
    1552          203 :             if (t->getSourceType() == Transformation::SourceType::RequestBody) {
    1553           22 :                 if (!requestBodyDataPart.str().empty()) {
    1554           21 :                     mustDecodeRequestBody = true;
    1555              :                 }
    1556              :                 else {
    1557            1 :                     LOGINFORMATIONAL(ert::tracing::Logger::informational("Empty request body received: some transformations will be ignored", ERT_FILE_LOCATION));
    1558              :                 }
    1559           22 :                 break;
    1560              :             }
    1561              :         }
    1562              :     }
    1563          153 :     if (mustDecodeRequestBody) {
    1564           23 :         requestBodyDataPart.decode(requestHeaders);
    1565              :     }
    1566              : 
    1567              :     // Request schema validation (normally used to validate native json received, but can also be used to validate the agent json representation (multipart, text, etc.)):
    1568          153 :     if (getRequestSchema()) {
    1569            2 :         std::string error{};
    1570            2 :         if (!getRequestSchema()->validate(requestBodyDataPart.getJson(), error)) {
    1571            1 :             responseStatusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
    1572            1 :             return; // INTERRUPT TRANSFORMATIONS
    1573              :         }
    1574            2 :     }
    1575              : 
    1576              :     // Find out if response body will need to be cloned (this is true if any transformation uses it as target):
    1577          152 :     bool usesResponseBodyAsTransformationJsonTarget = false;
    1578          289 :     for (const auto &t : transformations_) {
    1579          357 :         if (t->getTargetType() == Transformation::TargetType::ResponseBodyJson_String ||
    1580          332 :                 t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Integer ||
    1581          321 :                 t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Unsigned ||
    1582          316 :                 t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Float ||
    1583          312 :                 t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Boolean ||
    1584          512 :                 t->getTargetType() == Transformation::TargetType::ResponseBodyJson_Object ||
    1585          140 :                 t->getTargetType() == Transformation::TargetType::ResponseBodyJson_JsonString) {
    1586           50 :             usesResponseBodyAsTransformationJsonTarget = true;
    1587           50 :             break;
    1588              :         }
    1589              :     }
    1590              : 
    1591          152 :     nlohmann::json responseBodyJson;
    1592          152 :     if (usesResponseBodyAsTransformationJsonTarget) {
    1593           50 :         responseBodyJson = getResponseBody();   // clone provision response body to manipulate this copy and finally we will dump() it over 'responseBody':
    1594              :         // if(usesResponseBodyAsTransformationJsonTarget) responseBody = responseBodyJson.dump(); <--- place this after transformations (*)
    1595              :     }
    1596              :     else {
    1597          102 :         responseBody = getResponseBodyAsString(); // this could be overwritten by targets ResponseBodyString or ResponseBodyHexString
    1598              :     }
    1599              : 
    1600              :     // Type converter:
    1601          152 :     TypeConverter sourceVault{};
    1602              : 
    1603              :     // Apply transformations sequentially
    1604          152 :     bool breakCondition = false;
    1605          360 :     for (const auto &transformation : transformations_) {
    1606              : 
    1607          209 :         if (breakCondition) break;
    1608              : 
    1609          208 :         bool eraser = false;
    1610              : 
    1611          208 :         LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Processing transformation item: %s", transformation->asString().c_str()), ERT_FILE_LOCATION));
    1612              : 
    1613              :         // SOURCES: RequestUri, RequestUriPath, RequestUriParam, RequestBody, ResponseBody, RequestHeader, Eraser, Math, Random, Timestamp, Strftime, Recvseq, SVar, SGvar, Value, ServerEvent, InState
    1614          208 :         if (!processSources(transformation, sourceVault, variables, requestUri, requestUriPath, requestQueryParametersMap, requestBodyDataPart, requestHeaders, eraser, generalUniqueServerSequence, usesResponseBodyAsTransformationJsonTarget, responseBodyJson)) {
    1615           19 :             LOGDEBUG(ert::tracing::Logger::debug("Transformation item skipped on source", ERT_FILE_LOCATION));
    1616           39 :             continue;
    1617              :         }
    1618              : 
    1619          189 :         std::smatch matches; // BE CAREFUL!: https://stackoverflow.com/a/51709911/2576671
    1620              :         // So, we can't use 'matches' as container because source may change: BUT, using that source exclusively, it will work (*)
    1621          189 :         std::string source; // Now, this never will be out of scope, and 'matches' will be valid.
    1622              : 
    1623              :         // FILTERS: RegexCapture, RegexReplace, Append, Prepend, Sum, Multiply, ConditionVar, EqualTo, DifferentFrom, JsonConstraint, SchemaId
    1624          189 :         bool hasFilter = transformation->hasFilter();
    1625          189 :         if (hasFilter) {
    1626           59 :             if (eraser || !processFilters(transformation, sourceVault, variables, matches, source)) {
    1627           11 :                 LOGDEBUG(ert::tracing::Logger::debug("Transformation item skipped on filter", ERT_FILE_LOCATION));
    1628           11 :                 if (eraser) LOGWARNING(ert::tracing::Logger::warning("Filter is not allowed when using 'eraser' source type. Transformation will be ignored.", ERT_FILE_LOCATION));
    1629              : 
    1630              :                 // onFilterFail:
    1631           11 :                 executeOnFilterFail(transformation->getOnFilterFail(), requestUri, requestUriPath, requestQueryParametersMap, requestBodyDataPart, requestHeaders, generalUniqueServerSequence, sourceVault, variables, usesResponseBodyAsTransformationJsonTarget, responseStatusCode, responseBodyJson, responseBody, responseHeaders, responseDelayMs, outState, outStateMethod, outStateUri, clientProvisionTriggers, breakCondition);
    1632              : 
    1633           11 :                 continue;
    1634              :             }
    1635              :         }
    1636              : 
    1637              :         // TARGETS: ResponseBodyString, ResponseBodyHexString, ResponseBodyJson_String, ResponseBodyJson_Integer, ResponseBodyJson_Unsigned, ResponseBodyJson_Float, ResponseBodyJson_Boolean, ResponseBodyJson_Object, ResponseBodyJson_JsonString, ResponseHeader, ResponseStatusCode, ResponseDelayMs, TVar, TGVar, OutState, TTxtFile, TBinFile, UDPSocket, ServerEventToPurge, Break
    1638          178 :         if (!processTargets(transformation, sourceVault, variables, matches, eraser, hasFilter, responseStatusCode, responseBodyJson, responseBody, responseHeaders, responseDelayMs, outState, outStateMethod, outStateUri, clientProvisionTriggers, breakCondition)) {
    1639            9 :             LOGDEBUG(ert::tracing::Logger::debug("Transformation item skipped on target", ERT_FILE_LOCATION));
    1640            9 :             continue;
    1641              :         }
    1642              : 
    1643          209 :     }
    1644              : 
    1645              :     // (*) Regenerate final responseBody after transformations:
    1646          152 :     if(usesResponseBodyAsTransformationJsonTarget && !responseBodyJson.empty()) {
    1647              :         try {
    1648           49 :             responseBody = responseBodyJson.dump(); // this may arise type error, for example in case of trying to set json field value with binary data:
    1649              :             // When having a provision transformation from 'request.body' to 'response.body.json.string./whatever':
    1650              :             // echo -en '\x80\x01' | curl --http2-prior-knowledge -i -H 'content-type:application/octet-stream' -X GET "<traffic url>/uri" --data-binary @-
    1651              :             //
    1652              :             // This is not valid and must be protected. The user should use another kind of target to store binary.
    1653              :         }
    1654            1 :         catch (const std::exception& e)
    1655              :         {
    1656            1 :             ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
    1657            1 :         }
    1658              :     }
    1659              : 
    1660              :     // Response schema validation (not supported for response body created by non-json targets, to simplify the fact to parse need on ResponseBodyString/ResponseBodyHexString):
    1661          152 :     if (getResponseSchema()) {
    1662            1 :         std::string error{};
    1663            1 :         if (!getResponseSchema()->validate(usesResponseBodyAsTransformationJsonTarget ? responseBodyJson:getResponseBody(), error)) {
    1664            1 :             responseStatusCode = ert::http2comm::ResponseCode::INTERNAL_SERVER_ERROR; // 500: built response will be anyway sent although status code is overwritten with internal server error.
    1665              :         }
    1666            1 :     }
    1667          152 : }
    1668              : 
    1669          171 : bool AdminServerProvision::load(const nlohmann::json &j, bool regexMatchingConfigured) {
    1670              : 
    1671              :     // Store whole document (useful for GET operation)
    1672          171 :     json_ = j;
    1673              : 
    1674              :     // Mandatory
    1675          171 :     auto requestMethod_it = j.find("requestMethod");
    1676          171 :     request_method_ = *requestMethod_it;
    1677              : 
    1678          171 :     auto it = j.find("responseCode");
    1679          171 :     response_code_ = *it;
    1680              : 
    1681              :     // Optional
    1682          171 :     it = j.find("requestUri");
    1683          171 :     if (it != j.end() && it->is_string()) {
    1684          171 :         request_uri_ = *it;
    1685              :     }
    1686              : 
    1687          171 :     it = j.find("inState");
    1688          171 :     if (it != j.end() && it->is_string()) {
    1689            3 :         in_state_ = *it;
    1690            3 :         if (in_state_.empty()) in_state_ = DEFAULT_ADMIN_PROVISION_STATE;
    1691              :     }
    1692              : 
    1693          171 :     it = j.find("outState");
    1694          171 :     if (it != j.end() && it->is_string()) {
    1695            2 :         out_state_ = *it;
    1696            2 :         if (out_state_.empty()) out_state_ = DEFAULT_ADMIN_PROVISION_STATE;
    1697              :     }
    1698              : 
    1699          171 :     it = j.find("requestSchemaId");
    1700          171 :     if (it != j.end() && it->is_string()) {
    1701            7 :         request_schema_id_ = *it;
    1702            7 :         if (request_schema_id_.empty()) {
    1703            1 :             ert::tracing::Logger::error("Invalid empty request schema identifier", ERT_FILE_LOCATION);
    1704            1 :             return false;
    1705              :         }
    1706              :     }
    1707              : 
    1708          170 :     it = j.find("responseSchemaId");
    1709          170 :     if (it != j.end() && it->is_string()) {
    1710            7 :         response_schema_id_ = *it;
    1711            7 :         if (response_schema_id_.empty()) {
    1712            1 :             ert::tracing::Logger::error("Invalid empty response schema identifier", ERT_FILE_LOCATION);
    1713            1 :             return false;
    1714              :         }
    1715              :     }
    1716              : 
    1717          169 :     it = j.find("responseHeaders");
    1718          169 :     if (it != j.end() && it->is_object()) {
    1719          483 :         for (auto& [key, val] : it->items())
    1720          483 :             response_headers_.emplace(key, nghttp2::asio_http2::header_value{val});
    1721              :     }
    1722              : 
    1723          169 :     it = j.find("responseBody");
    1724          169 :     if (it != j.end()) {
    1725          161 :         if (it->is_object() || it->is_array()) {
    1726          155 :             response_body_ = *it;
    1727          155 :             response_body_string_ = response_body_.dump(); // valid as cache for static responses (not updated with transformations)
    1728              :         }
    1729            6 :         else if (it->is_string()) {
    1730            1 :             response_body_string_ = *it;
    1731              :         }
    1732            5 :         else if (it->is_number_integer() || it->is_number_unsigned()) {
    1733              :             //response_body_integer_ = *it;
    1734            2 :             int number = *it;
    1735            2 :             response_body_string_ = std::to_string(number);
    1736              :         }
    1737            3 :         else if (it->is_number_float()) {
    1738              :             //response_body_number_ = *it;
    1739            1 :             response_body_string_ = std::to_string(double(*it));
    1740              :         }
    1741            2 :         else if (it->is_boolean()) {
    1742              :             //response_body_boolean_ = *it;
    1743            1 :             response_body_string_ = ((bool)(*it) ? "true":"false");
    1744              :         }
    1745            1 :         else if (it->is_null()) {
    1746              :             //response_body_null_ = true;
    1747            1 :             response_body_string_ = "null";
    1748              :         }
    1749              :     }
    1750              : 
    1751          169 :     it = j.find("responseDelayMs");
    1752          169 :     if (it != j.end() && it->is_number()) {
    1753          156 :         response_delay_ms_ = *it;
    1754              :     }
    1755              : 
    1756          169 :     auto transform_it = j.find("transform");
    1757          169 :     if (transform_it != j.end()) {
    1758          368 :         for (auto it : *transform_it) { // "it" is of type json::reference and has no key() member
    1759          218 :             loadTransformation(it);
    1760          218 :         }
    1761          150 :         if (!transformations_.empty() && transformations_.back()->getTargetType() == Transformation::TargetType::Break) {
    1762            0 :             LOGWARNING(ert::tracing::Logger::warning("Break as last 'transform' item is illogical (no further items to interrupt)", ERT_FILE_LOCATION));
    1763              :         }
    1764              :     }
    1765              : 
    1766              :     // Store key:
    1767          169 :     h2agent::model::calculateStringKey(key_, in_state_, request_method_, request_uri_);
    1768              : 
    1769          169 :     if (regexMatchingConfigured) {
    1770              :         // Precompile regex with key, only for 'RegexMatching' algorithm:
    1771              :         try {
    1772            6 :             LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Assigning regex: %s", key_.c_str()), ERT_FILE_LOCATION));
    1773            6 :             regex_.assign(key_, std::regex::optimize);
    1774              :         }
    1775            2 :         catch (std::regex_error &e) {
    1776            4 :             ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
    1777            2 :             ert::tracing::Logger::error("Invalid regular expression (detected when joining 'inState' and/or 'requestUri' from provision) for current 'RegexMatching' server matching algorithm", ERT_FILE_LOCATION);
    1778            2 :             return false;
    1779            2 :         }
    1780              :     }
    1781              : 
    1782          167 :     return true;
    1783              : }
    1784              : 
    1785          218 : void AdminServerProvision::loadTransformation(const nlohmann::json &j) {
    1786              : 
    1787          218 :     LOGDEBUG(
    1788              :         std::string msg = ert::tracing::Logger::asString("Loading transformation item: %s", j.dump().c_str()); // avoid newlines in traces (dump(n) pretty print)
    1789              :         ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
    1790              :     );
    1791              : 
    1792              :     // Transformation object to fill:
    1793          218 :     auto transformation = std::make_shared<Transformation>();
    1794              : 
    1795          218 :     if (transformation->load(j)) {
    1796          215 :         transformations_.push_back(transformation);
    1797          215 :         LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Loaded transformation item: %s", transformation->asString().c_str()), ERT_FILE_LOCATION));
    1798              :     }
    1799              :     else {
    1800            6 :         ert::tracing::Logger::error("Discarded transform item due to incoherent data", ERT_FILE_LOCATION);
    1801              :     }
    1802          218 : }
    1803              : 
    1804              : 
    1805              : }
    1806              : }
    1807              : 
        

Generated by: LCOV version 2.0-1