Skip to content

Commit

Permalink
Adding baggage implementation (#676)
Browse files Browse the repository at this point in the history
  • Loading branch information
nikhil1511 authored Apr 21, 2021
1 parent 2e7594e commit c8c697f
Show file tree
Hide file tree
Showing 8 changed files with 592 additions and 8 deletions.
304 changes: 304 additions & 0 deletions api/include/opentelemetry/baggage/baggage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
// Copyright 2021, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include "opentelemetry/common/kv_properties.h"
#include "opentelemetry/nostd/shared_ptr.h"
#include "opentelemetry/nostd/string_view.h"
#include "opentelemetry/version.h"

OPENTELEMETRY_BEGIN_NAMESPACE

namespace baggage
{

class Baggage
{
public:
static constexpr size_t kMaxKeyValuePairs = 180;
static constexpr size_t kMaxKeyValueSize = 4096;
static constexpr size_t kMaxSize = 8192;
static constexpr char kKeyValueSeparator = '=';
static constexpr char kMembersSeparator = ',';
static constexpr char kMetadataSeparator = ';';

Baggage() : kv_properties_(new opentelemetry::common::KeyValueProperties()) {}
Baggage(size_t size) : kv_properties_(new opentelemetry::common::KeyValueProperties(size)){};

template <class T>
Baggage(const T &keys_and_values)
: kv_properties_(new opentelemetry::common::KeyValueProperties(keys_and_values))
{}

static nostd::shared_ptr<Baggage> GetDefault()
{
static nostd::shared_ptr<Baggage> baggage{new Baggage()};
return baggage;
}

/* Get value for key in the baggage
@returns true if key is found, false otherwise
*/
bool GetValue(nostd::string_view key, std::string &value) const
{
return kv_properties_->GetValue(key, value);
}

/* Returns shared_ptr of new baggage object which contains new key-value pair. If key or value is
invalid, copy of current baggage is returned
*/
nostd::shared_ptr<Baggage> Set(const nostd::string_view &key, const nostd::string_view &value)
{

nostd::shared_ptr<Baggage> baggage(new Baggage(kv_properties_->Size() + 1));
const bool valid_kv = IsValidKey(key) && IsValidValue(value);

if (valid_kv)
{
baggage->kv_properties_->AddEntry(key, value);
}

// add rest of the fields.
kv_properties_->GetAllEntries(
[&baggage, &key, &valid_kv](nostd::string_view e_key, nostd::string_view e_value) {
// if key or value was not valid, add all the entries. Add only remaining entries
// otherwise.
if (!valid_kv || key != e_key)
{
baggage->kv_properties_->AddEntry(e_key, e_value);
}

return true;
});

return baggage;
}

// @return all key-values entries 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);
}

// delete key from the baggage if it exists. Returns shared_ptr of new baggage object.
// if key does not exist, copy of current baggage is returned.
// Validity of key is not checked as invalid keys should never be populated in baggage in the
// first place.
nostd::shared_ptr<Baggage> Delete(nostd::string_view key)
{
// keeping size of baggage same as key might not be found in it
nostd::shared_ptr<Baggage> baggage(new Baggage(kv_properties_->Size()));
kv_properties_->GetAllEntries(
[&baggage, &key](nostd::string_view e_key, nostd::string_view e_value) {
if (key != e_key)
baggage->kv_properties_->AddEntry(e_key, e_value);
return true;
});
return baggage;
}

// Returns shared_ptr of baggage after extracting key-value pairs from header
static nostd::shared_ptr<Baggage> FromHeader(nostd::string_view header)
{
if (header.size() > kMaxSize)
{
// header size exceeds maximum threshold, return empty baggage
return GetDefault();
}

common::KeyValueStringTokenizer kv_str_tokenizer(header);
size_t cnt = kv_str_tokenizer.NumTokens(); // upper bound on number of kv pairs
if (cnt > kMaxKeyValuePairs)
{
cnt = kMaxKeyValuePairs;
}

nostd::shared_ptr<Baggage> baggage(new Baggage(cnt));
bool kv_valid;
nostd::string_view key, value;

while (kv_str_tokenizer.next(kv_valid, key, value) && baggage->kv_properties_->Size() < cnt)
{
if (!kv_valid || (key.size() + value.size() > kMaxKeyValueSize))
{
// if kv pair is not valid, skip it
continue;
}

// NOTE : metadata is kept as part of value only as it does not have any semantic meaning.
// but, we need to extract it (else Decode on value will return error)
nostd::string_view metadata;
auto metadata_separator = value.find(kMetadataSeparator);
if (metadata_separator != std::string::npos)
{
metadata = value.substr(metadata_separator);
value = value.substr(0, metadata_separator);
}

bool err = 0;
auto key_str = UrlDecode(common::StringUtil::Trim(key), err);
auto value_str = UrlDecode(common::StringUtil::Trim(value), err);

if (err == false && IsValidKey(key_str) && IsValidValue(value_str))
{
if (!metadata.empty())
{
value_str.append(metadata.data(), metadata.size());
}
baggage->kv_properties_->AddEntry(key_str, value_str);
}
}

return baggage;
}

// Creates string from baggage object.
std::string ToHeader() const
{
std::string header_s;
bool first = true;
kv_properties_->GetAllEntries([&](nostd::string_view key, nostd::string_view value) {
if (!first)
{
header_s.push_back(kMembersSeparator);
}
else
{
first = false;
}
header_s.append(UrlEncode(key));
header_s.push_back(kKeyValueSeparator);

// extracting metadata from value. We do not encode metadata
auto metadata_separator = value.find(kMetadataSeparator);
if (metadata_separator != std::string::npos)
{
header_s.append(UrlEncode(value.substr(0, metadata_separator)));
auto metadata = value.substr(metadata_separator);
header_s.append(std::string(metadata.data(), metadata.size()));
}
else
{
header_s.append(UrlEncode(value));
}
return true;
});
return header_s;
}

private:
static bool IsPrintableString(nostd::string_view str)
{
for (const auto ch : str)
{
if (ch < ' ' || ch > '~')
{
return false;
}
}

return true;
}

static bool IsValidKey(nostd::string_view key) { return key.size() && IsPrintableString(key); }

static bool IsValidValue(nostd::string_view value) { return IsPrintableString(value); }

// Uri encode key value pairs before injecting into header
// Implementation inspired from : https://golang.org/src/net/url/url.go?s=7851:7884#L264
static std::string UrlEncode(nostd::string_view str)
{
auto to_hex = [](char c) -> char {
static const char *hex = "0123456789ABCDEF";
return hex[c & 15];
};

std::string ret;

for (auto c : str)
{
if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
{
ret.push_back(c);
}
else if (c == ' ')
{
ret.push_back('+');
}
else
{
ret.push_back('%');
ret.push_back(to_hex(c >> 4));
ret.push_back(to_hex(c & 15));
}
}

return ret;
}

// Uri decode key value pairs after extracting from header
static std::string UrlDecode(nostd::string_view str, bool &err)
{
auto IsHex = [](char c) {
return std::isdigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
};

auto from_hex = [](char c) -> char {
return std::isdigit(c) ? c - '0' : std::toupper(c) - 'A' + 10;
};

std::string ret;

for (size_t i = 0; i < str.size(); i++)
{
if (str[i] == '%')
{
if (i + 2 >= str.size() || !IsHex(str[i + 1]) || !IsHex(str[i + 2]))
{
err = 1;
return "";
}
ret.push_back(from_hex(str[i + 1]) << 4 | from_hex(str[i + 2]));
i += 2;
}
else if (str[i] == '+')
{
ret.push_back(' ');
}
else if (std::isalnum(str[i]) || str[i] == '-' || str[i] == '_' || str[i] == '.' ||
str[i] == '~')
{
ret.push_back(str[i]);
}
else
{
err = 1;
return "";
}
}

return ret;
}

private:
// Store entries in a C-style array to avoid using std::array or std::vector.
nostd::unique_ptr<opentelemetry::common::KeyValueProperties> kv_properties_;
};

} // namespace baggage

