support base85 encoding/decoding

PiperOrigin-RevId: 725692677
This commit is contained in:
Guogang Li
2025-02-11 10:50:44 -08:00
committed by Copybara-Service
parent 7b31ce68c4
commit 58eea077e1
5 changed files with 343 additions and 0 deletions
+2
View File
@@ -416,6 +416,7 @@ let package = Package(
"internal/crypto/BUILD",
"internal/crypto/BUILD.gn",
"internal/crypto_cros/BUILD",
"internal/encoding/BUILD",
"internal/interop/BUILD",
"internal/weave/BUILD",
"internal/weave/sockets/BUILD",
@@ -505,6 +506,7 @@ let package = Package(
"internal/crypto_cros/sha2_unittest.cc",
"internal/crypto_cros/signature_verifier_unittest.cc",
"internal/crypto_cros/symmetric_key_unittest.cc",
"internal/encoding/base85_test.cc",
"internal/data/leveldb_data_set_test.cc",
"internal/data/memory_data_set_test.cc",
"internal/flags/nearby_flags_test.cc",
+39
View File
@@ -0,0 +1,39 @@
# Copyright 2025 Google LLC
#
# 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
#
# https://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.
licenses(["notice"])
package(default_visibility = ["//:__subpackages__"])
cc_library(
name = "base85",
srcs = ["base85.cc"],
hdrs = ["base85.h"],
compatible_with = ["//buildenv/target:non_prod"],
deps = [
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:string_view",
],
)
cc_test(
name = "base85_test",
srcs = ["base85_test.cc"],
deps = [
":base85",
"@com_github_protobuf_matchers//protobuf-matchers",
"@com_google_absl//absl/strings",
"@com_google_googletest//:gtest_main",
],
)
+190
View File
@@ -0,0 +1,190 @@
// Copyright 2025 Google LLC
//
// 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
//
// https://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.
#include "internal/encoding/base85.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
namespace nearby {
namespace encoding {
namespace {
constexpr int kBase85CharsNumber = 85;
constexpr uint8_t kBase85Zero = 'z';
constexpr unsigned char kBase85EncodeChars[] = {
'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>',
'?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\',
']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u'};
constexpr uint8_t kBase85LastEncodeChar =
kBase85EncodeChars[kBase85CharsNumber - 1];
constexpr uint8_t kBase85DecodeCharsCount[] = {0, 0, 1, 2, 3};
static uint8_t kBase85DecodeChars[256];
void InitBase85DecodeChars() {
if (kBase85DecodeChars[kBase85LastEncodeChar]) {
return;
}
std::fill(kBase85DecodeChars, kBase85DecodeChars + 256, 0xff);
for (uint8_t i = 0; i < kBase85CharsNumber; ++i) {
kBase85DecodeChars[kBase85EncodeChars[i]] = i;
}
}
} // namespace
std::string Base85Encode(const std::string& input, bool padding) {
size_t result_size =
5 * (input.size() >> 2) + (((input.size() % 4) * 5 + 3) >> 2);
if (padding) {
result_size = 5 * (input.size() >> 2) + ((input.size() % 4 != 0) ? 5 : 0);
}
std::string result;
result.resize(result_size);
char* result_buffer = result.data();
char* result_buffer_end = result.data() + result.size();
size_t bytes = input.size();
const char* data = input.data();
while (bytes) {
int count;
uint32_t value = 0;
for (count = 24; count >= 0; count -= 8) {
uint32_t ch = *data++;
value |= ch << count;
if (--bytes == 0) break;
}
if (value == 0) {
// special case, use 'z' to encode it
result_buffer[0] = kBase85Zero;
++result_buffer;
continue;
}
int changed_count = 0;
for (count = 4; count >= 0; count--) {
int val = value % 85;
value /= 85;
if (result_buffer + count < result_buffer_end) {
result_buffer[count] = kBase85EncodeChars[val];
++changed_count;
}
}
result_buffer += changed_count;
}
if (result_buffer != result_buffer_end) {
// special case, may include z encoding
return std::string(result.data(), result_buffer - result.data());
}
return result;
}
std::optional<std::string> Base85Decode(const std::string& input) {
InitBase85DecodeChars();
size_t length = 0;
size_t pos = 0;
for (; pos < input.size();) {
if (input[pos] == kBase85Zero) {
// special zero case
length += 4;
++pos;
continue;
}
if (pos + 5 > input.size()) {
size_t remain = input.size() - pos;
if (remain == 1) {
return std::nullopt;
}
length += kBase85DecodeCharsCount[remain];
break;
}
pos += 5;
length += 4;
}
std::string result;
result.resize(length);
const char* input_buffer = input.data();
const char* input_buffer_end = input_buffer + input.size();
char* result_buffer = result.data();
char* result_buffer_end = result_buffer + result.size();
while (length) {
int count = 4;
int decoded_char;
uint8_t input_char;
uint32_t value = 0;
do {
if (input_buffer < input_buffer_end) {
input_char = *input_buffer++;
// handle special case 'z'
if (count == 4 && input_char == kBase85Zero) {
for (int i = 0; i < count; ++i) {
*result_buffer++ = 0x00;
}
length -= 4;
continue;
}
decoded_char = kBase85DecodeChars[input_char];
if (decoded_char == 0xff) return std::nullopt;
} else {
decoded_char = kBase85LastEncodeChar;
}
value = value * 85 + decoded_char;
} while (--count);
if (input_buffer < input_buffer_end) {
input_char = *input_buffer++;
decoded_char = kBase85DecodeChars[input_char];
if (decoded_char == 0xff) return std::nullopt;
} else {
decoded_char = kBase85LastEncodeChar;
}
/* Detect overflow. */
if (0xffffffff / 85 < value || 0xffffffff - decoded_char < (value *= 85))
return std::nullopt;
value += decoded_char;
count = (length < 4) ? length : 4;
length -= count;
do {
value = (value << 8) | (value >> 24);
if (result_buffer < result_buffer_end) {
*result_buffer++ = value;
}
} while (--count);
}
return result;
}
} // namespace encoding
} // namespace nearby
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2025 Google LLC
//
// 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
//
// https://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.
#ifndef THIRD_PARTY_NEARBY_INTERNAL_ENCODING_BASE85_H_
#define THIRD_PARTY_NEARBY_INTERNAL_ENCODING_BASE85_H_
#include <optional>
#include <string>
namespace nearby {
namespace encoding {
// Encodes the input string to Base85. If pad is true, the output string will
// not be truncated.
std::string Base85Encode(const std::string& input, bool padding = false);
// Decodes the input string from Base85.
std::optional<std::string> Base85Decode(const std::string& input);
} // namespace encoding
} // namespace nearby
#endif // THIRD_PARTY_NEARBY_INTERNAL_ENCODING_BASE85_H_
+78
View File
@@ -0,0 +1,78 @@
// Copyright 2025 Google LLC
//
// 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
//
// https://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.
#include "internal/encoding/base85.h"
#include <string>
#include "gtest/gtest.h"
namespace nearby {
namespace encoding {
namespace {
TEST(Base85Test, EncodeAndDecodeString) {
EXPECT_EQ(Base85Encode(""), "");
EXPECT_EQ(Base85Decode(Base85Encode("")), "");
EXPECT_EQ(Base85Encode("b"), "@K");
EXPECT_EQ(Base85Decode(Base85Encode("b")), "b");
EXPECT_EQ(Base85Encode("ab"), "@:B");
EXPECT_EQ(Base85Decode(Base85Encode("ab")), "ab");
EXPECT_EQ(Base85Encode("xyz"), "G^4T");
EXPECT_EQ(Base85Decode(Base85Encode("xyz")), "xyz");
EXPECT_EQ(Base85Encode("true"), "FE2M8");
EXPECT_EQ(Base85Decode(Base85Encode("true")), "true");
EXPECT_EQ(Base85Encode("ab", /*padding=*/true), "@:B3:");
std::string expected_decoded_string = "ab";
expected_decoded_string.push_back('\0');
expected_decoded_string.push_back('\0');
EXPECT_EQ(Base85Decode(Base85Encode("ab", /*padding=*/true)),
expected_decoded_string);
EXPECT_EQ(Base85Encode("hello,world"), "BOu!rD_-*NEbo7");
EXPECT_EQ(Base85Decode("BOu!rD_-*NEbo7"), "hello,world");
EXPECT_EQ(Base85Encode("abcdefgh"), "@:E_WAS,Rg");
EXPECT_EQ(Base85Decode(Base85Encode("abcdefgh")), "abcdefgh");
}
TEST(Base85Test, EncodeAndDecodeBytes) {
std::string data;
data.push_back(0x00);
EXPECT_EQ(Base85Encode(data), "z");
std::string expected_data;
expected_data.push_back(0x00);
expected_data.push_back(0x00);
expected_data.push_back(0x00);
expected_data.push_back(0x00);
EXPECT_EQ(Base85Decode(Base85Encode(data)), expected_data);
data.clear();
data.push_back(0x00);
data.push_back(0x01);
EXPECT_EQ(Base85Encode(data), "!!*");
data.clear();
data.push_back(0x00);
data.push_back(0x01);
data.push_back(0xff);
EXPECT_EQ(Base85Encode(data), "!!3*");
EXPECT_EQ(Base85Decode(Base85Encode(data)), data);
}
TEST(Base85Test, InvalideEncodedString) {
EXPECT_FALSE(Base85Decode("abhjki"));
EXPECT_FALSE(Base85Decode("abz"));
EXPECT_FALSE(Base85Decode("u`qL["));
}
} // namespace
} // namespace encoding
} // namespace nearby