pw_protobuf: Use pw::InlineString for string fields

- Use pw::InlineString instead of pw::Vector<char> for string fields.
- Temporarily overload pw::string::Copy() for pw::InlineString<> to
  simplify transitioning away from pw::Vector<char>.

Requires: pigweed-internal:31961
Change-Id: I1416f2d85471da4c174052a085cd896a0b1dc084
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/110111
Commit-Queue: Wyatt Hepler <hepler@google.com>
Reviewed-by: Armando Montanez <amontanez@google.com>
diff --git a/pw_protobuf/BUILD.bazel b/pw_protobuf/BUILD.bazel
index 3b287c7..b4dbf07 100644
--- a/pw_protobuf/BUILD.bazel
+++ b/pw_protobuf/BUILD.bazel
@@ -68,6 +68,7 @@
         "//pw_status",
         "//pw_stream",
         "//pw_stream:interval_reader",
+        "//pw_string:string",
         "//pw_varint",
         "//pw_varint:stream",
     ],
diff --git a/pw_protobuf/BUILD.gn b/pw_protobuf/BUILD.gn
index e6ec3f8..a68ae74 100644
--- a/pw_protobuf/BUILD.gn
+++ b/pw_protobuf/BUILD.gn
@@ -79,6 +79,7 @@
     "message.cc",
     "stream_decoder.cc",
   ]
+  deps = [ "$dir_pw_string:string" ]
 }
 
 pw_source_set("bytes_utils") {
diff --git a/pw_protobuf/CMakeLists.txt b/pw_protobuf/CMakeLists.txt
index 9bdc38f..f8d1966 100644
--- a/pw_protobuf/CMakeLists.txt
+++ b/pw_protobuf/CMakeLists.txt
@@ -54,6 +54,8 @@
     pw_stream.interval_reader
     pw_varint
     pw_varint.stream
+  PRIVATE_DEPS
+    pw_string.string
   SOURCES
     decoder.cc
     encoder.cc
diff --git a/pw_protobuf/docs.rst b/pw_protobuf/docs.rst
index 3a816c0..b25c02e 100644
--- a/pw_protobuf/docs.rst
+++ b/pw_protobuf/docs.rst
@@ -789,10 +789,10 @@
       pw::Vector<std::byte, 64> serial_number;
     };
 
-* `string` fields are represented by ``pw::Vector`` when the ``max_size`` option
-  is set for that field. Since the size is provided, the string is not
-  automatically null-terminated. :ref:`module-pw_string` provides utility
-  methods to copy string data into and out of this vector.
+* `string` fields are represented by a :cpp:type:`pw::InlineString` when the
+  ``max_size`` option is set for that field. The string can hold up to
+  ``max_size`` characters, and is always null terminated. The null terminator is
+  not counted in ``max_size``.
 
   .. code::
 
@@ -807,7 +807,7 @@
   .. code:: c++
 
     struct Employee::Message {
-      pw::Vector<char, 128> name;
+      pw::InlineString<128> name;
     };
 
 * Nested messages with a dependency cycle, repeated scalar fields without a
diff --git a/pw_protobuf/encoder.cc b/pw_protobuf/encoder.cc
index 54a4677..7c8491d 100644
--- a/pw_protobuf/encoder.cc
+++ b/pw_protobuf/encoder.cc
@@ -30,6 +30,7 @@
 #include "pw_status/try.h"
 #include "pw_stream/memory_stream.h"
 #include "pw_stream/stream.h"
+#include "pw_string/string.h"
 #include "pw_varint/varint.h"
 
 namespace pw::protobuf {
@@ -537,17 +538,18 @@
             PW_TRY(WriteLengthDelimitedField(field.field_number(), values));
           }
         } else {
-          // bytes or string field with a maximum size. Struct member is a
-          // pw::Vector<std::byte>. Use the contents as a span and call
-          // WriteLengthDelimitedField() to output it to the stream.
+          // bytes or string field with a maximum size. Struct member is
+          // pw::Vector<std::byte> for bytes or pw::InlineString<> for string.
+          // Use the contents as a span and call WriteLengthDelimitedField() to
+          // output it to the stream.
           PW_CHECK(field.elem_size() == sizeof(std::byte),
                    "Mismatched message field type and size");
-          const auto* vector =
-              reinterpret_cast<const pw::Vector<const std::byte>*>(
-                  values.data());
-          if (!vector->empty()) {
-            PW_TRY(WriteLengthDelimitedField(
-                field.field_number(), span(vector->data(), vector->size())));
+          if (field.is_string()) {
+            PW_TRY(WriteStringOrBytes<const InlineString<>>(
+                field.field_number(), values.data()));
+          } else {
+            PW_TRY(WriteStringOrBytes<const Vector<const std::byte>>(
+                field.field_number(), values.data()));
           }
         }
         break;
diff --git a/pw_protobuf/public/pw_protobuf/encoder.h b/pw_protobuf/public/pw_protobuf/encoder.h
index 7e8becb..ee3d52b 100644
--- a/pw_protobuf/public/pw_protobuf/encoder.h
+++ b/pw_protobuf/public/pw_protobuf/encoder.h
@@ -747,6 +747,16 @@
                           span<const std::byte> values,
                           size_t elem_size);
 
