123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- // Copyright The OpenTelemetry Authors
- // SPDX-License-Identifier: Apache-2.0
- #pragma once
- #include <ctype.h>
- #include <cstddef>
- #include <string>
- #include <vector>
- #include "opentelemetry/common/kv_properties.h"
- #include "opentelemetry/nostd/function_ref.h"
- #include "opentelemetry/nostd/shared_ptr.h"
- #include "opentelemetry/nostd/string_view.h"
- #include "opentelemetry/nostd/unique_ptr.h"
- #include "opentelemetry/version.h"
- #if OPENTELEMETRY_HAVE_WORKING_REGEX
- # include <regex>
- #endif
- OPENTELEMETRY_BEGIN_NAMESPACE
- namespace trace
- {
- /**
- * TraceState carries tracing-system specific context in a list of key-value pairs. TraceState
- * allows different vendors to propagate additional information and inter-operate with their legacy
- * id formats.
- *
- * For more information, see the W3C Trace Context specification:
- * https://www.w3.org/TR/trace-context
- */
- class OPENTELEMETRY_EXPORT TraceState
- {
- public:
- static constexpr int kKeyMaxSize = 256;
- static constexpr int kValueMaxSize = 256;
- static constexpr int kMaxKeyValuePairs = 32;
- static constexpr auto kKeyValueSeparator = '=';
- static constexpr auto kMembersSeparator = ',';
- OPENTELEMETRY_API_SINGLETON static nostd::shared_ptr<TraceState> GetDefault()
- {
- static nostd::shared_ptr<TraceState> ts{new TraceState()};
- return ts;
- }
- /**
- * Returns shared_ptr to a newly created TraceState parsed from the header provided.
- * @param header Encoding of the tracestate header defined by
- * the W3C Trace Context specification https://www.w3.org/TR/trace-context/
- * @return TraceState A new TraceState instance or DEFAULT
- */
- static nostd::shared_ptr<TraceState> FromHeader(nostd::string_view header) noexcept
- {
- common::KeyValueStringTokenizer kv_str_tokenizer(header);
- size_t cnt = kv_str_tokenizer.NumTokens(); // upper bound on number of kv pairs
- if (cnt > kMaxKeyValuePairs)
- {
- // trace state should be discarded if count exceeds
- return GetDefault();
- }
- nostd::shared_ptr<TraceState> ts(new TraceState(cnt));
- bool kv_valid;
- nostd::string_view key, value;
- while (kv_str_tokenizer.next(kv_valid, key, value) && ts->kv_properties_->Size() < cnt)
- {
- if (kv_valid == false)
- {
- return GetDefault();
- }
- if (!IsValidKey(key) || !IsValidValue(value))
- {
- // invalid header. return empty TraceState
- ts->kv_properties_.reset(new common::KeyValueProperties());
- break;
- }
- ts->kv_properties_->AddEntry(key, value);
- }
- return ts;
- }
- /**
- * Creates a w3c tracestate header from TraceState object
- */
- std::string ToHeader() const noexcept
- {
- std::string header_s;
- bool first = true;
- kv_properties_->GetAllEntries(
- [&header_s, &first](nostd::string_view key, nostd::string_view value) noexcept {
- if (!first)
- {
- header_s.append(1, kMembersSeparator);
- }
- else
- {
- first = false;
- }
- header_s.append(std::string(key.data(), key.size()));
- header_s.append(1, kKeyValueSeparator);
- header_s.append(std::string(value.data(), value.size()));
- return true;
- });
- return header_s;
- }
- /**
- * Returns `value` associated with `key` passed as argument
- * Returns empty string if key is invalid or not found
- */
- bool Get(nostd::string_view key, std::string &value) const noexcept
- {
- if (!IsValidKey(key))
- {
- return false;
- }
- return kv_properties_->GetValue(key, value);
- }
- /**
- * Returns shared_ptr of `new` TraceState object with following mutations applied to the existing
- * instance: Update Key value: The updated value must be moved to beginning of List Add : The new
- * key-value pair SHOULD be added to beginning of List
- *
- * If the provided key-value pair is invalid, or results in transtate that violates the
- * tracecontext specification, empty TraceState instance will be returned.
- *
- * If the existing object has maximum list members, it's copy is returned.
- */
- nostd::shared_ptr<TraceState> Set(const nostd::string_view &key,
- const nostd::string_view &value) noexcept
- {
- auto curr_size = kv_properties_->Size();
- if (!IsValidKey(key) || !IsValidValue(value))
- {
- // max size reached or invalid key/value. Returning empty TraceState
- return TraceState::GetDefault();
- }
- auto allocate_size = curr_size;
- if (curr_size < kMaxKeyValuePairs)
- {
- allocate_size += 1;
- }
- nostd::shared_ptr<TraceState> ts(new TraceState(allocate_size));
- if (curr_size < kMaxKeyValuePairs)
- {
- // add new field first
- ts->kv_properties_->AddEntry(key, value);
- }
- // add rest of the fields.
- kv_properties_->GetAllEntries([&ts](nostd::string_view key, nostd::string_view value) {
- ts->kv_properties_->AddEntry(key, value);
- return true;
- });
- return ts;
- }
- /**
- * Returns shared_ptr to a `new` TraceState object after removing the attribute with given key (
- * if present )
- * @returns empty TraceState object if key is invalid
- * @returns copy of original TraceState object if key is not present (??)
- */
- nostd::shared_ptr<TraceState> Delete(const nostd::string_view &key) noexcept
- {
- if (!IsValidKey(key))
- {
- return TraceState::GetDefault();
- }
- auto curr_size = kv_properties_->Size();
- auto allocate_size = curr_size;
- std::string unused;
- if (kv_properties_->GetValue(key, unused))
- {
- allocate_size -= 1;
- }
- nostd::shared_ptr<TraceState> ts(new TraceState(allocate_size));
- kv_properties_->GetAllEntries(
- [&ts, &key](nostd::string_view e_key, nostd::string_view e_value) {
- if (key != e_key)
- ts->kv_properties_->AddEntry(e_key, e_value);
- return true;
- });
- return ts;
- }
- // Returns true if there are no keys, false otherwise.
- bool Empty() const noexcept { return kv_properties_->Size() == 0; }
- // @return all key-values entris by repeatedly invoking the function reference passed as argument
- // for each entry
- bool GetAllEntries(
- nostd::function_ref<bool(nostd::string_view, nostd::string_view)> callback) const noexcept
- {
- return kv_properties_->GetAllEntries(callback);
- }
- /** Returns whether key is a valid key. See https://www.w3.org/TR/trace-context/#key
- * Identifiers MUST begin with a lowercase letter or a digit, and can only contain
- * lowercase letters (a-z), digits (0-9), underscores (_), dashes (-), asterisks (*),
- * and forward slashes (/).
- * For multi-tenant vendor scenarios, an at sign (@) can be used to prefix the vendor name.
- *
- */
- static bool IsValidKey(nostd::string_view key)
- {
- #if OPENTELEMETRY_HAVE_WORKING_REGEX
- return IsValidKeyRegEx(key);
- #else
- return IsValidKeyNonRegEx(key);
- #endif
- }
- /** Returns whether value is a valid value. See https://www.w3.org/TR/trace-context/#value
- * The value is an opaque string containing up to 256 printable ASCII (RFC0020)
- * characters ((i.e., the range 0x20 to 0x7E) except comma , and equal =)
- */
- static bool IsValidValue(nostd::string_view value)
- {
- #if OPENTELEMETRY_HAVE_WORKING_REGEX
- return IsValidValueRegEx(value);
- #else
- return IsValidValueNonRegEx(value);
- #endif
- }
- private:
- TraceState() : kv_properties_(new common::KeyValueProperties()) {}
- TraceState(size_t size) : kv_properties_(new common::KeyValueProperties(size)) {}
- static nostd::string_view TrimString(nostd::string_view str, size_t left, size_t right)
- {
- while (str[static_cast<std::size_t>(right)] == ' ' && left < right)
- {
- right--;
- }
- while (str[static_cast<std::size_t>(left)] == ' ' && left < right)
- {
- left++;
- }
- return str.substr(left, right - left + 1);
- }
- #if OPENTELEMETRY_HAVE_WORKING_REGEX
- static bool IsValidKeyRegEx(nostd::string_view key)
- {
- static std::regex reg_key("^[a-z0-9][a-z0-9*_\\-/]{0,255}$");
- static std::regex reg_key_multitenant(
- "^[a-z0-9][a-z0-9*_\\-/]{0,240}(@)[a-z0-9][a-z0-9*_\\-/]{0,13}$");
- std::string key_s(key.data(), key.size());
- if (std::regex_match(key_s, reg_key) || std::regex_match(key_s, reg_key_multitenant))
- {
- return true;
- }
- return false;
- }
- static bool IsValidValueRegEx(nostd::string_view value)
- {
- // Hex 0x20 to 0x2B, 0x2D to 0x3C, 0x3E to 0x7E
- static std::regex reg_value(
- "^[\\x20-\\x2B\\x2D-\\x3C\\x3E-\\x7E]{0,255}[\\x21-\\x2B\\x2D-\\x3C\\x3E-\\x7E]$");
- // Need to benchmark without regex, as a string object is created here.
- return std::regex_match(std::string(value.data(), value.size()), reg_value);
- }
- #else
- static bool IsValidKeyNonRegEx(nostd::string_view key)
- {
- if (key.empty() || key.size() > kKeyMaxSize || !IsLowerCaseAlphaOrDigit(key[0]))
- {
- return false;
- }
- int ats = 0;
- for (const char c : key)
- {
- if (!IsLowerCaseAlphaOrDigit(c) && c != '_' && c != '-' && c != '@' && c != '*' && c != '/')
- {
- return false;
- }
- if ((c == '@') && (++ats > 1))
- {
- return false;
- }
- }
- return true;
- }
- static bool IsValidValueNonRegEx(nostd::string_view value)
- {
- if (value.empty() || value.size() > kValueMaxSize)
- {
- return false;
- }
- for (const char c : value)
- {
- if (c < ' ' || c > '~' || c == ',' || c == '=')
- {
- return false;
- }
- }
- return true;
- }
- #endif
- static bool IsLowerCaseAlphaOrDigit(char c) { return isdigit(c) || islower(c); }
- private:
- // Store entries in a C-style array to avoid using std::array or std::vector.
- nostd::unique_ptr<common::KeyValueProperties> kv_properties_;
- };
- } // namespace trace
- OPENTELEMETRY_END_NAMESPACE
|