base: add base64.cc

Add a dedicated base64 translation unit for both encoding
and decoding. Will be used by follow-up CLs.
Also refactor existing code currently using base64
from string_utils.cc.

Bug: 205274609
Change-Id: I8ce97dd09fc1707c9989c0f148bd972b805d4b49
diff --git a/Android.bp b/Android.bp
index f5484c8..ea02f7e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6842,6 +6842,7 @@
     name: "perfetto_src_base_base",
     srcs: [
         "src/base/android_utils.cc",
+        "src/base/base64.cc",
         "src/base/crash_keys.cc",
         "src/base/ctrl_c_handler.cc",
         "src/base/event_fd.cc",
@@ -6888,6 +6889,7 @@
 filegroup {
     name: "perfetto_src_base_unittests",
     srcs: [
+        "src/base/base64_unittest.cc",
         "src/base/circular_queue_unittest.cc",
         "src/base/flat_set_unittest.cc",
         "src/base/getopt_compat_unittest.cc",
diff --git a/BUILD b/BUILD
index 6422ad6..c69e93d 100644
--- a/BUILD
+++ b/BUILD
@@ -335,6 +335,7 @@
     name = "include_perfetto_ext_base_base",
     srcs = [
         "include/perfetto/ext/base/android_utils.h",
+        "include/perfetto/ext/base/base64.h",
         "include/perfetto/ext/base/circular_queue.h",
         "include/perfetto/ext/base/container_annotations.h",
         "include/perfetto/ext/base/crash_keys.h",
@@ -640,6 +641,7 @@
     name = "src_base_base",
     srcs = [
         "src/base/android_utils.cc",
+        "src/base/base64.cc",
         "src/base/crash_keys.cc",
         "src/base/ctrl_c_handler.cc",
         "src/base/event_fd.cc",
diff --git a/include/perfetto/ext/base/BUILD.gn b/include/perfetto/ext/base/BUILD.gn
index f3b9113..34e09de 100644
--- a/include/perfetto/ext/base/BUILD.gn
+++ b/include/perfetto/ext/base/BUILD.gn
@@ -17,6 +17,7 @@
 source_set("base") {
   sources = [
     "android_utils.h",
+    "base64.h",
     "circular_queue.h",
     "container_annotations.h",
     "crash_keys.h",
diff --git a/include/perfetto/ext/base/base64.h b/include/perfetto/ext/base/base64.h
new file mode 100644
index 0000000..84e739c
--- /dev/null
+++ b/include/perfetto/ext/base/base64.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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 INCLUDE_PERFETTO_EXT_BASE_BASE64_H_
+#define INCLUDE_PERFETTO_EXT_BASE_BASE64_H_
+
+#include <string>
+
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/string_view.h"
+
+namespace perfetto {
+namespace base {
+
+// Returns the length of the destination string (included '=' padding).
+// Does NOT include the size of the string null terminator.
+inline size_t Base64EncSize(size_t src_size) {
+  return (src_size + 2) / 3 * 4;
+}
+
+// Returns the upper bound on the length of the destination buffer.
+// The actual decoded length might be <= the number returned here.
+inline size_t Base64DecSize(size_t src_size) {
+  return (src_size + 3) / 4 * 3;
+}
+
+// Does NOT null-terminate |dst|.
+ssize_t Base64Encode(const void* src,
+                     size_t src_size,
+                     char* dst,
+                     size_t dst_size);
+
+std::string Base64Encode(const void* src, size_t src_size);
+
+inline std::string Base64Encode(StringView sv) {
+  return Base64Encode(sv.data(), sv.size());
+}
+
+// Returns -1 in case of failure.
+ssize_t Base64Decode(const char* src,
+                     size_t src_size,
+                     uint8_t* dst,
+                     size_t dst_size);
+
+Optional<std::string> Base64Decode(const char* src, size_t src_size);
+
+inline Optional<std::string> Base64Decode(StringView sv) {
+  return Base64Decode(sv.data(), sv.size());
+}
+
+}  // namespace base
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_BASE_BASE64_H_
diff --git a/include/perfetto/ext/base/string_utils.h b/include/perfetto/ext/base/string_utils.h
index 2508aec..58444a8 100644
--- a/include/perfetto/ext/base/string_utils.h
+++ b/include/perfetto/ext/base/string_utils.h
@@ -125,7 +125,6 @@
                        const std::string& to_replace,
                        const std::string& replacement);
 std::string TrimLeading(const std::string& str);
-std::string Base64Encode(const void* raw, size_t size);
 
 // A BSD-style strlcpy without the return value.
 // Copies at most |dst_size|-1 characters. Unlike strncpy, it always \0
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index 156c98b..f6d86b0 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -32,6 +32,7 @@
   ]
   sources = [
     "android_utils.cc",
+    "base64.cc",
     "crash_keys.cc",
     "ctrl_c_handler.cc",
     "event_fd.cc",
@@ -159,6 +160,7 @@
   ]
 
   sources = [
+    "base64_unittest.cc",
     "circular_queue_unittest.cc",
     "flat_set_unittest.cc",
     "getopt_compat_unittest.cc",
diff --git a/src/base/base64.cc b/src/base/base64.cc
new file mode 100644
index 0000000..437ad67
--- /dev/null
+++ b/src/base/base64.cc
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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 "perfetto/ext/base/base64.h"
+
+namespace perfetto {
+namespace base {
+
+namespace {
+
+constexpr char kPadding = '=';
+
+constexpr char kEncTable[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static_assert(sizeof(kEncTable) == (1u << 6) + sizeof('\0'), "Bad table size");
+
+// Maps an ASCII character to its 6-bit value. It only contains translations
+// from '+' to 'z'. Supports the standard (+/) and URL-safe (-_) alphabets.
+constexpr uint8_t kX = 0xff;  // Value used for invalid characters
+constexpr uint8_t kDecTable[] = {
+    62, kX, 62, kX, 63, 52, 53, 54, 55, 56,  // 00 - 09
+    57, 58, 59, 60, 61, kX, kX, kX, 0,  kX,  // 10 - 19
+    kX, kX, 0,  1,  2,  3,  4,  5,  6,  7,   // 20 - 29
+    8,  9,  10, 11, 12, 13, 14, 15, 16, 17,  // 30 - 39
+    18, 19, 20, 21, 22, 23, 24, 25, kX, kX,  // 40 - 49
+    kX, kX, 63, kX, 26, 27, 28, 29, 30, 31,  // 50 - 59
+    32, 33, 34, 35, 36, 37, 38, 39, 40, 41,  // 60 - 69
+    42, 43, 44, 45, 46, 47, 48, 49, 50, 51,  // 70 - 79
+};
+constexpr char kMinDecChar = '+';
+constexpr char kMaxDecChar = 'z';
+static_assert(kMaxDecChar - kMinDecChar <= sizeof(kDecTable), "Bad table size");
+
+inline uint8_t DecodeChar(char c) {
+  if (c < kMinDecChar || c > kMaxDecChar)
+    return kX;
+  return kDecTable[c - kMinDecChar];
+}
+
+}  // namespace
+
+ssize_t Base64Encode(const void* src,
+                     size_t src_size,
+                     char* dst,
+                     size_t dst_size) {
+  const size_t padded_dst_size = Base64EncSize(src_size);
+  if (dst_size < padded_dst_size)
+    return -1;  // Not enough space in output.
+
+  const uint8_t* rd = static_cast<const uint8_t*>(src);
+  const uint8_t* const end = rd + src_size;
+  size_t wr_size = 0;
+  while (rd < end) {
+    uint8_t s[3]{};
+    s[0] = *(rd++);
+    dst[wr_size++] = kEncTable[s[0] >> 2];
+
+    uint8_t carry0 = static_cast<uint8_t>((s[0] & 0x03) << 4);
+    if (PERFETTO_LIKELY(rd < end)) {
+      s[1] = *(rd++);
+      dst[wr_size++] = kEncTable[carry0 | (s[1] >> 4)];
+    } else {
+      dst[wr_size++] = kEncTable[carry0];
+      dst[wr_size++] = kPadding;
+      dst[wr_size++] = kPadding;
+      break;
+    }
+
+    uint8_t carry1 = static_cast<uint8_t>((s[1] & 0x0f) << 2);
+    if (PERFETTO_LIKELY(rd < end)) {
+      s[2] = *(rd++);
+      dst[wr_size++] = kEncTable[carry1 | (s[2] >> 6)];
+    } else {
+      dst[wr_size++] = kEncTable[carry1];
+      dst[wr_size++] = kPadding;
+      break;
+    }
+
+    dst[wr_size++] = kEncTable[s[2] & 0x3f];
+  }
+  PERFETTO_DCHECK(wr_size == padded_dst_size);
+  return static_cast<ssize_t>(padded_dst_size);
+}
+
+std::string Base64Encode(const void* src, size_t src_size) {
+  std::string dst;
+  dst.resize(Base64EncSize(src_size));
+  auto res = Base64Encode(src, src_size, &dst[0], dst.size());
+  PERFETTO_CHECK(res == static_cast<ssize_t>(dst.size()));
+  return dst;
+}
+
+ssize_t Base64Decode(const char* src,
+                     size_t src_size,
+                     uint8_t* dst,
+                     size_t dst_size) {
+  const size_t min_dst_size = Base64DecSize(src_size);
+  if (dst_size < min_dst_size)
+    return -1;
+
+  const char* rd = src;
+  const char* const end = src + src_size;
+  size_t wr_size = 0;
+
+  char s[4]{};
+  while (rd < end) {
+    uint8_t d[4];
+    for (uint32_t j = 0; j < 4; j++) {
+      // Padding is only feasible for the last 2 chars of each group of 4.
+      s[j] = rd < end ? *(rd++) : (j < 2 ? '\0' : kPadding);
+      d[j] = DecodeChar(s[j]);
+      if (d[j] == kX)
+        return -1;  // Invalid input char.
+    }
+    dst[wr_size] = static_cast<uint8_t>((d[0] << 2) | (d[1] >> 4));
+    dst[wr_size + 1] = static_cast<uint8_t>((d[1] << 4) | (d[2] >> 2));
+    dst[wr_size + 2] = static_cast<uint8_t>((d[2] << 6) | (d[3]));
+    wr_size += 3;
+  }
+
+  PERFETTO_CHECK(wr_size <= dst_size);
+  wr_size -= (s[3] == kPadding ? 1 : 0) + (s[2] == kPadding ? 1 : 0);
+  return static_cast<ssize_t>(wr_size);
+}
+
+Optional<std::string> Base64Decode(const char* src, size_t src_size) {
+  std::string dst;
+  dst.resize(Base64DecSize(src_size));
+  auto res = Base64Decode(src, src_size, reinterpret_cast<uint8_t*>(&dst[0]),
+                          dst.size());
+  if (res < 0)
+    return nullopt;  // Decoding error.
+
+  PERFETTO_CHECK(res <= static_cast<ssize_t>(dst.size()));
+  dst.resize(static_cast<size_t>(res));
+  return make_optional(dst);
+}
+
+}  // namespace base
+}  // namespace perfetto
diff --git a/src/base/base64_unittest.cc b/src/base/base64_unittest.cc
new file mode 100644
index 0000000..fb131d9
--- /dev/null
+++ b/src/base/base64_unittest.cc
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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 "perfetto/ext/base/base64.h"
+
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/ext/base/utils.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace base {
+namespace {
+
+struct TestPattern {
+  size_t decoded_len;
+  const char* decoded;
+  const char* encoded;
+};
+
+TestPattern kPatterns[] = {
+
+    // Basic bit patterns;
+    // values obtained with "echo -n '...' | uuencode -m test"
+
+    {1, "\000", "AA=="},
+    {1, "\001", "AQ=="},
+    {1, "\002", "Ag=="},
+    {1, "\004", "BA=="},
+    {1, "\010", "CA=="},
+    {1, "\020", "EA=="},
+    {1, "\040", "IA=="},
+    {1, "\100", "QA=="},
+    {1, "\200", "gA=="},
+
+    {1, "\377", "/w=="},
+    {1, "\376", "/g=="},
+    {1, "\375", "/Q=="},
+    {1, "\373", "+w=="},
+    {1, "\367", "9w=="},
+    {1, "\357", "7w=="},
+    {1, "\337", "3w=="},
+    {1, "\277", "vw=="},
+    {1, "\177", "fw=="},
+    {2, "\000\000", "AAA="},
+    {2, "\000\001", "AAE="},
+    {2, "\000\002", "AAI="},
+    {2, "\000\004", "AAQ="},
+    {2, "\000\010", "AAg="},
+    {2, "\000\020", "ABA="},
+    {2, "\000\040", "ACA="},
+    {2, "\000\100", "AEA="},
+    {2, "\000\200", "AIA="},
+    {2, "\001\000", "AQA="},
+    {2, "\002\000", "AgA="},
+    {2, "\004\000", "BAA="},
+    {2, "\010\000", "CAA="},
+    {2, "\020\000", "EAA="},
+    {2, "\040\000", "IAA="},
+    {2, "\100\000", "QAA="},
+    {2, "\200\000", "gAA="},
+
+    {2, "\377\377", "//8="},
+    {2, "\377\376", "//4="},
+    {2, "\377\375", "//0="},
+    {2, "\377\373", "//s="},
+    {2, "\377\367", "//c="},
+    {2, "\377\357", "/+8="},
+    {2, "\377\337", "/98="},
+    {2, "\377\277", "/78="},
+    {2, "\377\177", "/38="},
+    {2, "\376\377", "/v8="},
+    {2, "\375\377", "/f8="},
+    {2, "\373\377", "+/8="},
+    {2, "\367\377", "9/8="},
+    {2, "\357\377", "7/8="},
+    {2, "\337\377", "3/8="},
+    {2, "\277\377", "v/8="},
+    {2, "\177\377", "f/8="},
+
+    {3, "\000\000\000", "AAAA"},
+    {3, "\000\000\001", "AAAB"},
+    {3, "\000\000\002", "AAAC"},
+    {3, "\000\000\004", "AAAE"},
+    {3, "\000\000\010", "AAAI"},
+    {3, "\000\000\020", "AAAQ"},
+    {3, "\000\000\040", "AAAg"},
+    {3, "\000\000\100", "AABA"},
+    {3, "\000\000\200", "AACA"},
+    {3, "\000\001\000", "AAEA"},
+    {3, "\000\002\000", "AAIA"},
+    {3, "\000\004\000", "AAQA"},
+    {3, "\000\010\000", "AAgA"},
+    {3, "\000\020\000", "ABAA"},
+    {3, "\000\040\000", "ACAA"},
+    {3, "\000\100\000", "AEAA"},
+    {3, "\000\200\000", "AIAA"},
+    {3, "\001\000\000", "AQAA"},
+    {3, "\002\000\000", "AgAA"},
+    {3, "\004\000\000", "BAAA"},
+    {3, "\010\000\000", "CAAA"},
+    {3, "\020\000\000", "EAAA"},
+    {3, "\040\000\000", "IAAA"},
+    {3, "\100\000\000", "QAAA"},
+    {3, "\200\000\000", "gAAA"},
+
+    {3, "\377\377\377", "////"},
+    {3, "\377\377\376", "///+"},
+    {3, "\377\377\375", "///9"},
+    {3, "\377\377\373", "///7"},
+    {3, "\377\377\367", "///3"},
+    {3, "\377\377\357", "///v"},
+    {3, "\377\377\337", "///f"},
+    {3, "\377\377\277", "//+/"},
+    {3, "\377\377\177", "//9/"},
+    {3, "\377\376\377", "//7/"},
+    {3, "\377\375\377", "//3/"},
+    {3, "\377\373\377", "//v/"},
+    {3, "\377\367\377", "//f/"},
+    {3, "\377\357\377", "/+//"},
+    {3, "\377\337\377", "/9//"},
+    {3, "\377\277\377", "/7//"},
+    {3, "\377\177\377", "/3//"},
+    {3, "\376\377\377", "/v//"},
+    {3, "\375\377\377", "/f//"},
+    {3, "\373\377\377", "+///"},
+    {3, "\367\377\377", "9///"},
+    {3, "\357\377\377", "7///"},
+    {3, "\337\377\377", "3///"},
+    {3, "\277\377\377", "v///"},
+    {3, "\177\377\377", "f///"},
+
+    // Random numbers: values obtained with
+    //
+    //  #! /bin/bash
+    //  dd bs=$1 count=1 if=/dev/random of=/tmp/bar.random
+    //  od -N $1 -t o1 /tmp/bar.random
+    //  uuencode -m test < /tmp/bar.random
+    //
+    // where $1 is the number of bytes (2, 3)
+
+    {2, "\243\361", "o/E="},
+    {2, "\024\167", "FHc="},
+    {2, "\313\252", "y6o="},
+    {2, "\046\041", "JiE="},
+    {2, "\145\236", "ZZ4="},
+    {2, "\254\325", "rNU="},
+    {2, "\061\330", "Mdg="},
+    {2, "\245\032", "pRo="},
+    {2, "\006\000", "BgA="},
+    {2, "\375\131", "/Vk="},
+    {2, "\303\210", "w4g="},
+    {2, "\040\037", "IB8="},
+    {2, "\261\372", "sfo="},
+    {2, "\335\014", "3Qw="},
+    {2, "\233\217", "m48="},
+    {2, "\373\056", "+y4="},
+    {2, "\247\232", "p5o="},
+    {2, "\107\053", "Rys="},
+    {2, "\204\077", "hD8="},
+    {2, "\276\211", "vok="},
+    {2, "\313\110", "y0g="},
+    {2, "\363\376", "8/4="},
+    {2, "\251\234", "qZw="},
+    {2, "\103\262", "Q7I="},
+    {2, "\142\312", "Yso="},
+    {2, "\067\211", "N4k="},
+    {2, "\220\001", "kAE="},
+    {2, "\152\240", "aqA="},
+    {2, "\367\061", "9zE="},
+    {2, "\133\255", "W60="},
+    {2, "\176\035", "fh0="},
+    {2, "\032\231", "Gpk="},
+
+    {3, "\013\007\144", "Cwdk"},
+    {3, "\030\112\106", "GEpG"},
+    {3, "\047\325\046", "J9Um"},
+    {3, "\310\160\022", "yHAS"},
+    {3, "\131\100\237", "WUCf"},
+    {3, "\064\342\134", "NOJc"},
+    {3, "\010\177\004", "CH8E"},
+    {3, "\345\147\205", "5WeF"},
+    {3, "\300\343\360", "wOPw"},
+    {3, "\061\240\201", "MaCB"},
+    {3, "\225\333\044", "ldsk"},
+    {3, "\215\137\352", "jV/q"},
+    {3, "\371\147\160", "+Wdw"},
+    {3, "\030\320\051", "GNAp"},
+    {3, "\044\174\241", "JHyh"},
+    {3, "\260\127\037", "sFcf"},
+    {3, "\111\045\033", "SSUb"},
+    {3, "\202\114\107", "gkxH"},
+    {3, "\057\371\042", "L/ki"},
+    {3, "\223\247\244", "k6ek"},
+    {3, "\047\216\144", "J45k"},
+    {3, "\203\070\327", "gzjX"},
+    {3, "\247\140\072", "p2A6"},
+    {3, "\124\115\116", "VE1O"},
+    {3, "\157\162\050", "b3Io"},
+    {3, "\357\223\004", "75ME"},
+    {3, "\052\117\156", "Kk9u"},
+    {3, "\347\154\000", "52wA"},
+    {3, "\303\012\142", "wwpi"},
+    {3, "\060\035\362", "MB3y"},
+    {3, "\130\226\361", "WJbx"},
+    {3, "\173\013\071", "ews5"},
+    {3, "\336\004\027", "3gQX"},
+    {3, "\357\366\234", "7/ac"},
+    {3, "\353\304\111", "68RJ"},
+    {3, "\024\264\131", "FLRZ"},
+    {3, "\075\114\251", "PUyp"},
+    {3, "\315\031\225", "zRmV"},
+    {3, "\154\201\276", "bIG+"},
+    {3, "\200\066\072", "gDY6"},
+    {3, "\142\350\267", "Yui3"},
+    {3, "\033\000\166", "GwB2"},
+    {3, "\210\055\077", "iC0/"},
+    {3, "\341\037\124", "4R9U"},
+    {3, "\161\103\152", "cUNq"},
+    {3, "\270\142\131", "uGJZ"},
+    {3, "\337\076\074", "3z48"},
+    {3, "\375\106\362", "/Uby"},
+    {3, "\227\301\127", "l8FX"},
+    {3, "\340\002\234", "4AKc"},
+    {3, "\121\064\033", "UTQb"},
+    {3, "\157\134\143", "b1xj"},
+    {3, "\247\055\327", "py3X"},
+    {3, "\340\142\005", "4GIF"},
+    {3, "\060\260\143", "MLBj"},
+    {3, "\075\203\170", "PYN4"},
+    {3, "\143\160\016", "Y3AO"},
+    {3, "\313\013\063", "ywsz"},
+    {3, "\174\236\135", "fJ5d"},
+    {3, "\103\047\026", "QycW"},
+    {3, "\365\005\343", "9QXj"},
+    {3, "\271\160\223", "uXCT"},
+    {3, "\362\255\172", "8q16"},
+    {3, "\113\012\015", "SwoN"},
+
+    // various lengths, generated by this python script:
+    //
+    // from string import lowercase as lc
+    // for i in range(27):
+    //   print '{ %2d, "%s",%s "%s" },' % (i, lc[:i], ' ' * (26-i),
+    //                                     lc[:i].encode('base64').strip())
+
+    {0, "abcdefghijklmnopqrstuvwxyz", ""},
+    {1, "abcdefghijklmnopqrstuvwxyz", "YQ=="},
+    {2, "abcdefghijklmnopqrstuvwxyz", "YWI="},
+    {3, "abcdefghijklmnopqrstuvwxyz", "YWJj"},
+    {4, "abcdefghijklmnopqrstuvwxyz", "YWJjZA=="},
+    {5, "abcdefghijklmnopqrstuvwxyz", "YWJjZGU="},
+    {6, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVm"},
+    {7, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZw=="},
+    {8, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2g="},
+    {9, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hp"},
+    {10, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpag=="},
+    {11, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpams="},
+    {12, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamts"},
+    {13, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbQ=="},
+    {14, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW4="},
+    {15, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5v"},
+    {16, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcA=="},
+    {17, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHE="},
+    {18, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFy"},
+    {19, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFycw=="},
+    {20, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3Q="},
+    {21, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1"},
+    {22, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dg=="},
+    {23, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnc="},
+    {24, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4"},
+    {25, "abcdefghijklmnopqrstuvwxy", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eQ=="},
+    {26, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo="},
+};
+
+TEST(Base64Test, Encode) {
+  EXPECT_EQ(Base64Encode(""), "");
+  EXPECT_EQ(Base64Encode("f"), "Zg==");
+  EXPECT_EQ(Base64Encode("fo"), "Zm8=");
+  EXPECT_EQ(Base64Encode("foo"), "Zm9v");
+  EXPECT_EQ(Base64Encode("foob"), "Zm9vYg==");
+  EXPECT_EQ(Base64Encode("fooba"), "Zm9vYmE=");
+  EXPECT_EQ(Base64Encode("foobar"), "Zm9vYmFy");
+  EXPECT_EQ(Base64Encode("\xff"), "/w==");
+  EXPECT_EQ(Base64Encode("\xff\xfe"), "//4=");
+  EXPECT_EQ(Base64Encode("\xff\xfe\xfd"), "//79");
+  EXPECT_EQ(Base64Encode("\xff\xfe\xfd\xfc"), "//79/A==");
+
+  for (size_t i = 0; i < ArraySize(kPatterns); ++i) {
+    const auto& p = kPatterns[i];
+    std::string res = Base64Encode(StringView(p.decoded, p.decoded_len));
+    EXPECT_EQ(p.encoded, res);
+  }
+
+  // Error cases
+  char buf[4];
+  EXPECT_EQ(0, Base64Encode("", 0, buf, 0));
+  EXPECT_EQ(0, Base64Encode("", 0, buf, 1));
+  EXPECT_EQ(-1, Base64Encode("a", 1, buf, 0));
+  EXPECT_EQ(-1, Base64Encode("abc", 3, buf, 0));
+  EXPECT_EQ(-1, Base64Encode("abc", 3, buf, 1));
+  EXPECT_EQ(-1, Base64Encode("abc", 3, buf, 3));
+  EXPECT_EQ(4, Base64Encode("abc", 3, buf, 4));
+}
+
+TEST(Base64Test, Decode) {
+  EXPECT_EQ(Base64Decode(""), "");
+  EXPECT_EQ(Base64Decode("Zg=="), "f");
+  EXPECT_EQ(Base64Decode("Zg="), "f");
+  EXPECT_EQ(Base64Decode("Zg"), "f");
+  EXPECT_EQ(Base64Decode("Zm8="), "fo");
+  EXPECT_EQ(Base64Decode("Zm8"), "fo");
+  EXPECT_EQ(Base64Decode("Zm9v"), "foo");
+  EXPECT_EQ(Base64Decode("Zm9vYg=="), "foob");
+  EXPECT_EQ(Base64Decode("Zm9vYg="), "foob");
+  EXPECT_EQ(Base64Decode("Zm9vYg"), "foob");
+  EXPECT_EQ(Base64Decode("Zm9vYmE="), "fooba");
+  EXPECT_EQ(Base64Decode("Zm9vYmE"), "fooba");
+  EXPECT_EQ(Base64Decode("Zm9vYmFy"), "foobar");
+  EXPECT_EQ(Base64Decode("/w=="), "\xff");
+  EXPECT_EQ(Base64Decode("/w="), "\xff");
+  EXPECT_EQ(Base64Decode("/w"), "\xff");
+  EXPECT_EQ(Base64Decode("//4="), "\xff\xfe");
+  EXPECT_EQ(Base64Decode("//4"), "\xff\xfe");
+  EXPECT_EQ(Base64Decode("//79"), "\xff\xfe\xfd");
+  EXPECT_EQ(Base64Decode("//79/A=="), "\xff\xfe\xfd\xfc");
+  EXPECT_EQ(Base64Decode("//79/A="), "\xff\xfe\xfd\xfc");
+  EXPECT_EQ(Base64Decode("//79/A"), "\xff\xfe\xfd\xfc");
+
+  for (size_t i = 0; i < ArraySize(kPatterns); ++i) {
+    const auto& p = kPatterns[i];
+    Optional<std::string> dec = Base64Decode(StringView(p.encoded));
+    EXPECT_TRUE(dec.has_value());
+    EXPECT_EQ(dec.value(), StringView(p.decoded, p.decoded_len).ToStdString());
+  }
+
+  // Error cases:
+  EXPECT_EQ(Base64Decode("Z"), nullopt);
+  EXPECT_EQ(Base64Decode("Zm9vY"), nullopt);
+
+  uint8_t buf[4];
+  EXPECT_EQ(Base64Decode("", 0, buf, 2), 0);       // Valid, 0 len.
+  EXPECT_EQ(Base64Decode("Z", 1, buf, 1), -1);     // Invalid input.
+  EXPECT_EQ(Base64Decode("Zg==", 4, buf, 1), -1);  // Not enough dst space.
+}
+
+}  // namespace
+}  // namespace base
+}  // namespace perfetto
diff --git a/src/base/string_utils.cc b/src/base/string_utils.cc
index af663b8..5efca77 100644
--- a/src/base/string_utils.cc
+++ b/src/base/string_utils.cc
@@ -33,11 +33,6 @@
 
 namespace perfetto {
 namespace base {
-namespace {
-constexpr char kBase64Table[] =
-    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-    "abcdefghijklmnopqrstuvwxyz0123456789+/";
-}
 
 // Locale-independant as possible version of strtod.
 double StrToD(const char* nptr, char** endptr) {
@@ -215,44 +210,6 @@
   return idx == std::string::npos ? str : str.substr(idx);
 }
 
-std::string Base64Encode(const void* raw, size_t size) {
-  // The following three cases are based on the tables in the example
-  // section in https://en.wikipedia.org/wiki/Base64. We process three
-  // input bytes at a time, emitting 4 output bytes at a time.
-  const uint8_t* ptr = static_cast<const uint8_t*>(raw);
-  size_t ii = 0;
-
-  std::string out;
-  out.reserve((size + 2) * 4 / 3);
-
-  // While possible, process three input bytes.
-  for (; ii + 3 <= size; ii += 3) {
-    uint32_t twentyfour_bits =
-        (uint32_t(ptr[ii]) << 16) | (uint32_t(ptr[ii + 1]) << 8) | ptr[ii + 2];
-    out.push_back(kBase64Table[(twentyfour_bits >> 18)]);
-    out.push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]);
-    out.push_back(kBase64Table[(twentyfour_bits >> 6) & 0x3f]);
-    out.push_back(kBase64Table[twentyfour_bits & 0x3f]);
-  }
-  if (ii + 2 <= size) {  // Process two input bytes.
-    uint32_t twentyfour_bits =
-        (uint32_t(ptr[ii]) << 16) | (uint32_t(ptr[ii + 1]) << 8);
-    out.push_back(kBase64Table[(twentyfour_bits >> 18)]);
-    out.push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]);
-    out.push_back(kBase64Table[(twentyfour_bits >> 6) & 0x3f]);
-    out.push_back('=');  // Emit padding.
-    return out;
-  }
-  if (ii + 1 <= size) {  // Process a single input byte.
-    uint32_t twentyfour_bits = (uint32_t(ptr[ii]) << 16);
-    out.push_back(kBase64Table[(twentyfour_bits >> 18)]);
-    out.push_back(kBase64Table[(twentyfour_bits >> 12) & 0x3f]);
-    out.push_back('=');  // Emit padding.
-    out.push_back('=');  // Emit padding.
-  }
-  return out;
-}
-
 size_t SprintfTrunc(char* dst, size_t dst_size, const char* fmt, ...) {
   if (PERFETTO_UNLIKELY(dst_size) == 0)
     return 0;
diff --git a/src/base/string_utils_unittest.cc b/src/base/string_utils_unittest.cc
index 0787802..39803ff 100644
--- a/src/base/string_utils_unittest.cc
+++ b/src/base/string_utils_unittest.cc
@@ -303,29 +303,6 @@
   EXPECT_EQ(TrimLeading(" aaaaa     "), "aaaaa     ");
 }
 
-TEST(StringUtilsTest, Base64Encode) {
-  auto base64_encode = [](const std::string& str) {
-    return Base64Encode(str.c_str(), str.size());
-  };
-
-  EXPECT_EQ(base64_encode(""), "");
-  EXPECT_EQ(base64_encode("f"), "Zg==");
-  EXPECT_EQ(base64_encode("fo"), "Zm8=");
-  EXPECT_EQ(base64_encode("foo"), "Zm9v");
-  EXPECT_EQ(base64_encode("foob"), "Zm9vYg==");
-  EXPECT_EQ(base64_encode("fooba"), "Zm9vYmE=");
-  EXPECT_EQ(base64_encode("foobar"), "Zm9vYmFy");
-
-  EXPECT_EQ(Base64Encode("foo\0bar", 7), "Zm9vAGJhcg==");
-
-  std::vector<uint8_t> buffer = {0x04, 0x53, 0x42, 0x35,
-                                 0x32, 0xFF, 0x00, 0xFE};
-  EXPECT_EQ(Base64Encode(buffer.data(), buffer.size()), "BFNCNTL/AP4=");
-
-  buffer = {0xfb, 0xf0, 0x3e, 0x07, 0xfc};
-  EXPECT_EQ(Base64Encode(buffer.data(), buffer.size()), "+/A+B/w=");
-}
-
 TEST(StringUtilsTest, StringCopy) {
   // Nothing should be written when |dst_size| = 0.
   {
diff --git a/src/trace_processor/importers/proto/metadata_module.cc b/src/trace_processor/importers/proto/metadata_module.cc
index b811320..cfb644b 100644
--- a/src/trace_processor/importers/proto/metadata_module.cc
+++ b/src/trace_processor/importers/proto/metadata_module.cc
@@ -16,7 +16,7 @@
 
 #include "src/trace_processor/importers/proto/metadata_module.h"
 
-#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/base64.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/proto/metadata_tracker.h"