OPENTELEMETRY_END_NAMESPACE
25 changes: 18 additions & 7 deletions api/include/opentelemetry/common/kv_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ class KeyValueStringTokenizer
: str_(str), opts_(opts), index_(0)
{}

static nostd::string_view GetDefaultKeyOrValue()
{
static std::string default_str = "";
return default_str;
}

// Returns next key value in the string header
// @param valid_kv : if the found kv pair is valid or not
// @param key : key in kv pair
Expand All @@ -57,29 +63,34 @@ class KeyValueStringTokenizer
valid_kv = true;
while (index_ < str_.size())
{
size_t end = str_.find(opts_.member_separator, index_);
bool is_empty_pair = false;
size_t end = str_.find(opts_.member_separator, index_);
if (end == std::string::npos)
{
end = str_.size() - 1;
}
else if (end == index_) // empty pair. do not update end
{
is_empty_pair = true;
}
else
{
end--;
}

auto list_member = StringUtil::Trim(str_, index_, end);
if (list_member.size() == 0)
if (list_member.size() == 0 || is_empty_pair)
{
// empty list member
index_ = end + 2;
index_ = end + 2 - is_empty_pair;
if (opts_.ignore_empty_members)
{
continue;
}

valid_kv = true;
key = "";
value = "";
key = GetDefaultKeyOrValue();
value = GetDefaultKeyOrValue();
return true;
}

Expand Down Expand Up @@ -226,7 +237,7 @@ class KeyValueProperties
}

// Adds new kv pair into kv properties
void AddEntry(const nostd::string_view &key, const nostd::string_view &value)
void AddEntry(nostd::string_view key, nostd::string_view value)
{
if (num_entries_ < max_num_entries_)
{
Expand All @@ -251,7 +262,7 @@ class KeyValueProperties
}

// Return value for key if exists, return false otherwise
bool GetValue(const nostd::string_view key, std::string &value) const
bool GetValue(nostd::string_view key, std::string &value) const
{
for (size_t i = 0; i < num_entries_; i++)
{
Expand Down
10 changes: 10 additions & 0 deletions api/include/opentelemetry/common/string_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ class StringUtil
}
return str.substr(left, 1 + right - left);
}

static nostd::string_view Trim(nostd::string_view str)
{
if (str.empty())
{
return str;
}

return Trim(str, 0, str.size() - 1);
}
};

} // namespace common
Expand Down
1 change: 1 addition & 0 deletions api/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ add_subdirectory(trace)
add_subdirectory(metrics)
add_subdirectory(logs)
add_subdirectory(common)
add_subdirectory(baggage)
Loading

0 comments on commit c8c697f

Please sign in to comment.