Skip to content
Open
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
1 change: 1 addition & 0 deletions libs/internal/include/launchdarkly/async/promise.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <memory>
#include <mutex>
#include <optional>
#include <variant>
#include <vector>

namespace launchdarkly::async {
Expand Down
9 changes: 9 additions & 0 deletions libs/internal/include/launchdarkly/encoding/base_64.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,13 @@ namespace launchdarkly::encoding {
*/
std::string Base64UrlEncode(std::string const& input);

/**
* Return a standard base64 encoded version of the input string, using the
* RFC 4648 section 4 alphabet (with '+' and '/'). Unlike @ref Base64UrlEncode,
* this is NOT URL-safe.
* @param input The string to Base64 encode.
* @return The encoded value.
*/
std::string Base64Encode(std::string const& input);

} // namespace launchdarkly::encoding
16 changes: 16 additions & 0 deletions libs/internal/src/encoding/base_64.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#include <launchdarkly/encoding/base_64.hpp>

#include <openssl/evp.h>

#include <algorithm>
#include <array>
#include <bitset>
#include <climits>
#include <cstddef>
#include <vector>

static unsigned char const kEncodeSize = 4;
static unsigned char const kInputBytesPerEncodeSize = 3;
Expand Down Expand Up @@ -75,4 +78,17 @@ std::string Base64UrlEncode(std::string const& input) {
return out;
}

std::string Base64Encode(std::string const& input) {
if (input.empty()) {
return {};
}
// EVP_EncodeBlock writes 4 output characters per 3 input bytes (rounded up)
// plus a NUL terminator.
std::vector<unsigned char> out(4 * ((input.size() + 2) / 3) + 1);
int const written = EVP_EncodeBlock(
out.data(), reinterpret_cast<unsigned char const*>(input.data()),
static_cast<int>(input.size()));
return std::string(reinterpret_cast<char const*>(out.data()), written);
}

} // namespace launchdarkly::encoding
24 changes: 23 additions & 1 deletion libs/internal/tests/base_64_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

#include "launchdarkly/encoding/base_64.hpp"

using launchdarkly::encoding::Base64Encode;
using launchdarkly::encoding::Base64UrlEncode;

TEST(Base64Encoding, CanEncodeString) {
// Test vectors from RFC4668
// Test vectors from RFC4648
// https://datatracker.ietf.org/doc/html/rfc4648#section-10
EXPECT_EQ("", Base64UrlEncode(""));
EXPECT_EQ("Zg==", Base64UrlEncode("f"));
Expand All @@ -15,3 +16,24 @@ TEST(Base64Encoding, CanEncodeString) {
EXPECT_EQ("Zm9vYmE=", Base64UrlEncode("fooba"));
EXPECT_EQ("Zm9vYmFy", Base64UrlEncode("foobar"));
}

TEST(Base64Encoding, StandardCanEncodeString) {
// Test vectors from RFC4648
// https://datatracker.ietf.org/doc/html/rfc4648#section-10
EXPECT_EQ("", Base64Encode(""));
EXPECT_EQ("Zg==", Base64Encode("f"));
EXPECT_EQ("Zm8=", Base64Encode("fo"));
EXPECT_EQ("Zm9v", Base64Encode("foo"));
EXPECT_EQ("Zm9vYg==", Base64Encode("foob"));
EXPECT_EQ("Zm9vYmE=", Base64Encode("fooba"));
EXPECT_EQ("Zm9vYmFy", Base64Encode("foobar"));
}

TEST(Base64Encoding, StandardUsesNonUrlSafeAlphabet) {
// "???" encodes to a value ending in '/' under the standard alphabet and
// '_' under the URL-safe one; ">>>" exercises '+' versus '-'.
EXPECT_EQ("Pz8/", Base64Encode("???"));
EXPECT_EQ("Pz8_", Base64UrlEncode("???"));
EXPECT_EQ("Pj4+", Base64Encode(">>>"));
EXPECT_EQ("Pj4-", Base64UrlEncode(">>>"));
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,9 @@ IBigSegmentStore::GetMembershipResult DynamoDBBigSegmentStore::GetMembership(
it != item.end()) {
if (it->second.GetType() !=
Aws::DynamoDB::Model::ValueType::STRING_SET) {
return tl::make_unexpected(
std::string("DynamoDB Big Segments '") +
kBigSegmentsIncludedAttribute +
"' is not of type STRING_SET");
return tl::make_unexpected(std::string("DynamoDB Big Segments '") +
kBigSegmentsIncludedAttribute +
"' is not of type STRING_SET");
}
for (auto const& ref : it->second.GetSS()) {
included.emplace_back(ref);
Expand All @@ -102,10 +101,9 @@ IBigSegmentStore::GetMembershipResult DynamoDBBigSegmentStore::GetMembership(
it != item.end()) {
if (it->second.GetType() !=
Aws::DynamoDB::Model::ValueType::STRING_SET) {
return tl::make_unexpected(
std::string("DynamoDB Big Segments '") +
kBigSegmentsExcludedAttribute +
"' is not of type STRING_SET");
return tl::make_unexpected(std::string("DynamoDB Big Segments '") +
kBigSegmentsExcludedAttribute +
"' is not of type STRING_SET");
}
for (auto const& ref : it->second.GetSS()) {
excluded.emplace_back(ref);
Expand Down Expand Up @@ -157,7 +155,9 @@ IBigSegmentStore::GetMetadataResult DynamoDBBigSegmentStore::GetMetadata()
"DynamoDB Big Segments 'synchronizedOn' is not a valid integer");
}

return StoreMetadata{std::chrono::milliseconds{parsed}};
// The stored value is a Unix-epoch millisecond count: system_clock's epoch.
return StoreMetadata{std::chrono::system_clock::time_point{
std::chrono::milliseconds{parsed}}};
}

} // namespace launchdarkly::server_side::integrations
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ TEST_F(DynamoDBBigSegmentTests, GetMetadataWithEmptyPrefix) {
ASSERT_TRUE(result);
ASSERT_TRUE(result->has_value());
ASSERT_EQ(result->value().last_up_to_date,
std::chrono::milliseconds{1700000000000LL});
std::chrono::system_clock::time_point{
std::chrono::milliseconds{1700000000000LL}});
}

TEST_F(DynamoDBBigSegmentTests, GetMembershipRejectsMalformedIncluded) {
Expand All @@ -182,7 +183,8 @@ TEST_F(DynamoDBBigSegmentTests, GetMetadataReturnsSyncTime) {
ASSERT_TRUE(result);
ASSERT_TRUE(result->has_value());
ASSERT_EQ(result->value().last_up_to_date,
std::chrono::milliseconds{1700000000000LL});
std::chrono::system_clock::time_point{
std::chrono::milliseconds{1700000000000LL}});
}

TEST_F(DynamoDBBigSegmentTests, GetMetadataAbsentSyncTimeReturnsNoMetadata) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ IBigSegmentStore::GetMetadataResult RedisBigSegmentStore::GetMetadata() const {
"Redis Big Segments synchronized_on is not a valid integer");
}

