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

Generated by: LCOV version 2.0-1