Replace base64 implementation with absl
Benchmarking results show a much more efficient implementation, around 80% faster for decoding 4K inputs.
benchmark_result
name old cpu/op new cpu/op delta
BM_Base64Encode/64 156ns ± 1% 66ns ± 2% -57.30% (p=0.000 n=9+10)
BM_Base64Encode/512 1.00µs ± 2% 0.34µs ± 2% -65.82% (p=0.000 n=9+10)
BM_Base64Encode/4096 7.79µs ± 2% 2.50µs ± 1% -67.97% (p=0.000 n=10+9)
BM_Base64Encode/32768 61.8µs ± 2% 19.6µs ± 2% -68.21% (p=0.000 n=10+10)
BM_Base64Encode/262144 495µs ± 2% 157µs ± 2% -68.27% (p=0.000 n=10+9)
BM_Base64Encode/2097152 4.04ms ± 2% 1.26ms ± 2% -68.89% (p=0.000 n=9+9)
BM_Base64Encode/8388608 16.1ms ± 3% 5.0ms ± 2% -68.75% (p=0.000 n=10+9)
BM_Base64Decode/64 331ns ± 2% 137ns ± 2% -58.45% (p=0.000 n=9+10)
BM_Base64Decode/512 2.22µs ± 2% 0.88µs ± 2% -60.35% (p=0.000 n=10+10)
BM_Base64Decode/4096 17.5µs ± 1% 6.8µs ± 1% -61.38% (p=0.000 n=9+9)
BM_Base64Decode/32768 139µs ± 1% 54µs ± 1% -61.39% (p=0.000 n=9+8)
BM_Base64Decode/262144 1.11ms ± 3% 0.43ms ± 2% -61.46% (p=0.000 n=10+9)
BM_Base64Decode/2097152 8.93ms ± 2% 3.42ms ± 2% -61.75% (p=0.000 n=10+9)
BM_Base64Decode/8388608 35.7ms ± 1% 13.7ms ± 2% -61.63% (p=0.000 n=9+9)
BM_Base64DecodeForgiving/64 327ns ± 2% 96ns ± 2% -70.69% (p=0.000 n=9+10)
BM_Base64DecodeForgiving/512 2.35µs ± 3% 0.49µs ± 2% -78.93% (p=0.000 n=10+10)
BM_Base64DecodeForgiving/4096 18.6µs ± 2% 3.7µs ± 2% -80.21% (p=0.000 n=8+10)
BM_Base64DecodeForgiving/32768 148µs ± 2% 29µs ± 4% -80.45% (p=0.000 n=9+10)
BM_Base64DecodeForgiving/262144 1.18ms ± 2% 0.23ms ± 2% -80.51% (p=0.000 n=10+10)
BM_Base64DecodeForgiving/2097152 9.54ms ± 1% 1.84ms ± 1% -80.69% (p=0.000 n=8+10)
BM_Base64DecodeForgiving/8388608 37.9ms ± 2% 7.4ms ± 1% -80.56% (p=0.000 n=10+10)
name old time/op new time/op delta
BM_Base64Encode/64 156ns ± 1% 67ns ± 2% -57.29% (p=0.000 n=9+10)
BM_Base64Encode/512 1.00µs ± 2% 0.34µs ± 2% -65.82% (p=0.000 n=9+10)
BM_Base64Encode/4096 7.83µs ± 1% 2.50µs ± 1% -68.05% (p=0.000 n=9+9)
BM_Base64Encode/32768 62.0µs ± 2% 19.7µs ± 2% -68.21% (p=0.000 n=10+10)
BM_Base64Encode/262144 496µs ± 2% 157µs ± 2% -68.27% (p=0.000 n=10+9)
BM_Base64Encode/2097152 4.05ms ± 2% 1.26ms ± 2% -68.88% (p=0.000 n=9+9)
BM_Base64Encode/8388608 16.1ms ± 3% 5.0ms ± 2% -68.83% (p=0.000 n=10+8)
BM_Base64Decode/64 331ns ± 2% 138ns ± 2% -58.34% (p=0.000 n=9+9)
BM_Base64Decode/512 2.22µs ± 2% 0.88µs ± 2% -60.35% (p=0.000 n=10+10)
BM_Base64Decode/4096 17.6µs ± 1% 6.8µs ± 1% -61.37% (p=0.000 n=9+9)
BM_Base64Decode/32768 140µs ± 1% 54µs ± 1% -61.38% (p=0.000 n=9+8)
BM_Base64Decode/262144 1.11ms ± 3% 0.43ms ± 2% -61.45% (p=0.000 n=10+9)
BM_Base64Decode/2097152 8.95ms ± 2% 3.43ms ± 2% -61.73% (p=0.000 n=10+9)
BM_Base64Decode/8388608 35.8ms ± 1% 13.7ms ± 2% -61.61% (p=0.000 n=9+9)
BM_Base64DecodeForgiving/64 328ns ± 2% 96ns ± 2% -70.68% (p=0.000 n=9+10)
BM_Base64DecodeForgiving/512 2.35µs ± 3% 0.50µs ± 2% -78.92% (p=0.000 n=10+10)
BM_Base64DecodeForgiving/4096 18.6µs ± 2% 3.7µs ± 2% -80.21% (p=0.000 n=8+10)
BM_Base64DecodeForgiving/32768 149µs ± 2% 29µs ± 4% -80.45% (p=0.000 n=9+10)
BM_Base64DecodeForgiving/262144 1.18ms ± 2% 0.23ms ± 2% -80.51% (p=0.000 n=10+10)
BM_Base64DecodeForgiving/2097152 9.56ms ± 1% 1.85ms ± 1% -80.68% (p=0.000 n=8+10)
BM_Base64DecodeForgiving/8388608 38.0ms ± 2% 7.4ms ± 1% -80.55% (p=0.000 n=10+10)
name old INSTRUCTIONS/op new INSTRUCTIONS/op delta
BM_Base64Encode/64 2.27k ± 0% 0.87k ± 0% -61.60% (p=0.000 n=10+10)
BM_Base64Encode/512 15.4k ± 0% 4.5k ± 0% -71.19% (p=0.000 n=10+10)
BM_Base64Encode/4096 121k ± 0% 33k ± 0% -72.48% (p=0.000 n=10+10)
BM_Base64Encode/32768 965k ± 0% 263k ± 0% -72.77% (p=0.000 n=10+10)
BM_Base64Encode/262144 7.71M ± 0% 2.10M ± 0% -72.81% (p=0.000 n=9+10)
BM_Base64Encode/2097152 61.7M ± 0% 16.8M ± 0% -72.81% (p=0.000 n=9+10)
BM_Base64Encode/8388608 247M ± 0% 67M ± 0% -72.81% (p=0.000 n=10+10)
BM_Base64Decode/64 4.41k ± 0% 1.91k ± 0% -56.60% (p=0.000 n=9+10)
BM_Base64Decode/512 32.4k ± 0% 12.1k ± 0% -62.84% (p=0.000 n=10+10)
BM_Base64Decode/4096 257k ± 0% 94k ± 0% -63.65% (p=0.001 n=8+9)
BM_Base64Decode/32768 2.05M ± 0% 0.74M ± 0% -63.81% (p=0.000 n=9+10)
BM_Base64Decode/262144 16.4M ± 0% 5.9M ± 0% -63.83% (p=0.000 n=9+8)
BM_Base64Decode/2097152 131M ± 0% 48M ± 0% -63.83% (p=0.000 n=10+10)
BM_Base64Decode/8388608 526M ± 0% 190M ± 0% -63.83% (p=0.000 n=10+10)
BM_Base64DecodeForgiving/64 4.45k ± 0% 1.30k ± 0% -70.73% (p=0.000 n=9+10)
BM_Base64DecodeForgiving/512 32.7k ± 0% 7.1k ± 0% -78.15% (p=0.000 n=9+10)
BM_Base64DecodeForgiving/4096 259k ± 0% 54k ± 0% -79.01% (p=0.000 n=9+8)
BM_Base64DecodeForgiving/32768 2.07M ± 0% 0.43M ± 0% -79.23% (p=0.000 n=8+8)
BM_Base64DecodeForgiving/262144 16.6M ± 0% 3.4M ± 0% -79.25% (p=0.000 n=8+9)
BM_Base64DecodeForgiving/2097152 132M ± 0% 27M ± 0% -79.26% (p=0.000 n=8+9)
BM_Base64DecodeForgiving/8388608 530M ± 0% 110M ± 0% -79.26% (p=0.001 n=6+8)
name old CYCLES/op new CYCLES/op delta
BM_Base64Encode/64 502 ± 0% 214 ± 0% -57.25% (p=0.000 n=10+8)
BM_Base64Encode/512 3.22k ± 0% 1.10k ± 0% -65.82% (p=0.000 n=9+10)
BM_Base64Encode/4096 25.1k ± 0% 8.0k ± 0% -68.02% (p=0.000 n=8+10)
BM_Base64Encode/32768 200k ± 0% 63k ± 0% -68.33% (p=0.000 n=10+10)
BM_Base64Encode/262144 1.60M ± 0% 0.51M ± 0% -68.38% (p=0.000 n=9+9)
BM_Base64Encode/2097152 13.0M ± 0% 4.0M ± 0% -68.91% (p=0.000 n=10+10)
BM_Base64Encode/8388608 52.0M ± 0% 16.2M ± 0% -68.88% (p=0.000 n=10+10)
BM_Base64Decode/64 1.07k ± 0% 0.44k ± 0% -58.38% (p=0.000 n=10+10)
BM_Base64Decode/512 7.19k ± 0% 2.85k ± 0% -60.36% (p=0.000 n=9+10)
BM_Base64Decode/4096 56.5k ± 0% 21.9k ± 0% -61.30% (p=0.000 n=8+10)
BM_Base64Decode/32768 450k ± 0% 173k ± 0% -61.50% (p=0.000 n=10+9)
BM_Base64Decode/262144 3.60M ± 0% 1.38M ± 0% -61.58% (p=0.000 n=9+10)
BM_Base64Decode/2097152 28.8M ± 0% 11.1M ± 0% -61.60% (p=0.000 n=9+9)
BM_Base64Decode/8388608 115M ± 0% 44M ± 0% -61.56% (p=0.000 n=9+9)
BM_Base64DecodeForgiving/64 1.06k ± 0% 0.31k ± 0% -70.66% (p=0.000 n=9+10)
BM_Base64DecodeForgiving/512 7.61k ± 0% 1.60k ± 0% -79.00% (p=0.000 n=10+10)
BM_Base64DecodeForgiving/4096 60.1k ± 0% 11.9k ± 0% -80.14% (p=0.000 n=10+10)
BM_Base64DecodeForgiving/32768 478k ± 0% 94k ± 0% -80.42% (p=0.000 n=9+9)
BM_Base64DecodeForgiving/262144 3.83M ± 0% 0.75M ± 0% -80.51% (p=0.000 n=10+10)
BM_Base64DecodeForgiving/2097152 30.6M ± 0% 6.0M ± 0% -80.54% (p=0.000 n=10+10)
BM_Base64DecodeForgiving/8388608 123M ± 0% 24M ± 0% -80.55% (p=0.000 n=9+10)
Bug: webrtc:42220265
Change-Id: I753d9314f2102efddc7480537b104fee73d11dca
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/384400
Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org>
Commit-Queue: Evan Shrubsole <eshr@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#44314}
diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn
index a6486b6..3ea1dc5 100644
--- a/rtc_base/BUILD.gn
+++ b/rtc_base/BUILD.gn
@@ -1788,9 +1788,9 @@
"base64.h",
]
deps = [
- ":checks",
"../api:array_view",
- "third_party/base64",
+ "//third_party/abseil-cpp/absl/algorithm:container",
+ "//third_party/abseil-cpp/absl/strings",
"//third_party/abseil-cpp/absl/strings:string_view",
]
}
diff --git a/rtc_base/base64.cc b/rtc_base/base64.cc
index 03f6a08..eaa4d2b 100644
--- a/rtc_base/base64.cc
+++ b/rtc_base/base64.cc
@@ -10,42 +10,36 @@
#include "rtc_base/base64.h"
-#include <cstdint>
#include <optional>
#include <string>
-#include <vector>
+#include <utility>
+#include "absl/algorithm/container.h"
+#include "absl/strings/ascii.h"
+#include "absl/strings/escaping.h"
#include "absl/strings/string_view.h"
-#include "api/array_view.h"
-#include "rtc_base/third_party/base64/base64.h"
namespace webrtc {
-std::string Base64Encode(ArrayView<const uint8_t> data) {
- std::string result;
- Base64::EncodeFromArray(data.data(), data.size(), &result);
- return result;
+namespace {
+
+bool IsStrictBase64(absl::string_view data) {
+ // Strict base64 must be a multiple of 4 bytes and have no whitespace.
+ return data.size() % 4 == 0 && absl::c_none_of(data, absl::ascii_isspace);
}
+} // namespace
-std::optional<std::vector<uint8_t>> Base64Decode(absl::string_view data,
- Base64DecodeOptions options) {
- Base64::DecodeFlags flags;
- switch (options) {
- case Base64DecodeOptions::kForgiving:
- flags =
- Base64::DO_PARSE_WHITE | Base64::DO_PAD_ANY | Base64::DO_TERM_BUFFER;
- break;
- case Base64DecodeOptions::kStrict:
- flags = Base64::DO_STRICT;
- break;
- }
-
- std::vector<uint8_t> result;
- if (!Base64::DecodeFromArray(data.data(), data.size(), flags, &result,
- nullptr)) {
+std::optional<std::string> Base64Decode(absl::string_view data,
+ Base64DecodeOptions options) {
+ // absl::Base64Unescape is forgiving. Return nullopt if the input is not
+ // strict.
+ if (options == Base64DecodeOptions::kStrict && !IsStrictBase64(data)) {
return std::nullopt;
}
- return result;
+
+ std::string dest;
+ return absl::Base64Unescape(data, &dest) ? std::make_optional(std::move(dest))
+ : std::nullopt;
}
} // namespace webrtc
diff --git a/rtc_base/base64.h b/rtc_base/base64.h
index 070e317..24c1d97 100644
--- a/rtc_base/base64.h
+++ b/rtc_base/base64.h
@@ -14,25 +14,31 @@
#include <cstdint>
#include <optional>
#include <string>
-#include <vector>
+#include "absl/strings/escaping.h"
#include "absl/strings/string_view.h"
#include "api/array_view.h"
namespace webrtc {
-std::string Base64Encode(ArrayView<const uint8_t> data);
+inline std::string Base64Encode(ArrayView<const uint8_t> data) {
+ return absl::Base64Escape(absl::string_view(
+ reinterpret_cast<const char*>(data.data()), data.size()));
+}
+
+inline std::string Base64Encode(absl::string_view data) {
+ return absl::Base64Escape(data);
+}
enum class Base64DecodeOptions {
kStrict,
-
// Matches https://infra.spec.whatwg.org/#forgiving-base64-decode.
kForgiving,
};
// Returns the decoded data if successful, or std::nullopt if the decoding
// failed.
-std::optional<std::vector<uint8_t>> Base64Decode(
+std::optional<std::string> Base64Decode(
absl::string_view data,
Base64DecodeOptions options = Base64DecodeOptions::kStrict);
diff --git a/rtc_base/base64_unittest.cc b/rtc_base/base64_unittest.cc
index 32d5a1a..51c90d5 100644
--- a/rtc_base/base64_unittest.cc
+++ b/rtc_base/base64_unittest.cc
@@ -10,33 +10,29 @@
#include "rtc_base/base64.h"
-#include <cstdint>
#include <optional>
#include <string>
-#include <vector>
#include "absl/strings/string_view.h"
-#include "api/array_view.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
-using ::testing::ElementsAreArray;
+
using ::testing::Eq;
using ::testing::Optional;
using ::testing::SizeIs;
using ::testing::TestWithParam;
TEST(Base64Test, Encode) {
- uint8_t data[] = {0x64, 0x65, 0x66};
+ std::string data{0x64, 0x65, 0x66};
EXPECT_THAT(Base64Encode(data), Eq("ZGVm"));
}
TEST(Base64Test, EncodeDecode) {
- uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05};
- EXPECT_THAT(Base64Decode(Base64Encode(data)),
- Optional(ElementsAreArray(data)));
+ std::string data{0x01, 0x02, 0x03, 0x04, 0x05};
+ EXPECT_THAT(Base64Decode(Base64Encode(data)), Optional(Eq(data)));
}
TEST(Base64Test, DecodeCertificate) {
@@ -66,73 +62,79 @@
struct Base64DecodeTestCase {
std::string name;
std::string data;
- std::optional<std::vector<uint8_t>> result;
+ std::optional<std::string> result;
};
const Base64DecodeTestCase kBase64DecodeTestCases[] = {
{"InvalidCharacters", "invalid;;;", std::nullopt},
{"InvalidLength", "abcde", std::nullopt},
- {"ValidInput", "abcd", std::vector<uint8_t>{0x69, 0xB7, 0x1D}},
- {"ValidInputPadding", "abc=", std::vector<uint8_t>{0x69, 0xB7}},
- {"EmptyInput", "", std::vector<uint8_t>{}},
+ {"ValidInput", "abcd", "i\xB7\x1D"},
+ {"ValidInputPadding", "abc=", "i\xB7"},
+ {"EmptyInput", "", ""},
};
using Base64DecodeTest = TestWithParam<Base64DecodeTestCase>;
INSTANTIATE_TEST_SUITE_P(
Base64DecodeTest,
Base64DecodeTest,
- ::testing::ValuesIn<Base64DecodeTestCase>(kBase64DecodeTestCases),
+ testing::ValuesIn<Base64DecodeTestCase>(kBase64DecodeTestCases),
[](const auto& info) { return info.param.name; });
TEST_P(Base64DecodeTest, TestDecodeStrict) {
absl::string_view data = GetParam().data;
- EXPECT_THAT(Base64Decode(data, Base64DecodeOptions::kForgiving),
- Eq(GetParam().result));
EXPECT_THAT(Base64Decode(data, Base64DecodeOptions::kStrict),
Eq(GetParam().result));
}
+TEST_P(Base64DecodeTest, TestDecodeForgiving) {
+ // Test default value is strict.
+ EXPECT_THAT(Base64Decode(GetParam().data), Eq(GetParam().result));
+}
+
const Base64DecodeTestCase kBase64DecodeForgivingTestCases[] = {
{
"ForgivingPadding",
"abc",
- std::vector<uint8_t>{0x69, 0xB7},
+ "i\xB7",
},
{
"WhitespaceForgivenTab",
"ab\tcd",
- std::vector<uint8_t>{0x69, 0xB7, 0x1D},
+ "i\xB7\x1D",
},
{
"WhitespaceForgivenSpace",
"a bc d",
- std::vector<uint8_t>{0x69, 0xB7, 0x1D},
+ "i\xB7\x1D",
},
{
"WhitespaceForgivenNewline",
"a\nbc\nd",
- std::vector<uint8_t>{0x69, 0xB7, 0x1D},
+ "i\xB7\x1D",
},
{
"WhitespaceForgivenCarriageReturn",
"a\r\nbc\rd",
- std::vector<uint8_t>{0x69, 0xB7, 0x1D},
+ "i\xB7\x1D",
},
- {"WhitespaceForgivenLineFeed", "a\fbcd",
- std::vector<uint8_t>{0x69, 0xB7, 0x1D}},
+ {"WhitespaceForgivenLineFeed", "a\fbcd", "i\xB7\x1D"},
};
+
using Base64DecodeForgivingTest = TestWithParam<Base64DecodeTestCase>;
INSTANTIATE_TEST_SUITE_P(
Base64DecodeTest,
Base64DecodeForgivingTest,
- ::testing::ValuesIn<Base64DecodeTestCase>(kBase64DecodeForgivingTestCases),
+ testing::ValuesIn<Base64DecodeTestCase>(kBase64DecodeForgivingTestCases),
[](const auto& info) { return info.param.name; });
TEST_P(Base64DecodeForgivingTest, TestDecodeForgiving) {
- absl::string_view data = GetParam().data;
- EXPECT_THAT(Base64Decode(data, Base64DecodeOptions::kForgiving),
+ EXPECT_THAT(Base64Decode(GetParam().data, Base64DecodeOptions::kForgiving),
Eq(GetParam().result));
- EXPECT_THAT(Base64Decode(data), Eq(std::nullopt));
+}
+
+TEST_P(Base64DecodeForgivingTest, TestDecodeStrictFails) {
+ // Test default value is strict.
+ EXPECT_THAT(Base64Decode(GetParam().data), Eq(std::nullopt));
}
} // namespace
diff --git a/test/fuzzers/base64_encode_fuzzer.cc b/test/fuzzers/base64_encode_fuzzer.cc
index 51d329a..ccf81b2 100644
--- a/test/fuzzers/base64_encode_fuzzer.cc
+++ b/test/fuzzers/base64_encode_fuzzer.cc
@@ -11,7 +11,7 @@
#include <cstdint>
#include <cstring>
#include <optional>
-#include <vector>
+#include <string>
#include "api/array_view.h"
#include "rtc_base/base64.h"
@@ -20,7 +20,7 @@
namespace webrtc {
void FuzzOneInput(const uint8_t* data, size_t size) {
- std::optional<std::vector<uint8_t>> decoded_encoded_data =
+ std::optional<std::string> decoded_encoded_data =
Base64Decode(Base64Encode(rtc::MakeArrayView(data, size)));
RTC_CHECK(decoded_encoded_data.has_value());
RTC_CHECK_EQ(std::memcmp(data, decoded_encoded_data->data(), size), 0);