return StoreMetadata{std::chrono::milliseconds{parsed}};
// The stored value is a Unix-epoch millisecond count: system_clock's epoch.
return StoreMetadata{std::chrono::system_clock::time_point{
std::chrono::milliseconds{parsed}}};
}

} // namespace launchdarkly::server_side::integrations
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ TEST_F(RedisBigSegmentTests, GetMetadataWithEmptyPrefix) {
ASSERT_TRUE(result);
ASSERT_TRUE(result->has_value());
ASSERT_EQ(result->value().last_up_to_date,
std::chrono::milliseconds{1700000000000LL});
std::chrono::system_clock::time_point{
std::chrono::milliseconds{1700000000000LL}});
}

TEST_F(RedisBigSegmentTests, GetMetadataReturnsSyncTime) {
Expand All @@ -153,7 +154,8 @@ TEST_F(RedisBigSegmentTests, GetMetadataReturnsSyncTime) {
ASSERT_TRUE(result);
ASSERT_TRUE(result->has_value());
ASSERT_EQ(result->value().last_up_to_date,
std::chrono::milliseconds{1700000000000LL});
std::chrono::system_clock::time_point{
std::chrono::milliseconds{1700000000000LL}});
}

TEST_F(RedisBigSegmentTests, GetMetadataRejectsMalformedSyncTime) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,11 @@ class Membership {
*/
struct StoreMetadata {
/**
* @brief Wall-clock timestamp (Unix epoch) at which the data populator
* (e.g. the LaunchDarkly Relay Proxy) last confirmed it had pushed all
* pending Big Segments updates to the store.
* @brief Wall-clock instant at which the data populator (e.g. the
* LaunchDarkly Relay Proxy) last confirmed it had pushed all pending Big
* Segments updates to the store.
*/
std::chrono::milliseconds last_up_to_date;
std::chrono::system_clock::time_point last_up_to_date;
};

} // namespace launchdarkly::server_side::integrations
5 changes: 5 additions & 0 deletions libs/server-sdk/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ target_sources(${LIBNAME}
data_components/dependency_tracker/dependency_tracker.cpp
data_components/expiration_tracker/expiration_tracker.hpp
data_components/expiration_tracker/expiration_tracker.cpp
data_components/big_segments/big_segments_status.hpp
data_components/big_segments/membership_cache.hpp
data_components/big_segments/membership_cache.cpp
data_components/big_segments/big_segment_store_wrapper.hpp
data_components/big_segments/big_segment_store_wrapper.cpp
data_interfaces/destination/itransactional_destination.hpp
data_components/memory_store/memory_store.hpp
data_components/memory_store/memory_store.cpp
Expand Down
Loading
Loading