LCOV - code coverage report
Current view: top level - model - functions.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 95.6 % 180 172
Test Date: 2026-04-17 17:21:26 Functions: 100.0 % 13 13

            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 <fstream>
      37              : #include <regex>
      38              : #include <ctype.h>
      39              : 
      40              : #include <functions.hpp>
      41              : 
      42              : #include <ert/tracing/Logger.hpp>
      43              : #include <ert/http2comm/URLFunctions.hpp>
      44              : 
      45              : 
      46              : namespace h2agent
      47              : {
      48              : namespace model
      49              : {
      50              : 
      51          946 : void calculateStringKey(std::string &key, const std::string &k1, const std::string &k2, const std::string &k3) {
      52          946 :     key = k1;
      53          946 :     key += "#";
      54          946 :     key += k2;
      55          946 :     if (!k3.empty()) {
      56          579 :         key += "#";
      57          579 :         key += k3;
      58              :     }
      59          946 : }
      60              : 
      61            2 : void aggregateKeyPart(std::string &key, const std::string &k1, const std::string &k2) {
      62            2 :     if (k2.empty()) {
      63            1 :         key.insert(0, k1 + "#");
      64            1 :         return;
      65              :     }
      66            1 :     key = k1;
      67            1 :     key += "#";
      68            1 :     key += k2;
      69              : }
      70              : 
      71           60 : bool string2uint64andSign(const std::string &input, std::uint64_t &output, bool &negative) {
      72              : 
      73           60 :     bool result = false;
      74              : 
      75           60 :     if (!input.empty()) {
      76           58 :         negative = (input[0] == '-');
      77              : 
      78              :         try {
      79           70 :             output = std::stoull(negative ? input.substr(1):input);
      80           46 :             result = true;
      81              :         }
      82           12 :         catch(std::exception &e)
      83              :         {
      84           12 :             std::string msg = ert::tracing::Logger::asString("Error converting string '%s' to unsigned long long integer%s: %s", input.c_str(), (negative ? " with negative sign":""), e.what());
      85           12 :             ert::tracing::Logger::error(msg, ERT_FILE_LOCATION);
      86           12 :         }
      87              :     }
      88              : 
      89           60 :     return result;
      90              : }
      91              : 
      92          274 : std::map<std::string, std::string> extractQueryParameters(const std::string &queryParams, std::string *sortedQueryParameters, char separator) {
      93          274 :     std::map<std::string, std::string> result, resultOriginal;
      94              : 
      95          274 :     if (queryParams.empty()) return result;
      96              : 
      97              :     // Inspired in https://github.com/ben-zen/uri-library
      98              :     // Loop over the query string looking for '&'s (maybe ';'s), then check each one for
      99              :     // an '=' to find keys and values; if there's not an '=' then the key
     100              :     // will have an empty value in the map.
     101          274 :     size_t pos = 0;
     102          274 :     size_t qpair_end = queryParams.find_first_of(separator);
     103              :     do
     104              :     {
     105          398 :         std::string qpair = queryParams.substr(pos, ((qpair_end != std::string::npos) ? (qpair_end - pos) : std::string::npos));
     106          398 :         size_t key_value_divider = qpair.find_first_of('=');
     107          398 :         std::string key = qpair.substr(0, key_value_divider);
     108          398 :         std::string value;
     109          398 :         if (key_value_divider != std::string::npos)
     110              :         {
     111          393 :             value = qpair.substr((key_value_divider + 1));
     112              :         }
     113              : 
     114          398 :         if (result.count(key) != 0)
     115              :         {
     116            1 :             ert::tracing::Logger::error("Cannot normalize URI query parameters: repeated key found", ERT_FILE_LOCATION);
     117            1 :             result.clear();
     118            1 :             return result;
     119              :         }
     120              : 
     121              :         // Store original value when sorted query parameters list is requested:
     122          397 :         if (sortedQueryParameters) {
     123            6 :             resultOriginal.emplace(key, value);
     124              :         }
     125              : 
     126          397 :         std::string valueDecoded = ert::http2comm::URLFunctions::decode(value);
     127          397 :         bool decoded = (valueDecoded != value);
     128          397 :         if (decoded) {
     129           31 :             value = valueDecoded;
     130              :         }
     131          397 :         LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Extracted query parameter %s = %s%s", key.c_str(), value.c_str(), (decoded ? " (decoded)":"")), ERT_FILE_LOCATION));
     132          397 :         result.emplace(key, value);
     133          397 :         pos = ((qpair_end != std::string::npos) ? (qpair_end + 1) : std::string::npos);
     134          397 :         qpair_end = queryParams.find_first_of(separator, pos);
     135          400 :     }
     136          397 :     while ((qpair_end != std::string::npos) || (pos != std::string::npos));
     137              : 
     138              :     // Build sorted literal:
     139          273 :     if (sortedQueryParameters) {
     140            3 :         std::string &ref = *sortedQueryParameters;
     141            3 :         ref.clear();
     142            3 :         ref.reserve(queryParams.size());
     143            9 :         for(auto it = resultOriginal.begin(); it != resultOriginal.end(); it ++) {
     144            6 :             if (it != resultOriginal.begin()) ref += separator;
     145            6 :             ref += it->first; // key
     146            6 :             if (!it->second.empty()) {
     147            6 :                 ref += "=";
     148            6 :                 ref += it->second;
     149              :             }
     150              :         }
     151              :     }
     152              : 
     153          273 :     return result;
     154              : } // LCOV_EXCL_LINE
     155              : 
     156            2 : bool getFileContent(const std::string &filePath, std::string &content)
     157              : {
     158            2 :     std::ifstream ifs(filePath);
     159              : 
     160            2 :     if (!ifs.is_open()) {
     161            1 :         ert::tracing::Logger::error(ert::tracing::Logger::asString("Cannot open file '%s' !", filePath.c_str()), ERT_FILE_LOCATION);
     162            1 :         return false;
     163              :     }
     164              : 
     165            1 :     LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Reading content from file '%s'", filePath.c_str()), ERT_FILE_LOCATION));
     166            1 :     std::stringstream buffer;
     167            1 :     buffer << ifs.rdbuf();
     168            1 :     ifs.close();
     169            1 :     content = buffer.str();
     170              : 
     171            1 :     return true;
     172            2 : }
     173              : 
     174          727 : bool parseJsonContent(const std::string &content, nlohmann::json &jsonObject, bool writeException) {
     175              : 
     176              :     try {
     177          731 :         jsonObject = nlohmann::json::parse(content);
     178          723 :         LOGDEBUG(
     179              :             std::string msg("Json body parsed: ");
     180              :             msg += jsonObject.dump();
     181              :             ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     182              :         );
     183              :     }
     184            4 :     catch (nlohmann::json::parse_error& e)
     185              :     {
     186            4 :         std::stringstream ss;
     187            4 :         ss << "Json content parse error: " << e.what()
     188            4 :            << " | exception id: " << e.id
     189            4 :            << " | byte position of error: " << e.byte;
     190              : 
     191            4 :         if (writeException)
     192            2 :             jsonObject =  ss.str();
     193              : 
     194              :         // This will be debug, not error, because plain strings would always fail and some application
     195              :         //  could work with non-json bodies:
     196            4 :         LOGDEBUG(ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION));
     197              : 
     198            4 :         return false;
     199            4 :     }
     200              : 
     201          723 :     return true;
     202              : }
     203              : 
     204            4 : bool asAsciiString(const std::string &input, std::string &output) {
     205              : 
     206            4 :     bool result = true; // supposed printable by default
     207              : 
     208            4 :     if(input.empty()) {
     209            1 :         output = "<null>";
     210            1 :         return false;
     211              :     }
     212              : 
     213            3 :     std::for_each(input.begin(), input.end(), [&] (char const &c) {
     214              : 
     215           19 :         int printable = isprint(c);
     216           19 :         output += (printable ? c:'.');
     217              : 
     218           19 :         if(!printable) result = false;
     219           19 :     });
     220              : 
     221            3 :     return result;
     222              : }
     223              : 
     224          175 : bool asHexString(const std::string &input, std::string &output) {
     225              : 
     226          175 :     bool result = true; // supposed printable by default
     227              : 
     228              :     int byte;
     229          175 :     output = "0x";
     230              : 
     231          175 :     std::for_each(input.begin(), input.end(), [&] (char const &c) {
     232              : 
     233         1710 :         byte = (c & 0xf0) >> 4;
     234         1710 :         output += (byte >= 0 && byte <= 9) ? (byte + '0') : ((byte - 0xa) + 'a');
     235         1710 :         byte = (c & 0x0f);
     236         1710 :         output += (byte >= 0 && byte <= 9) ? (byte + '0') : ((byte - 0xa) + 'a');
     237              : 
     238         1710 :         if (!isprint(c)) result = false;
     239         1710 :     });
     240              : 
     241          175 :     return result;
     242              : }
     243              : 
     244           23 : bool fromHexString(const std::string &input, std::string &output) {
     245              : 
     246           23 :     bool result = true; // supposed successful by default
     247              : 
     248           23 :     bool has0x = (input.rfind("0x", 0) == 0);
     249              : 
     250           23 :     if((input.length() % 2) != 0) {
     251            1 :         LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Invalid hexadecimal string due to odd length (%d): %s", input.length(), input.c_str()), ERT_FILE_LOCATION));
     252            1 :         return false;
     253              :     }
     254              : 
     255           22 :     output = "";
     256           22 :     const char* src = input.data(); // fastest that accessing input[ii]
     257              :     unsigned char hex;
     258              :     int aux;
     259              : 
     260          426 :     for(int ii = 1 + (has0x ? 2:0), maxii = input.length(); ii < maxii; ii += 2) {
     261          406 :         if(isxdigit(aux = src[ii-1]) == 0) {
     262            1 :             LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Invalid hexadecimal string: %s", input.c_str()), ERT_FILE_LOCATION));
     263            1 :             return false;
     264              :         }
     265              : 
     266          405 :         hex = ((aux >= '0' && aux <= '9') ? (aux - '0') : ((aux - 'a') + 0x0a)) << 4;
     267              : 
     268          405 :         if(isxdigit(aux = src[ii]) == 0) {
     269            1 :             LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Invalid hexadecimal string: %s", input.c_str()), ERT_FILE_LOCATION));
     270            1 :             return false;
     271              :         }
     272              : 
     273          404 :         hex |= (aux >= '0' && aux <= '9') ? (aux - '0') : ((aux - 'a') + 0x0a);
     274          404 :         output += hex;
     275              :     }
     276              : 
     277           20 :     return result;
     278              : }
     279              : 
     280           17 : bool jsonConstraint(const nlohmann::json &received, const nlohmann::json &expected, std::string &failReport) {
     281              : 
     282           17 :     LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Received object: %s", received.dump().c_str()), ERT_FILE_LOCATION));
     283           17 :     LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Expected object: %s", expected.dump().c_str()), ERT_FILE_LOCATION));
     284              : 
     285              :     // Array mode: every element in expected must exist in received
     286           17 :     if (expected.is_array()) {
     287            2 :         if (!received.is_array()) {
     288            0 :             failReport = "JsonConstraint FAILED: expected array but received non-array";
     289            0 :             LOGINFORMATIONAL(ert::tracing::Logger::informational(failReport, ERT_FILE_LOCATION));
     290            0 :             return false;
     291              :         }
     292            8 :         for (size_t i = 0; i < expected.size(); i++) {
     293            6 :             bool found = false;
     294           12 :             for (size_t j = 0; j < received.size(); j++) {
     295           12 :                 if (expected[i].is_object() && received[j].is_object()) {
     296            6 :                     std::string dummy;
     297            6 :                     if (h2agent::model::jsonConstraint(received[j], expected[i], dummy)) { found = true; break; }
     298            6 :                 }
     299            6 :                 else if (received[j] == expected[i]) { found = true; break; }
     300              :             }
     301            6 :             if (!found) {
     302            0 :                 failReport = ert::tracing::Logger::asString("JsonConstraint FAILED: expected element at index '%zu' not found in validated source array: %s", i, expected[i].dump().c_str());
     303            0 :                 LOGINFORMATIONAL(ert::tracing::Logger::informational(failReport, ERT_FILE_LOCATION));
     304            0 :                 return false;
     305              :             }
     306              :         }
     307            2 :         LOGDEBUG(ert::tracing::Logger::debug("JsonConstraint SUCCEED", ERT_FILE_LOCATION));
     308            2 :         return true;
     309              :     }
     310              : 
     311              :     // Object mode: every key/value in expected must exist in received
     312           28 :     for (auto& [key, value] : expected.items()) {
     313              : 
     314              :         // Check if key exists in document:
     315           21 :         if (!received.contains(key)) {
     316            1 :             failReport = ert::tracing::Logger::asString("JsonConstraint FAILED: expected key '%s' is missing in validated source", key.c_str());
     317            1 :             LOGINFORMATIONAL(ert::tracing::Logger::informational(failReport, ERT_FILE_LOCATION));
     318            1 :             return false;
     319              :         }
     320              : 
     321              :         // Check if value is JSON object to make recursive call:
     322           20 :         if (value.is_object()) {
     323            2 :             if (!h2agent::model::jsonConstraint(received[key], value, failReport)) {
     324            1 :                 return false;
     325              :             }
     326           18 :         } else if (value.is_array() && received[key].is_array()) {
     327              :             // Recursive call: reuses top-level array "contains" logic (order-independent, extras allowed)
     328            1 :             if (!h2agent::model::jsonConstraint(received[key], value, failReport)) {
     329            0 :                 return false;
     330              :             }
     331              :         } else {
     332              :             // Check same value:
     333           17 :             if (received[key] != value) {
     334            6 :                 failReport = ert::tracing::Logger::asString("JsonConstraint FAILED: expected value for key '%s' differs regarding validated source", key.c_str());
     335            6 :                 LOGINFORMATIONAL(ert::tracing::Logger::informational(failReport, ERT_FILE_LOCATION));
     336            6 :                 return false;
     337              :             }
     338              :         }
     339           23 :     }
     340              : 
     341            7 :     LOGDEBUG(ert::tracing::Logger::debug("JsonConstraint SUCCEED", ERT_FILE_LOCATION));
     342            7 :     return true;
     343              : }
     344              : 
     345           11 : std::string fixMetricsName(const std::string &in) {
     346              : 
     347           11 :     std::string result{}; // = std::regex_replace(key_, invalidMetricsNamesCharactersRegex, "_");
     348              : 
     349              :     // https://prometheus.io/docs/instrumenting/writing_exporters/#naming
     350           11 :     static std::regex validMetricsNamesCharactersRegex("[a-zA-Z0-9:_]", std::regex::optimize);
     351              : 
     352          119 :     for (char c : in) {
     353          216 :         if (std::regex_match(std::string(1, c), validMetricsNamesCharactersRegex)) {
     354          104 :             result += c;
     355              :         } else {
     356            4 :             result += "_";
     357              :         }
     358              :     }
     359              : 
     360           11 :     return result;
     361            0 : }
     362              : 
     363              : }
     364              : }
     365              : 
        

Generated by: LCOV version 2.0-1