+  template <typename Container>
+  Status WriteStringOrBytes(uint32_t field_number,
+                            const std::byte* raw_container) {
+    const auto& container = *reinterpret_cast<Container*>(raw_container);
+    if (container.empty()) {
+      return OkStatus();
+    }
+    return WriteLengthDelimitedField(field_number, as_bytes(span(container)));
+  }
+
   // Checks if a write is invalid or will cause the encoder to enter an error
   // state, and preemptively sets this encoder's status to that error to block
   // the write. Only the first error encountered is tracked.
diff --git a/pw_protobuf/public/pw_protobuf/stream_decoder.h b/pw_protobuf/public/pw_protobuf/stream_decoder.h
index 6ca8803..edbb877 100644
--- a/pw_protobuf/public/pw_protobuf/stream_decoder.h
+++ b/pw_protobuf/public/pw_protobuf/stream_decoder.h
@@ -694,6 +694,18 @@
     }
   }
 
+  template <typename Container>
+  Status ReadStringOrBytesField(std::byte* raw_container) {
+    auto& container = *reinterpret_cast<Container*>(raw_container);
+    if (container.capacity() < delimited_field_size_) {
+      return Status::ResourceExhausted();
+    }
+    container.resize(container.capacity());
+    const auto sws = ReadDelimitedField(as_writable_bytes(span(container)));
+    container.resize(sws.size());
+    return sws.status();
+  }
+
   Status CheckOkToRead(WireType type);
 
   stream::Reader& reader_;
diff --git a/pw_protobuf/py/pw_protobuf/codegen_pwpb.py b/pw_protobuf/py/pw_protobuf/codegen_pwpb.py
index 55116b1..8e12cd5 100644
--- a/pw_protobuf/py/pw_protobuf/codegen_pwpb.py
+++ b/pw_protobuf/py/pw_protobuf/codegen_pwpb.py
@@ -353,6 +353,15 @@
         """True if this field is a string field (as opposed to bytes)."""
         return False
 
+    @staticmethod
+    def repeated_field_container(type_name: str, max_size: int) -> str:
+        """Returns the container type used for repeated fields.
+
+        Defaults to ::pw::Vector<type, max_size>. String fields use
+        ::pw::InlineString<max_size> instead.
+        """
+        return f'::pw::Vector<{type_name}, {max_size}>'
+
     def use_callback(self) -> bool:  # pylint: disable=no-self-use
         """Returns whether the decoder should use a callback."""
         options = self._field.options()
@@ -411,7 +420,7 @@
                                                 max_size), self.name())
 
         # Otherwise prefer pw::Vector for repeated fields.
-        return ('::pw::Vector<{}, {}>'.format(self.type_name(from_root),
+        return (self.repeated_field_container(self.type_name(from_root),
                                               max_size), self.name())
 
     def _varint_type_table_entry(self) -> str:
@@ -1583,6 +1592,10 @@
     def is_string(self) -> bool:
         return True
 
+    @staticmethod
+    def repeated_field_container(type_name: str, max_size: int) -> str:
+        return f'::pw::InlineBasicString<{type_name}, {max_size}>'
+
     def _size_fn(self) -> str:
         # This uses the WithoutValue method to ensure that the maximum length
         # of the delimited field size varint is used. This accounts for scratch
@@ -2265,6 +2278,7 @@
     output.write_line('#include "pw_span/span.h"')
     output.write_line('#include "pw_status/status.h"')
     output.write_line('#include "pw_status/status_with_size.h"')
+    output.write_line('#include "pw_string/string.h"')
 
     for imported_file in file_descriptor_proto.dependency:
         generated_header = _proto_filename_to_generated_header(imported_file)
diff --git a/pw_protobuf/stream_decoder.cc b/pw_protobuf/stream_decoder.cc
index a781cf6..c94b8e8 100644
--- a/pw_protobuf/stream_decoder.cc
+++ b/pw_protobuf/stream_decoder.cc
@@ -32,6 +32,7 @@
 #include "pw_status/status.h"
 #include "pw_status/status_with_size.h"
 #include "pw_status/try.h"
+#include "pw_string/string.h"
 #include "pw_varint/stream.h"
 #include "pw_varint/varint.h"
 
@@ -679,21 +680,15 @@
                    "Mismatched message field type and size");
           PW_TRY(ReadDelimitedField(out));
         } else {
-          // bytes or string field with a maximum size. Struct member is a
-          // pw::Vector<std::byte>. There's no vector equivalent of
-          // ReadDelimitedField() to call, so just implement what that would
-          // look like here (since it wouldn't be used anywhere else).
+          // bytes or string field with a maximum size. The struct member is
+          // pw::Vector<std::byte> for bytes or pw::InlineString<> for string.
           PW_CHECK(field->elem_size() == sizeof(std::byte),
                    "Mismatched message field type and size");
-          auto* vector = reinterpret_cast<pw::Vector<std::byte>*>(out.data());
-          if (vector->capacity() < delimited_field_size_) {
-            return Status::ResourceExhausted();
+          if (field->is_string()) {
+            PW_TRY(ReadStringOrBytesField<pw::InlineString<>>(out.data()));
+          } else {
+            PW_TRY(ReadStringOrBytesField<pw::Vector<std::byte>>(out.data()));
           }
-          vector->resize(vector->capacity());
-          const auto sws =
-              ReadDelimitedField(span(vector->data(), vector->size()));
-          vector->resize(sws.size());
-          PW_TRY(sws);
         }
         break;
       }
