pw_tokenizer: Make Base64 encoding easier to use

- Have PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE represent the whole encoded
  buffer, including the 4-byte token. This makes the value easier to use
  directly since the token is already accounted for.
- Update the Base64 encoding functions to always add a null terminator
  to the Base64 output.
- Provide a pw::tokenizer::PrefixedBase64Encode overload that allocates
  the buffer using pw::Vector.

Change-Id: Id78ef06a7d2111e7dfe5604ee091975be40ceed4
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/19720
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Commit-Queue: Wyatt Hepler <hepler@google.com>
diff --git a/pw_tokenizer/base64_test.cc b/pw_tokenizer/base64_test.cc
index d6ff56e..e751b81 100644
--- a/pw_tokenizer/base64_test.cc
+++ b/pw_tokenizer/base64_test.cc
@@ -27,7 +27,12 @@
 
 class PrefixedBase64 : public ::testing::Test {
  protected:
-  PrefixedBase64() : binary_{}, base64_{} {}
+  static constexpr char kUnset = '#';
+
+  PrefixedBase64() {
+    std::memset(binary_, kUnset, sizeof(binary_));
+    std::memset(base64_, kUnset, sizeof(base64_));
+  }
 
   byte binary_[32];
   char base64_[32];
@@ -62,6 +67,7 @@
   for (auto& [binary, base64] : kTestData) {
     EXPECT_EQ(base64.size(), PrefixedBase64Encode(binary, base64_));
     ASSERT_EQ(base64, base64_);
+    EXPECT_EQ('\0', base64_[base64.size()]);
   }
 }
 
@@ -73,7 +79,47 @@
 TEST_F(PrefixedBase64, Encode_EmptyOutput_WritesNothing) {
   EXPECT_EQ(0u,
             PrefixedBase64Encode(kTestData[5].binary, std::span(base64_, 0)));
+  EXPECT_EQ(kUnset, base64_[0]);
+}
+
+TEST_F(PrefixedBase64, Encode_SingleByteOutput_OnlyNullTerminates) {
+  EXPECT_EQ(0u,
+            PrefixedBase64Encode(kTestData[5].binary, std::span(base64_, 1)));
   EXPECT_EQ('\0', base64_[0]);
+  EXPECT_EQ(kUnset, base64_[1]);
+}
+
+TEST_F(PrefixedBase64, Encode_NoRoomForNullAfterMessage_OnlyNullTerminates) {
+  EXPECT_EQ(
+      0u,
+      PrefixedBase64Encode(kTestData[5].binary,
+                           std::span(base64_, kTestData[5].base64.size())));
+  EXPECT_EQ('\0', base64_[0]);
+  EXPECT_EQ(kUnset, base64_[1]);
+}
+
+TEST_F(PrefixedBase64, EncodeToVector_EmptyInput_WritesPrefix) {
+  auto buffer = PrefixedBase64Encode(std::span<byte>());
+  ASSERT_EQ(1u, buffer.size());
+  EXPECT_EQ('$', buffer[0]);
+  EXPECT_EQ('\0', buffer[1]);
+}
+
+TEST_F(PrefixedBase64, EncodeToVector_Successful) {
+  auto buffer = PrefixedBase64Encode(kTestData[5].binary);
+  ASSERT_EQ(buffer.size(), kTestData[5].base64.size());
+  EXPECT_EQ(
+      0, std::memcmp(buffer.data(), kTestData[5].base64.data(), buffer.size()));
+  EXPECT_EQ('\0', buffer[buffer.size()]);
+}
+
+TEST_F(PrefixedBase64, EncodeToVector_VectorTooSmall_OnlyNullTerminates) {
+  constexpr byte big[Base64EncodedSize(
+      PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE_BYTES + 1)] = {};
+
+  auto buffer = PrefixedBase64Encode(big);
+  ASSERT_EQ(0u, buffer.size());
+  EXPECT_EQ('\0', buffer[0]);
 }
 
 TEST_F(PrefixedBase64, Decode) {
@@ -85,18 +131,18 @@
 
 TEST_F(PrefixedBase64, Decode_EmptyInput_WritesNothing) {
   EXPECT_EQ(0u, PrefixedBase64Decode({}, binary_));
-  EXPECT_EQ(byte{0}, binary_[0]);
+  EXPECT_EQ(byte{kUnset}, binary_[0]);
 }
 
 TEST_F(PrefixedBase64, Decode_OnlyPrefix_WritesNothing) {
   EXPECT_EQ(0u, PrefixedBase64Decode("$", binary_));
-  EXPECT_EQ(byte{0}, binary_[0]);
+  EXPECT_EQ(byte{kUnset}, binary_[0]);
 }
 
 TEST_F(PrefixedBase64, Decode_EmptyOutput_WritesNothing) {
   EXPECT_EQ(0u,
             PrefixedBase64Decode(kTestData[5].base64, std::span(binary_, 0)));
-  EXPECT_EQ(byte{0}, binary_[0]);
+  EXPECT_EQ(byte{kUnset}, binary_[0]);
 }
 
 TEST_F(PrefixedBase64, Decode_OutputTooSmall_WritesNothing) {
@@ -104,7 +150,7 @@
   EXPECT_EQ(0u,
             PrefixedBase64Decode(item.base64,
                                  std::span(binary_, item.binary.size() - 1)));
-  EXPECT_EQ(byte{0}, binary_[0]);
+  EXPECT_EQ(byte{kUnset}, binary_[0]);
 }
 
 TEST(PrefixedBase64, DecodeInPlace) {