Skip to content

Commit e63ad4d

Browse files
authored
fix: DH-21772: C++ Client: Fix Base64Encode (deephaven#7713)
It is incorrect on architectures where char is signed and the input bytes are >= 0x80
1 parent 59df336 commit e63ad4d

File tree

2 files changed

+33
-41
lines changed

2 files changed

+33
-41
lines changed

cpp-client/deephaven/dhcore/src/utility/utility.cc

Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -31,46 +31,22 @@ const char kEncodeLookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwx
3131
const char kPadCharacter = '=';
3232
} // namespace
3333

34-
// Adapted from
35-
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64#C++
36-
std::string Base64Encode(const std::string &input_buffer) {
37-
std::string encoded_string;
38-
encoded_string.reserve(((input_buffer.size() + 2) / 3) * 4);
39-
size_t i = 0;
40-
while (i + 2 < input_buffer.size()) {
41-
auto temp = static_cast<uint32_t>(input_buffer[i++]) << 16;
42-
temp |= static_cast<uint32_t>(input_buffer[i++]) << 8;
43-
temp |= static_cast<uint32_t>(input_buffer[i++]);
44-
encoded_string.push_back(kEncodeLookup[(temp & 0x00FC0000) >> 18]);
45-
encoded_string.push_back(kEncodeLookup[(temp & 0x0003F000) >> 12]);
46-
encoded_string.push_back(kEncodeLookup[(temp & 0x00000FC0) >> 6]);
47-
encoded_string.push_back(kEncodeLookup[(temp & 0x0000003F)]);
34+
std::string Base64Encode(const std::string& input) {
35+
std::string output;
36+
output.reserve(((input.size() + 2) / 3) * 4);
37+
38+
for (size_t i = 0; i < input.size(); i += 3) {
39+
uint32_t n = (static_cast<uint8_t>(input[i])) << 16;
40+
if (i + 1 < input.size()) n |= static_cast<uint8_t>(input[i + 1]) << 8;
41+
if (i + 2 < input.size()) n |= static_cast<uint8_t>(input[i + 2]);
42+
43+
output.push_back(kEncodeLookup[(n >> 18) & 0x3F]);
44+
output.push_back(kEncodeLookup[(n >> 12) & 0x3F]);
45+
output.push_back((i + 1 < input.size()) ? kEncodeLookup[(n >> 6) & 0x3F] : '=');
46+
output.push_back((i + 2 < input.size()) ? kEncodeLookup[n & 0x3F] : '=');
4847
}
4948

50-
if (i == input_buffer.size() - 1) {
51-
uint32_t temp = static_cast<uint32_t>(input_buffer[i++]) << 16;
52-
encoded_string.push_back(kEncodeLookup[(temp & 0x00FC0000) >> 18]);
53-
encoded_string.push_back(kEncodeLookup[(temp & 0x0003F000) >> 12]);
54-
encoded_string.push_back(kPadCharacter);
55-
encoded_string.push_back(kPadCharacter);
56-
} else if (i == input_buffer.size() - 2) {
57-
uint32_t temp = static_cast<uint32_t>(input_buffer[i++]) << 16;
58-
temp |= static_cast<uint32_t>(input_buffer[i++]) << 8;
59-
encoded_string.push_back(kEncodeLookup[(temp & 0x00FC0000) >> 18]);
60-
encoded_string.push_back(kEncodeLookup[(temp & 0x0003F000) >> 12]);
61-
encoded_string.push_back(kEncodeLookup[(temp & 0x00000FC0) >> 6]);
62-
encoded_string.push_back(kPadCharacter);
63-
}
64-
return encoded_string;
65-
}
66-
67-
void AssertLessEq(size_t lhs, size_t rhs, std::string_view context, std::string_view lhs_text,
68-
std::string_view rhs_text) {
69-
if (lhs <= rhs) {
70-
return;
71-
}
72-
auto message = fmt::format("{}: assertion failed: {} <= {} ({} <= {})",
73-
context, lhs, rhs, lhs_text, rhs_text);
49+
return output;
7450
}
7551

7652
SimpleOstringstream::SimpleOstringstream() : std::ostream(this), dest_(&internalBuffer_) {}

cpp-client/deephaven/tests/src/utility_test.cc

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,25 @@ using deephaven::dhcore::utility::VerboseSharedPtrCast;
2323
namespace deephaven::client::tests {
2424
TEST_CASE("Base64encode", "[utility]") {
2525
// https://en.wikipedia.org/wiki/Base64
26-
CHECK(Base64Encode("light work.") == "bGlnaHQgd29yay4=");
27-
CHECK(Base64Encode("light work") == "bGlnaHQgd29yaw==");
28-
CHECK(Base64Encode("light wor") == "bGlnaHQgd29y");
26+
const auto [text, expected] = GENERATE(
27+
std::pair("", ""),
28+
std::pair("X", "WA=="),
29+
std::pair("XX", "WFg="),
30+
std::pair("XXX", "WFhY"),
31+
std::pair("light wor", "bGlnaHQgd29y"),
32+
std::pair("light work", "bGlnaHQgd29yaw=="),
33+
std::pair("light work.", "bGlnaHQgd29yay4="),
34+
std::pair("\x80", "gA=="),
35+
std::pair("\x80\x81", "gIE="),
36+
std::pair("\x80\x81\x82", "gIGC"),
37+
std::pair("\x80\x81\x82\xfd", "gIGC/Q=="),
38+
std::pair("\x80\x81\x82\xfd\xfe", "gIGC/f4="),
39+
std::pair("\x80\x81\x82\xfd\xfe\xff", "gIGC/f7/")
40+
);
41+
42+
auto actual = Base64Encode(text);
43+
INFO("While encoding " << text);
44+
CHECK(expected == actual);
2945
}
3046

3147
TEST_CASE("EpochMillisToStr", "[utility]") {

0 commit comments

Comments
 (0)