diff --git a/pw_protobuf_compiler/proto.cmake b/pw_protobuf_compiler/proto.cmake
index 7783461..473a044 100644
--- a/pw_protobuf_compiler/proto.cmake
+++ b/pw_protobuf_compiler/proto.cmake
@@ -196,6 +196,7 @@
       pw_build
       pw_protobuf
       pw_span
+      pw_string.string
       ${DEPS}
   )
   add_dependencies("${NAME}.pwpb" "${NAME}._generate.pwpb")
diff --git a/pw_protobuf_compiler/proto.gni b/pw_protobuf_compiler/proto.gni
index 41068dc..11cb828 100644
--- a/pw_protobuf_compiler/proto.gni
+++ b/pw_protobuf_compiler/proto.gni
@@ -199,6 +199,7 @@
     deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
     public_deps = [
                     "$dir_pw_containers:vector",
+                    "$dir_pw_string:string",
                     dir_pw_assert,
                     dir_pw_function,
                     dir_pw_preprocessor,
diff --git a/pw_protobuf_compiler/pw_proto_library.bzl b/pw_protobuf_compiler/pw_proto_library.bzl
index 3cf3d9d..034ada5 100644
--- a/pw_protobuf_compiler/pw_proto_library.bzl
+++ b/pw_protobuf_compiler/pw_proto_library.bzl
@@ -309,6 +309,7 @@
             "//pw_result",
             "//pw_span",
             "//pw_status",
+            "//pw_string:string",
         ],
         "include_nanopb_dep": False,
         "include_pwpb_dep": False,
diff --git a/pw_protobuf_compiler/pwpb_test.cc b/pw_protobuf_compiler/pwpb_test.cc
index 10c3962..c023697 100644
--- a/pw_protobuf_compiler/pwpb_test.cc
+++ b/pw_protobuf_compiler/pwpb_test.cc
@@ -20,5 +20,5 @@
   EXPECT_EQ(point.x, 4u);
   EXPECT_EQ(point.y, 8u);
   EXPECT_EQ(point.name.size(), 5u);
-  EXPECT_EQ(point.name.view(), "point");
+  EXPECT_EQ(point.name, "point");
 }
diff --git a/pw_rpc/test_helpers_test.cc b/pw_rpc/test_helpers_test.cc
index d5d1e7e..335424e 100644
--- a/pw_rpc/test_helpers_test.cc
+++ b/pw_rpc/test_helpers_test.cc
@@ -70,7 +70,7 @@
     return notifier_.try_acquire_for(duration);
   }
 
-  pw::Result<pw::Vector<char, 64>> LastEcho() const {
+  pw::Result<pw::InlineString<64>> LastEcho() const {
     std::lock_guard<pw::sync::InterruptSpinLock> lock(lock_);
     return last_echo_;
   }
@@ -79,7 +79,7 @@
   pw_rpc::pwpb::EchoService::Client& echo_client_;
   PwpbUnaryReceiver<EchoMessage::Message> call_;
   pw::sync::TimedThreadNotification notifier_;
-  pw::Result<pw::Vector<char, 64>> last_echo_ PW_GUARDED_BY(lock_);
+  pw::Result<pw::InlineString<64>> last_echo_ PW_GUARDED_BY(lock_);
   mutable pw::sync::InterruptSpinLock lock_;
 };
 
