Skip to content

Commit 35fb2ae

Browse files
committed
feat: add internal BigSegmentStoreWrapper with caching and status polling
1 parent 3c88c2a commit 35fb2ae

17 files changed

Lines changed: 1114 additions & 18 deletions

File tree

libs/internal/include/launchdarkly/async/promise.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <memory>
88
#include <mutex>
99
#include <optional>
10+
#include <variant>
1011
#include <vector>
1112

1213
namespace launchdarkly::async {

libs/internal/include/launchdarkly/encoding/base_64.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,13 @@ namespace launchdarkly::encoding {
1313
*/
1414
std::string Base64UrlEncode(std::string const& input);
1515

16+
/**
17+
* Return a standard base64 encoded version of the input string, using the
18+
* RFC 4648 section 4 alphabet (with '+' and '/'). Unlike @ref Base64UrlEncode,
19+
* this is NOT URL-safe.
20+
* @param input The string to Base64 encode.
21+
* @return The encoded value.
22+
*/
23+
std::string Base64Encode(std::string const& input);
24+
1625
} // namespace launchdarkly::encoding

libs/internal/src/encoding/base_64.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
#include <launchdarkly/encoding/base_64.hpp>
22

3+
#include <openssl/evp.h>
4+
35
#include <algorithm>
46
#include <array>
57
#include <bitset>
68
#include <climits>
79
#include <cstddef>
10+
#include <vector>
811

912
static unsigned char const kEncodeSize = 4;
1013
static unsigned char const kInputBytesPerEncodeSize = 3;
@@ -75,4 +78,17 @@ std::string Base64UrlEncode(std::string const& input) {
7578
return out;
7679
}
7780

81+
std::string Base64Encode(std::string const& input) {
82+
if (input.empty()) {
83+
return {};
84+
}
85+
// EVP_EncodeBlock writes 4 output characters per 3 input bytes (rounded up)
86+
// plus a NUL terminator.
87+
std::vector<unsigned char> out(4 * ((input.size() + 2) / 3) + 1);
88+
int const written = EVP_EncodeBlock(
89+
out.data(), reinterpret_cast<unsigned char const*>(input.data()),
90+
static_cast<int>(input.size()));
91+
return std::string(reinterpret_cast<char const*>(out.data()), written);
92+
}
93+
7894
} // namespace launchdarkly::encoding

libs/internal/tests/base_64_test.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "launchdarkly/encoding/base_64.hpp"
44

5+
using launchdarkly::encoding::Base64Encode;
56
using launchdarkly::encoding::Base64UrlEncode;
67

78
TEST(Base64Encoding, CanEncodeString) {
@@ -15,3 +16,24 @@ TEST(Base64Encoding, CanEncodeString) {
1516
EXPECT_EQ("Zm9vYmE=", Base64UrlEncode("fooba"));
1617
EXPECT_EQ("Zm9vYmFy", Base64UrlEncode("foobar"));
1718
}
19+
20+
TEST(Base64Encoding, StandardCanEncodeString) {
21+
// Test vectors from RFC4668
22+
// https://datatracker.ietf.org/doc/html/rfc4648#section-10
23+
EXPECT_EQ("", Base64Encode(""));
24+
EXPECT_EQ("Zg==", Base64Encode("f"));
25+
EXPECT_EQ("Zm8=", Base64Encode("fo"));
26+
EXPECT_EQ("Zm9v", Base64Encode("foo"));
27+
EXPECT_EQ("Zm9vYg==", Base64Encode("foob"));
28+
EXPECT_EQ("Zm9vYmE=", Base64Encode("fooba"));
29+
EXPECT_EQ("Zm9vYmFy", Base64Encode("foobar"));
30+
}
31+
32+
TEST(Base64Encoding, StandardUsesNonUrlSafeAlphabet) {
33+
// "???" encodes to a value ending in '/' under the standard alphabet and
34+
// '_' under the URL-safe one; ">>>" exercises '+' versus '-'.
35+
EXPECT_EQ("Pz8/", Base64Encode("???"));
36+
EXPECT_EQ("Pz8_", Base64UrlEncode("???"));
37+
EXPECT_EQ("Pj4+", Base64Encode(">>>"));
38+
EXPECT_EQ("Pj4-", Base64UrlEncode(">>>"));
39+
}

libs/server-sdk-dynamodb-source/src/dynamodb_big_segment_store.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,9 @@ IBigSegmentStore::GetMembershipResult DynamoDBBigSegmentStore::GetMembership(
8989
it != item.end()) {
9090
if (it->second.GetType() !=
9191
Aws::DynamoDB::Model::ValueType::STRING_SET) {
92-
return tl::make_unexpected(
93-
std::string("DynamoDB Big Segments '") +
94-
kBigSegmentsIncludedAttribute +
95-
"' is not of type STRING_SET");
92+
return tl::make_unexpected(std::string("DynamoDB Big Segments '") +
93+
kBigSegmentsIncludedAttribute +
94+
"' is not of type STRING_SET");
9695
}
9796
for (auto const& ref : it->second.GetSS()) {
9897
included.emplace_back(ref);
@@ -102,10 +101,9 @@ IBigSegmentStore::GetMembershipResult DynamoDBBigSegmentStore::GetMembership(
102101
it != item.end()) {
103102
if (it->second.GetType() !=
104103
Aws::DynamoDB::Model::ValueType::STRING_SET) {
105-
return tl::make_unexpected(
106-
std::string("DynamoDB Big Segments '") +
107-
kBigSegmentsExcludedAttribute +
108-
"' is not of type STRING_SET");
104+
return tl::make_unexpected(std::string("DynamoDB Big Segments '") +
105+
kBigSegmentsExcludedAttribute +
106+
"' is not of type STRING_SET");
109107
}
110108
for (auto const& ref : it->second.GetSS()) {
111109
excluded.emplace_back(ref);
@@ -157,7 +155,9 @@ IBigSegmentStore::GetMetadataResult DynamoDBBigSegmentStore::GetMetadata()
157155
"DynamoDB Big Segments 'synchronizedOn' is not a valid integer");
158156
}
159157

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

163163
} // namespace launchdarkly::server_side::integrations

libs/server-sdk-dynamodb-source/tests/dynamodb_big_segment_store_test.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ TEST_F(DynamoDBBigSegmentTests, GetMetadataWithEmptyPrefix) {
163163
ASSERT_TRUE(result);
164164
ASSERT_TRUE(result->has_value());
165165
ASSERT_EQ(result->value().last_up_to_date,
166-
std::chrono::milliseconds{1700000000000LL});
166+
std::chrono::system_clock::time_point{
167+
std::chrono::milliseconds{1700000000000LL}});
167168
}
168169

169170
TEST_F(DynamoDBBigSegmentTests, GetMembershipRejectsMalformedIncluded) {
@@ -182,7 +183,8 @@ TEST_F(DynamoDBBigSegmentTests, GetMetadataReturnsSyncTime) {
182183
ASSERT_TRUE(result);
183184
ASSERT_TRUE(result->has_value());
184185
ASSERT_EQ(result->value().last_up_to_date,
185-
std::chrono::milliseconds{1700000000000LL});
186+
std::chrono::system_clock::time_point{
187+
std::chrono::milliseconds{1700000000000LL}});
186188
}
187189

188190
TEST_F(DynamoDBBigSegmentTests, GetMetadataAbsentSyncTimeReturnsNoMetadata) {

libs/server-sdk-redis-source/src/redis_big_segment_store.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ IBigSegmentStore::GetMetadataResult RedisBigSegmentStore::GetMetadata() const {
9898
"Redis Big Segments synchronized_on is not a valid integer");
9999
}
100100

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

104106
} // namespace launchdarkly::server_side::integrations

