pw_tokenizer: Base64EncodedBufferSize function

Rename Base64EncodedSize to Base64EncodedBufferSize and have it include
the null terminator. This makes it easier to use directly to size
buffers for Base64 encoding.

Change-Id: Ied24eed67f8d3c5e42a405c98e0b20b30d543536
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/20300
Commit-Queue: Wyatt Hepler <hepler@google.com>
Reviewed-by: Ewout van Bekkum <ewout@google.com>
diff --git a/pw_tokenizer/base64.cc b/pw_tokenizer/base64.cc
index 7aab4af..2b87161 100644
--- a/pw_tokenizer/base64.cc
+++ b/pw_tokenizer/base64.cc
@@ -22,9 +22,9 @@
     void* output_buffer,
     size_t output_buffer_size_bytes) {
   char* output = static_cast<char*>(output_buffer);
-  const size_t encoded_size = Base64EncodedSize(binary_size_bytes);
+  const size_t encoded_size = Base64EncodedBufferSize(binary_size_bytes);
 
-  if (output_buffer_size_bytes < encoded_size + sizeof('\0')) {
+  if (output_buffer_size_bytes < encoded_size) {
     if (output_buffer_size_bytes > 0u) {
       output[0] = '\0';
     }
@@ -36,8 +36,8 @@
   base64::Encode(std::span(static_cast<const std::byte*>(binary_message),
                            binary_size_bytes),
                  &output[1]);
-  output[encoded_size] = '\0';
-  return encoded_size;
+  output[encoded_size - 1] = '\0';
+  return encoded_size - sizeof('\0');  // exclude the null terminator
 }
 
 extern "C" size_t pw_tokenizer_PrefixedBase64Decode(const void* base64_message,
diff --git a/pw_tokenizer/base64_test.cc b/pw_tokenizer/base64_test.cc
index e751b81..fbbd064 100644
--- a/pw_tokenizer/base64_test.cc
+++ b/pw_tokenizer/base64_test.cc
@@ -74,6 +74,7 @@
 TEST_F(PrefixedBase64, Encode_EmptyInput_WritesPrefix) {
   EXPECT_EQ(1u, PrefixedBase64Encode(std::span<byte>(), base64_));
   EXPECT_EQ('$', base64_[0]);
+  EXPECT_EQ('\0', base64_[1]);
 }
 
 TEST_F(PrefixedBase64, Encode_EmptyOutput_WritesNothing) {
@@ -114,7 +115,7 @@
 }
 
 TEST_F(PrefixedBase64, EncodeToVector_VectorTooSmall_OnlyNullTerminates) {
-  constexpr byte big[Base64EncodedSize(
+  constexpr byte big[Base64EncodedBufferSize(
       PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE_BYTES + 1)] = {};
 
   auto buffer = PrefixedBase64Encode(big);
@@ -122,6 +123,19 @@
   EXPECT_EQ('\0', buffer[0]);
 }
 
+TEST_F(PrefixedBase64, Base64EncodedBufferSize_Empty_RoomForPrefixAndNull) {
+  EXPECT_EQ(2u, Base64EncodedBufferSize(0));
+}
+
+TEST_F(PrefixedBase64, Base64EncodedBufferSize_PositiveSizes) {
+  for (unsigned i = 1; i <= 3; ++i) {
+    EXPECT_EQ(6u, Base64EncodedBufferSize(i));
+  }
+  for (unsigned i = 4; i <= 6; ++i) {
+    EXPECT_EQ(10u, Base64EncodedBufferSize(i));
+  }
+}
+
 TEST_F(PrefixedBase64, Decode) {
   for (auto& [binary, base64] : kTestData) {
     EXPECT_EQ(binary.size(), PrefixedBase64Decode(base64, binary_));
diff --git a/pw_tokenizer/public/pw_tokenizer/base64.h b/pw_tokenizer/public/pw_tokenizer/base64.h
index 42d1f5e..73ae3c2 100644
--- a/pw_tokenizer/public/pw_tokenizer/base64.h
+++ b/pw_tokenizer/public/pw_tokenizer/base64.h
@@ -70,22 +70,24 @@
 #include "pw_base64/base64.h"
 #include "pw_containers/vector.h"
 #include "pw_tokenizer/config.h"
+#include "pw_tokenizer/tokenize.h"
 
 namespace pw::tokenizer {
 
 inline constexpr char kBase64Prefix = PW_TOKENIZER_BASE64_PREFIX;
 
-// Returns the size of a Base64-encoded tokenized message. Includes the prefix
-// character ($) and the encoded data, but excludes the null terminator.
-constexpr size_t Base64EncodedSize(size_t data) {
-  return sizeof(kBase64Prefix) + base64::EncodedSize(data);
+// Returns the size of a tokenized message (token + arguments) when encoded as
+// prefixed Base64. This can be used to size a buffer for encoding. Includes
+// room for the prefix character ($), encoded message, and a null terminator.
+constexpr size_t Base64EncodedBufferSize(size_t message_size) {
+  return sizeof(kBase64Prefix) + base64::EncodedSize(message_size) +
+         sizeof('\0');
 }
 
 // The minimum buffer size that can hold a tokenized message that is
 // PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE_BYTES long encoded as prefixed Base64.
-inline constexpr size_t kBase64EncodedBufferSize =
-    Base64EncodedSize(PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE_BYTES) +
-    sizeof('\0');
+inline constexpr size_t kDefaultBase64EncodedBufferSize =
+    Base64EncodedBufferSize(PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE_BYTES);
 
 // Encodes a binary tokenized message as prefixed Base64 with a null terminator.
 // Returns the encoded string length (excluding the null terminator). Returns 0
@@ -109,29 +111,30 @@
 // PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE_BYTES long encoded as prefixed Base64.
 // The returned vector always contains a null-terminated Base64 string. If
 // size() is zero, the binary message did not fit.
-template <size_t buffer_size = kBase64EncodedBufferSize>
+template <size_t buffer_size = kDefaultBase64EncodedBufferSize>
 Vector<char, buffer_size> PrefixedBase64Encode(
     std::span<const std::byte> binary_message) {
-  static_assert(buffer_size >= Base64EncodedSize(sizeof(uint32_t)));
+  static_assert(buffer_size >= Base64EncodedBufferSize(sizeof(Token)),
+                "Buffer must be large enough for at least the uint32_t token");
 
   Vector<char, buffer_size> output;
-  const size_t encoded_size = Base64EncodedSize(binary_message.size());
+  const size_t encoded_size = Base64EncodedBufferSize(binary_message.size());
 
   // Make sure the encoded data and a null terminator can fit.
-  if (encoded_size + sizeof('\0') > buffer_size) {
+  if (buffer_size < encoded_size) {
     output[0] = '\0';
     return output;
   }
 
-  output.resize(encoded_size);
+  output.resize(encoded_size - 1);  // exclude null terminator
   output[0] = kBase64Prefix;
   base64::Encode(binary_message, &output[1]);
-  output[encoded_size] = '\0';
+  output[encoded_size - 1] = '\0';
   return output;
 }
 
 // Encode to a pw::Vector from std::span<const uint8_t>.
-template <size_t buffer_size = kBase64EncodedBufferSize>
+template <size_t buffer_size = kDefaultBase64EncodedBufferSize>
 Vector<char, buffer_size> PrefixedBase64Encode(
     std::span<const uint8_t> binary_message) {
   return PrefixedBase64Encode(std::as_bytes(binary_message));