LCOV - code coverage report
Current view: top level - datadog - remote_config.cpp (source / functions) Hit Total Coverage
Test: filtered.info Lines: 92 104 88.5 %
Date: 2024-01-03 20:30:12 Functions: 9 9 100.0 %

          Line data    Source code
       1             : #include "remote_config.h"
       2             : 
       3             : #include <cstdint>
       4             : #include <type_traits>
       5             : #include <unordered_set>
       6             : 
       7             : #include "base64.h"
       8             : #include "datadog/string_view.h"
       9             : #include "json.hpp"
      10             : #include "random.h"
      11             : #include "version.h"
      12             : 
      13             : using namespace nlohmann::literals;
      14             : 
      15             : namespace datadog {
      16             : namespace tracing {
      17             : namespace {
      18             : 
      19             : // The ".client.capabilities" field of the remote config request payload
      20             : // describes which parts of the library's configuration are supported for remote
      21             : // configuration.
      22             : //
      23             : // It's a bitset, 64 bits wide, where each bit indicates whether the library
      24             : // supports a particular feature for remote configuration.
      25             : //
      26             : // The bitset is encoded in the request as a JSON array of 8 integers, where
      27             : // each integer is one byte from the 64 bits. The bytes are in big-endian order
      28             : // within the array.
      29             : enum CapabilitiesFlag : uint64_t {
      30             :   APM_TRACING_SAMPLE_RATE = 1 << 12,
      31             : };
      32             : 
      33             : constexpr std::array<uint8_t, sizeof(uint64_t)> capabilities_byte_array(
      34             :     uint64_t in) {
      35             :   std::size_t j = sizeof(in) - 1;
      36             :   std::array<uint8_t, sizeof(uint64_t)> res{};
      37             :   for (std::size_t i = 0; i < sizeof(in); ++i) {
      38             :     res[j--] = in >> (i * 8);
      39             :   }
      40             : 
      41             :   return res;
      42             : }
      43             : 
      44             : constexpr std::array<uint8_t, sizeof(uint64_t)> k_apm_capabilities =
      45             :     capabilities_byte_array(APM_TRACING_SAMPLE_RATE);
      46             : 
      47             : constexpr StringView k_apm_product = "APM_TRACING";
      48             : constexpr StringView k_apm_product_path_substring = "/APM_TRACING/";
      49             : 
      50           3 : ConfigUpdate parse_dynamic_config(const nlohmann::json& j) {
      51           3 :   ConfigUpdate config_update;
      52             : 
      53           3 :   if (auto sampling_rate_it = j.find("tracing_sampling_rate");
      54           3 :       sampling_rate_it != j.cend()) {
      55           2 :     TraceSamplerConfig trace_sampler_cfg;
      56           2 :     trace_sampler_cfg.sample_rate = *sampling_rate_it;
      57             : 
      58           2 :     config_update.trace_sampler = trace_sampler_cfg;
      59           2 :   }
      60             : 
      61           3 :   return config_update;
      62           0 : }
      63             : 
      64             : }  // namespace
      65             : 
      66         447 : RemoteConfigurationManager::RemoteConfigurationManager(
      67         447 :     const TracerSignature& tracer_signature, ConfigManager& config_manager)
      68         447 :     : tracer_signature_(tracer_signature),
      69         447 :       config_manager_(config_manager),
      70         447 :       client_id_(uuid()) {}
      71             : 
      72          11 : bool RemoteConfigurationManager::is_new_config(
      73             :     StringView config_path, const nlohmann::json& config_meta) {
      74          11 :   auto it = applied_config_.find(std::string{config_path});
      75          11 :   if (it == applied_config_.cend()) return true;
      76             : 
      77           1 :   return it->second.hash !=
      78           2 :          config_meta.at("/hashes/sha256"_json_pointer).get<StringView>();
      79             : }
      80             : 
      81          14 : nlohmann::json RemoteConfigurationManager::make_request_payload() {
      82             :   // clang-format off
      83             :   auto j = nlohmann::json{
      84             :     {"client", {
      85          14 :       {"id", client_id_},
      86          42 :       {"products", nlohmann::json::array({k_apm_product})},
      87             :       {"is_tracer", true},
      88             :       {"capabilities", k_apm_capabilities},
      89             :       {"client_tracer", {
      90          14 :         {"runtime_id", tracer_signature_.runtime_id().string()},
      91          14 :         {"language", tracer_signature_.library_language()},
      92          14 :         {"tracer_version", tracer_signature_.library_version()},
      93          14 :         {"service", tracer_signature_.default_service()},
      94          14 :         {"env", tracer_signature_.default_environment()}
      95             :       }},
      96             :       {"state", {
      97           0 :         {"root_version", 1},
      98          14 :         {"targets_version", state_.targets_version},
      99          14 :         {"backend_client_state", state_.opaque_backend_state}
     100             :       }}
     101             :     }}
     102         994 :   };
     103             :   // clang-format on
     104             : 
     105          14 :   if (!applied_config_.empty()) {
     106           0 :     auto config_states = nlohmann::json::array();
     107           0 :     for (const auto& [_, config] : applied_config_) {
     108           0 :       config_states.emplace_back(nlohmann::json{{"id", config.id},
     109           0 :                                                 {"version", config.version},
     110           0 :                                                 {"product", k_apm_product}});
     111             :     }
     112             : 
     113           0 :     j["config_states"] = config_states;
     114           0 :   }
     115             : 
     116          14 :   if (state_.error_message) {
     117          13 :     j["has_error"] = true;
     118          13 :     j["error"] = *state_.error_message;
     119             :   }
     120             : 
     121          14 :   return j;
     122           0 : }
     123             : 
     124          19 : void RemoteConfigurationManager::process_response(const nlohmann::json& json) {
     125          19 :   state_.error_message = nullopt;
     126             : 
     127             :   try {
     128             :     const auto targets = nlohmann::json::parse(
     129          41 :         base64_decode(json.at("targets").get<StringView>()));
     130             : 
     131          16 :     state_.targets_version = targets.at("/signed/version"_json_pointer);
     132             :     state_.opaque_backend_state =
     133          14 :         targets.at("/signed/custom/opaque_backend_state"_json_pointer);
     134             : 
     135          14 :     const auto client_configs_it = json.find("client_configs");
     136             : 
     137             :     // `client_configs` is absent => remove previously applied configuration if
     138             :     // any applied.
     139          14 :     if (client_configs_it == json.cend()) {
     140           1 :       if (!applied_config_.empty()) {
     141           1 :         std::for_each(applied_config_.cbegin(), applied_config_.cend(),
     142           1 :                       [this](const auto it) { revert_config(it.second); });
     143           1 :         applied_config_.clear();
     144             :       }
     145           1 :       return;
     146             :     }
     147             : 
     148             :     // Keep track of config path received to know which ones to revert.
     149          13 :     std::unordered_set<std::string> visited_config;
     150          13 :     visited_config.reserve(client_configs_it->size());
     151             : 
     152          18 :     for (const auto& client_config : *client_configs_it) {
     153          13 :       auto config_path = client_config.get<StringView>();
     154          13 :       visited_config.emplace(config_path);
     155             : 
     156             :       const auto& config_metadata =
     157          15 :           targets.at("/signed/targets"_json_pointer).at(config_path);
     158          22 :       if (!contains(config_path, k_apm_product_path_substring) ||
     159          11 :           !is_new_config(config_path, config_metadata)) {
     160           2 :         continue;
     161             :       }
     162             : 
     163          12 :       const auto& target_files = json.at("/target_files"_json_pointer);
     164             :       auto target_it = std::find_if(
     165          11 :           target_files.cbegin(), target_files.cend(),
     166           9 :           [&config_path](const nlohmann::json& j) {
     167           9 :             return j.at("/path"_json_pointer).get<StringView>() == config_path;
     168          20 :           });
     169             : 
     170           9 :       if (target_it == target_files.cend()) {
     171             :         state_.error_message =
     172           1 :             "Missing configuration from Remote Configuration response";
     173           1 :         return;
     174             :       }
     175             : 
     176             :       const auto config_json = nlohmann::json::parse(
     177          18 :           base64_decode(target_it.value().at("raw").get<StringView>()));
     178             : 
     179           6 :       const auto& targeted_service = config_json.at("service_target");
     180           5 :       if (targeted_service.at("service").get<StringView>() !=
     181           9 :               tracer_signature_.default_service() ||
     182           4 :           targeted_service.at("env").get<StringView>() !=
     183             :               tracer_signature_.default_environment()) {
     184           2 :         continue;
     185             :       }
     186             : 
     187           3 :       Configuration new_config;
     188           3 :       new_config.hash = config_metadata.at("/hashes/sha256"_json_pointer);
     189           3 :       new_config.id = config_json.at("id");
     190           3 :       new_config.version = config_json.at("revision");
     191           3 :       new_config.content = parse_dynamic_config(config_json.at("lib_config"));
     192             : 
     193           3 :       apply_config(new_config);
     194           3 :       applied_config_[std::string{config_path}] = new_config;
     195           6 :     }
     196             : 
     197             :     // Applied configuration not present must be reverted.
     198           8 :     for (auto it = applied_config_.cbegin(); it != applied_config_.cend();) {
     199           3 :       if (!visited_config.count(it->first)) {
     200           0 :         revert_config(it->second);
     201           0 :         it = applied_config_.erase(it);
     202             :       } else {
     203           3 :         it++;
     204             :       }
     205             :     }
     206          35 :   } catch (const nlohmann::json::exception& e) {
     207          12 :     std::string error_message = "Ill-formatted Remote Configuration response: ";
     208          12 :     error_message += e.what();
     209             : 
     210          12 :     state_.error_message = std::move(error_message);
     211          12 :   }
     212             : }
     213             : 
     214           3 : void RemoteConfigurationManager::apply_config(Configuration config) {
     215           3 :   config_manager_.update(config.content);
     216           3 : }
     217             : 
     218           1 : void RemoteConfigurationManager::revert_config(Configuration) {
     219           1 :   config_manager_.reset();
     220           1 : }
     221             : 
     222             : }  // namespace tracing
     223             : }  // namespace datadog

Generated by: LCOV version 1.16