@@ -108,9 +108,9 @@
   // a separate thread we still need to wait with the timeout.
   ASSERT_TRUE(entity.WaitForEcho(kWaitForEchoTimeout));
 
-  pw::Result<pw::Vector<char, 64>> result = entity.LastEcho();
+  pw::Result<pw::InlineString<64>> result = entity.LastEcho();
   ASSERT_TRUE(result.ok());
-  EXPECT_EQ(result.value(), (pw::Vector<char, 64>{"Hello"}));
+  EXPECT_EQ(result.value(), "Hello");
 }
 
 TEST(RpcTestHelpersTest, SendResponseIfCalledNotOk) {
diff --git a/pw_software_update/bundled_update_service_pwpb.cc b/pw_software_update/bundled_update_service_pwpb.cc
index 29dbafe..0f26b07 100644
--- a/pw_software_update/bundled_update_service_pwpb.cc
+++ b/pw_software_update/bundled_update_service_pwpb.cc
@@ -98,7 +98,7 @@
 
   // Enable bundle transfer.
   Result<uint32_t> possible_transfer_id =
-      backend_.EnableBundleTransferHandler(request.bundle_filename.view());
+      backend_.EnableBundleTransferHandler(request.bundle_filename);
   if (!possible_transfer_id.ok()) {
     SET_ERROR(BundledUpdateResult::Enum::kTransferFailed,
               "Couldn't enable bundle transfer");
diff --git a/pw_string/BUILD.bazel b/pw_string/BUILD.bazel
index b2b5cc8..8ddf733 100644
--- a/pw_string/BUILD.bazel
+++ b/pw_string/BUILD.bazel
@@ -115,6 +115,7 @@
     includes = ["public"],
     deps = [
         ":pw_string",
+        ":string",
         "//pw_containers:vector",
         "//pw_status",
     ],
diff --git a/pw_string/BUILD.gn b/pw_string/BUILD.gn
index 55cebfa..c8b55c8 100644
--- a/pw_string/BUILD.gn
+++ b/pw_string/BUILD.gn
@@ -124,6 +124,7 @@
   public = [ "public/pw_string/vector.h" ]
   public_deps = [
     ":pw_string",
+    ":string",
     "$dir_pw_containers:vector",
     "$dir_pw_status",
     dir_pw_span,
diff --git a/pw_string/public/pw_string/vector.h b/pw_string/public/pw_string/vector.h
index bd25b9472..e37e8a9 100644
--- a/pw_string/public/pw_string/vector.h
+++ b/pw_string/public/pw_string/vector.h
@@ -13,13 +13,14 @@
 // the License.
 #pragma once
 
+#include <algorithm>
 #include <cstddef>
 #include <string_view>
 
 #include "pw_containers/vector.h"
-#include "pw_span/span.h"
 #include "pw_status/status.h"
 #include "pw_status/status_with_size.h"
+#include "pw_string/string.h"
 #include "pw_string/util.h"
 
 namespace pw {
@@ -48,11 +49,6 @@
       copied);
 }
 
-inline StatusWithSize Copy(const char* source, pw::Vector<char>& dest) {
-  PW_DASSERT(source != nullptr);
-  return Copy(ClampedCString(source, dest.capacity()), dest);
-}
-
 inline StatusWithSize Copy(const pw::Vector<char>& source, span<char> dest) {
   if (dest.empty()) {
     return StatusWithSize::ResourceExhausted();
@@ -67,5 +63,30 @@
       copied);
 }
 
+inline StatusWithSize Copy(const char* source, pw::Vector<char>& dest) {
+  PW_DASSERT(source != nullptr);
+  return Copy(ClampedCString(source, dest.capacity()), dest);
+}
+
+// Overload for InlineString to simplify the transition from using Vector<char>
+// for protobuf string fields. An external Copy() function is not necessary for
+// InlineString; use assign() or the assignment operator instead.
+//
+// This function will be removed once projects switch to pw::InlineString<>.
+inline StatusWithSize Copy(const std::string_view& source,
+                           pw::InlineString<>& dest) {
+  size_t copied = std::min(source.size(), static_cast<size_t>(dest.capacity()));
+  dest.assign(source, 0, copied);
+  return StatusWithSize(
+      copied == source.size() ? OkStatus() : Status::ResourceExhausted(),
+      copied);
+}
+
+inline StatusWithSize Copy(const char* source, pw::InlineString<>& dest) {
+  PW_DASSERT(source != nullptr);
+  // Clamp to capacity + 1 so strings larger than the capacity yield an error.
+  return Copy(ClampedCString(source, dest.capacity() + 1), dest);
+}
+
 }  // namespace string
 }  // namespace pw