Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encode push labels #675

Merged
merged 2 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/include/prometheus/labels.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ namespace prometheus {
/// \brief Multiple labels and their value.
using Labels = std::map<std::string, std::string>;

/// \brief Single label and its value.
using Label = Labels::value_type;

} // namespace prometheus
14 changes: 14 additions & 0 deletions push/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ cc_library(
visibility = ["//visibility:public"],
deps = [
"//core",
"//util",
"@com_github_curl//:curl",
],
)

cc_library(
name = "push_internal_headers",
hdrs = glob(
["src/detail/*.h"],
),
strip_include_prefix = "src",
visibility = ["//push/tests:__subpackages__"],
deps = [
"//core",
"//push",
],
)
13 changes: 11 additions & 2 deletions push/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
find_package(CURL REQUIRED)

add_library(push
src/curl_wrapper.cc
src/curl_wrapper.h
src/gateway.cc

src/detail/curl_wrapper.cc
src/detail/curl_wrapper.h
src/detail/label_encoder.cc
src/detail/label_encoder.h
)

add_library(${PROJECT_NAME}::push ALIAS push)
Expand All @@ -18,6 +21,7 @@ target_link_libraries(push
PUBLIC
${PROJECT_NAME}::core
PRIVATE
${PROJECT_NAME}::util
Threads::Threads
CURL::libcurl
$<$<AND:$<BOOL:UNIX>,$<NOT:$<BOOL:APPLE>>>:rt>
Expand Down Expand Up @@ -78,5 +82,10 @@ if(GENERATE_PKGCONFIG)
endif()

if(ENABLE_TESTING)
add_library(push_internal_headers INTERFACE)
add_library(${PROJECT_NAME}::push_internal_headers ALIAS push_internal_headers)
target_include_directories(push_internal_headers INTERFACE src)
target_link_libraries(push_internal_headers INTERFACE ${PROJECT_NAME}::push)

add_subdirectory(tests)
endif()
File renamed without changes.
File renamed without changes.
42 changes: 42 additions & 0 deletions push/src/detail/label_encoder.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include "label_encoder.h"

#include <algorithm>
#include <ostream>

#include "prometheus/detail/base64.h"

namespace prometheus {
namespace detail {

namespace {
// Does this character need encoding like in RFC 3986 section 2.3?
bool needsEncoding(char c) {
if (c >= 'a' && c <= 'z') {
return false;
}
if (c >= 'A' && c <= 'Z') {
return false;
}
if (c >= '0' && c <= '9') {
return false;
}
if (c == '-' || c == '.' || c == '_' || c == '~') {
return false;
}
return true;
}
} // namespace

void encodeLabel(std::ostream& os, const Label& label) {
if (label.second.empty()) {
os << "/" << label.first << "@base64/=";
} else if (std::any_of(label.second.begin(), label.second.end(),
needsEncoding)) {
os << "/" << label.first << "@base64/"
<< detail::base64url_encode(label.second);
} else {
os << "/" << label.first << "/" << label.second;
}
}
} // namespace detail
} // namespace prometheus
13 changes: 13 additions & 0 deletions push/src/detail/label_encoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include <iosfwd>

#include "prometheus/labels.h"

namespace prometheus {
namespace detail {

void encodeLabel(std::ostream& os, const Label& label);

}
} // namespace prometheus
8 changes: 5 additions & 3 deletions push/src/gateway.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
#include <mutex>
#include <sstream>

#include "curl_wrapper.h"
#include "detail/curl_wrapper.h"
#include "detail/label_encoder.h"
#include "prometheus/detail/future_std.h"
#include "prometheus/metric_family.h" // IWYU pragma: keep
#include "prometheus/text_serializer.h"
Expand All @@ -24,12 +25,13 @@ Gateway::Gateway(const std::string& host, const std::string& port,
curlWrapper_ = detail::make_unique<detail::CurlWrapper>(username, password);

std::stringstream jobUriStream;
jobUriStream << host << ':' << port << "/metrics/job/" << jobname;
jobUriStream << host << ':' << port << "/metrics";
detail::encodeLabel(jobUriStream, {"job", jobname});
jobUri_ = jobUriStream.str();

std::stringstream labelStream;
for (auto& label : labels) {
labelStream << "/" << label.first << "/" << label.second;
detail::encodeLabel(labelStream, label);
}
labels_ = labelStream.str();
}
Expand Down
1 change: 1 addition & 0 deletions push/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
add_subdirectory(integration)
add_subdirectory(internal)
13 changes: 13 additions & 0 deletions push/tests/internal/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
cc_test(
name = "internal",
srcs = glob([
"*.cc",
"*.h",
]),
copts = ["-Iexternal/googletest/include"],
linkstatic = True,
deps = [
"//push:push_internal_headers",
"@com_google_googletest//:gtest_main",
],
)
14 changes: 14 additions & 0 deletions push/tests/internal/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
add_executable(prometheus_push_internal_test
label_encoder_test.cc
)

target_link_libraries(prometheus_push_internal_test
PRIVATE
${PROJECT_NAME}::push_internal_headers
GTest::gmock_main
)

add_test(
NAME prometheus_push_internal_test
COMMAND prometheus_push_internal_test
)
43 changes: 43 additions & 0 deletions push/tests/internal/label_encoder_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include "detail/label_encoder.h"

#include <gtest/gtest.h>

#include <string>
#include <sstream>

namespace prometheus {
namespace {

class LabelEncoderTest : public testing::Test {
protected:
std::string Encode(const Label& label) {
std::stringstream ss;
detail::encodeLabel(ss, label);
return ss.str();
}
};

// test cases taken from https://github.com/prometheus/pushgateway#url

TEST_F(LabelEncoderTest, regular) {
EXPECT_EQ("/foo/bar", Encode(Label{"foo", "bar"}));
}

TEST_F(LabelEncoderTest, empty) {
EXPECT_EQ("/first_label@base64/=", Encode(Label{"first_label", ""}));
}

TEST_F(LabelEncoderTest, path) {
EXPECT_EQ("/path@base64/L3Zhci90bXA=", Encode(Label{"path", "/var/tmp"}));
}

TEST_F(LabelEncoderTest, unicode) {
const char unicodeText[] =
"\xce\xa0\xcf\x81\xce\xbf\xce\xbc\xce\xb7\xce\xb8\xce\xb5\xcf\x8d\xcf"
"\x82"; // Προμηθεύς
EXPECT_EQ("/name@base64/zqDPgc6_zrzOt864zrXPjc-C",
Encode(Label{"name", unicodeText}));
}

} // namespace
} // namespace prometheus
26 changes: 19 additions & 7 deletions util/include/prometheus/detail/base64.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
Expand Down Expand Up @@ -37,9 +38,9 @@ inline std::string base64_encode(const std::string& input) {
auto it = input.begin();

for (std::size_t i = 0; i < input.size() / 3; ++i) {
temp = (*it++) << 16;
temp += (*it++) << 8;
temp += (*it++);
temp = static_cast<std::uint8_t>(*it++) << 16;
temp += static_cast<std::uint8_t>(*it++) << 8;
temp += static_cast<std::uint8_t>(*it++);
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6]);
Expand All @@ -48,14 +49,14 @@ inline std::string base64_encode(const std::string& input) {

switch (input.size() % 3) {
case 1:
temp = (*it++) << 16;
temp = static_cast<std::uint8_t>(*it++) << 16;
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
encoded.append(2, kPadCharacter);
break;
case 2:
temp = (*it++) << 16;
temp += (*it++) << 8;
temp = static_cast<std::uint8_t>(*it++) << 16;
temp += static_cast<std::uint8_t>(*it++) << 8;
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6]);
Expand All @@ -66,6 +67,17 @@ inline std::string base64_encode(const std::string& input) {
return encoded;
}

// https://tools.ietf.org/html/rfc4648#section-5
inline std::string base64url_encode(const std::string& input) {
std::string s = base64_encode(input);
std::transform(begin(s), end(s), begin(s), [](char c) {
if (c == '+') return '-';
if (c == '/') return '_';
return c;
});
return s;
}

inline std::string base64_decode(const std::string& input) {
if (input.length() % 4) {
throw std::runtime_error("Invalid base64 length!");
Expand Down Expand Up @@ -118,7 +130,7 @@ inline std::string base64_decode(const std::string& input) {

decoded.push_back((temp >> 16) & 0x000000FF);
decoded.push_back((temp >> 8) & 0x000000FF);
decoded.push_back((temp)&0x000000FF);
decoded.push_back((temp) & 0x000000FF);
}

return decoded;
Expand Down
24 changes: 24 additions & 0 deletions util/tests/unit/base64_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,29 @@ struct TestVector {
};

const TestVector testVector[] = {
// RFC 3548 examples
{"\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l+"},
{"\x14\xfb\x9c\x03\xd9", "FPucA9k="},
{"\x14\xfb\x9c\x03", "FPucAw=="},

// RFC 4648 examples
{"", ""},
{"f", "Zg=="},
{"fo", "Zm8="},
{"foo", "Zm9v"},
{"foob", "Zm9vYg=="},
{"fooba", "Zm9vYmE="},
{"foobar", "Zm9vYmFy"},

// Wikipedia examples
{"sure.", "c3VyZS4="},
{"sure", "c3VyZQ=="},
{"sur", "c3Vy"},
{"su", "c3U="},
{"leasure.", "bGVhc3VyZS4="},
{"easure.", "ZWFzdXJlLg=="},
{"asure.", "YXN1cmUu"},
{"sure.", "c3VyZS4="},
};

using namespace testing;
Expand All @@ -31,6 +47,14 @@ TEST(Base64Test, encodeTest) {
}
}

TEST(Base64Test, encodeUrlTest) {
const char unicodeText[] =
"\xce\xa0\xcf\x81\xce\xbf\xce\xbc\xce\xb7\xce\xb8\xce\xb5\xcf\x8d\xcf"
"\x82"; // Προμηθεύς
std::string encoded = detail::base64url_encode(unicodeText);
EXPECT_EQ("zqDPgc6_zrzOt864zrXPjc-C", encoded);
}

TEST(Base64Test, decodeTest) {
for (const auto& test_case : testVector) {
std::string decoded = detail::base64_decode(test_case.encoded);
Expand Down