libs/server-sdk-redis-source/tests/redis_big_segment_store_test.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ TEST_F(RedisBigSegmentTests, GetMetadataWithEmptyPrefix) {
143143
ASSERT_TRUE(result);
144144
ASSERT_TRUE(result->has_value());
145145
ASSERT_EQ(result->value().last_up_to_date,
146-
std::chrono::milliseconds{1700000000000LL});
146+
std::chrono::system_clock::time_point{
147+
std::chrono::milliseconds{1700000000000LL}});
147148
}
148149

149150
TEST_F(RedisBigSegmentTests, GetMetadataReturnsSyncTime) {
@@ -153,7 +154,8 @@ TEST_F(RedisBigSegmentTests, GetMetadataReturnsSyncTime) {
153154
ASSERT_TRUE(result);
154155
ASSERT_TRUE(result->has_value());
155156
ASSERT_EQ(result->value().last_up_to_date,
156-
std::chrono::milliseconds{1700000000000LL});
157+
std::chrono::system_clock::time_point{
158+
std::chrono::milliseconds{1700000000000LL}});
157159
}
158160

159161
TEST_F(RedisBigSegmentTests, GetMetadataRejectsMalformedSyncTime) {

libs/server-sdk/include/launchdarkly/server_side/integrations/big_segments/big_segment_store_types.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,11 @@ class Membership {
8787
*/
8888
struct StoreMetadata {
8989
/**
90-
* @brief Wall-clock timestamp (Unix epoch) at which the data populator
91-
* (e.g. the LaunchDarkly Relay Proxy) last confirmed it had pushed all
92-
* pending Big Segments updates to the store.
90+
* @brief Wall-clock instant at which the data populator (e.g. the
91+
* LaunchDarkly Relay Proxy) last confirmed it had pushed all pending Big
92+
* Segments updates to the store.
9393
*/
94-
std::chrono::milliseconds last_up_to_date;
94+
std::chrono::system_clock::time_point last_up_to_date;
9595
};
9696

9797
} // namespace launchdarkly::server_side::integrations

libs/server-sdk/src/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ target_sources(${LIBNAME}
4444
data_components/dependency_tracker/dependency_tracker.cpp
4545
data_components/expiration_tracker/expiration_tracker.hpp
4646
data_components/expiration_tracker/expiration_tracker.cpp
47+
data_components/big_segments/big_segments_status.hpp
48+
data_components/big_segments/membership_cache.hpp
49+
data_components/big_segments/membership_cache.cpp
50+
data_components/big_segments/big_segment_store_wrapper.hpp
51+
data_components/big_segments/big_segment_store_wrapper.cpp
4752
data_interfaces/destination/itransactional_destination.hpp
4853
data_components/memory_store/memory_store.hpp
4954
data_components/memory_store/memory_store.cpp

0 commit comments

Comments
 (0)