pw_protobuf: Support encoding from pw::Vector

Extend StreamEncoder (and MemoryEncoder) to have WriteRepeatedInt32 etc.
methods that mirror the equivalent ReadRepeatedInt32 etc. methods on
StreamDecoder.

Likewise extend encoder codegen to have a WriteFoo override that accepts
a vector.

Change-Id: I09d374dfa60e36feea8bb3b9f6d5fb701eb7c9b4
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/88963
Reviewed-by: Armando Montanez <amontanez@google.com>
Commit-Queue: Scott James Remnant <keybuk@google.com>
Pigweed-Auto-Submit: Scott James Remnant <keybuk@google.com>
diff --git a/pw_protobuf/codegen_encoder_test.cc b/pw_protobuf/codegen_encoder_test.cc
index 2787681..673213d 100644
--- a/pw_protobuf/codegen_encoder_test.cc
+++ b/pw_protobuf/codegen_encoder_test.cc
@@ -343,6 +343,41 @@
             0);
 }
 
+TEST(CodegenRepeated, PackedScalarVector) {
+  std::byte encode_buffer[32];
+
+  stream::MemoryWriter writer(encode_buffer);
+  RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
+  const pw::Vector<uint32_t, 4> values = {0, 16, 32, 48};
+  repeated_test.WriteUint32s(values)
+      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  repeated_test.WriteFixed32s(values)
+      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+
+  // clang-format off
+  constexpr uint8_t expected_proto[] = {
+    // uint32s[], v={0, 16, 32, 48}
+    0x0a, 0x04,
+    0x00,
+    0x10,
+    0x20,
+    0x30,
+    // fixed32s[]. v={0, 16, 32, 48}
+    0x32, 0x10,
+    0x00, 0x00, 0x00, 0x00,
+    0x10, 0x00, 0x00, 0x00,
+    0x20, 0x00, 0x00, 0x00,
+    0x30, 0x00, 0x00, 0x00,
+  };
+  // clang-format on
+
+  ConstByteSpan result = writer.WrittenData();
+  ASSERT_EQ(repeated_test.status(), OkStatus());
+  EXPECT_EQ(result.size(), sizeof(expected_proto));
+  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
+            0);
+}
+
 TEST(CodegenRepeated, NonScalar) {
   std::byte encode_buffer[32];
 
diff --git a/pw_protobuf/docs.rst b/pw_protobuf/docs.rst
index 84ce593..9c6d984 100644
--- a/pw_protobuf/docs.rst
+++ b/pw_protobuf/docs.rst
@@ -315,6 +315,18 @@
     PW_LOG_INFO("Failed to decode proto; %s", client.status().str());
   }
 
+Repeated Fields
+---------------
+Repeated fields can be encoded a value at a time by repeatedly calling
+`WriteInt32` etc., or as a packed field by calling e.g. `WritePackedInt32` with
+a `std::span<Type>` or `WriteRepeatedInt32` with a `pw::Vector<Type>` (see
+:ref:`module-pw_containers` for details).
+
+The codegen wrappers provide a `WriteFieldName` method with three signatures.
+One that encodes a single value at a time, one that encodes a packed field
+from a `std::span<Type>`, and one that encodes a packed field from a
+`pw::Vector<Type>`. All three return `Status`.
+
 --------
 Decoding
 --------
diff --git a/pw_protobuf/encoder_test.cc b/pw_protobuf/encoder_test.cc
index 3c01c55..6417f69 100644
--- a/pw_protobuf/encoder_test.cc
+++ b/pw_protobuf/encoder_test.cc
@@ -307,6 +307,37 @@
   EXPECT_EQ(encoder.status(), Status::ResourceExhausted());
 }
 
+TEST(StreamEncoder, PackedVarintVector) {
+  std::byte encode_buffer[32];
+  MemoryEncoder encoder(encode_buffer);
+
+  // repeated uint32 values = 1;
+  const pw::Vector<uint32_t, 5> values = {0, 50, 100, 150, 200};
+  encoder.WriteRepeatedUint32(1, values)
+      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+
+  constexpr uint8_t encoded_proto[] = {
+      0x0a, 0x07, 0x00, 0x32, 0x64, 0x96, 0x01, 0xc8, 0x01};
+  //  key   size  v[0]  v[1]  v[2]  v[3]        v[4]
+
+  ASSERT_EQ(encoder.status(), OkStatus());
+  ConstByteSpan result(encoder);
+  EXPECT_EQ(result.size(), sizeof(encoded_proto));
+  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
+            0);
+}
+
+TEST(StreamEncoder, PackedVarintVectorInsufficientSpace) {
+  std::byte encode_buffer[8];
+  MemoryEncoder encoder(encode_buffer);
+
+  const pw::Vector<uint32_t, 5> values = {0, 50, 100, 150, 200};
+  encoder.WriteRepeatedUint32(1, values)
+      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+
+  EXPECT_EQ(encoder.status(), Status::ResourceExhausted());
+}
+
 TEST(StreamEncoder, PackedBool) {
   std::byte encode_buffer[32];
   MemoryEncoder encoder(encode_buffer);
@@ -353,6 +384,32 @@
             0);
 }
 
+TEST(StreamEncoder, PackedFixedVector) {
+  std::byte encode_buffer[32];
+  MemoryEncoder encoder(encode_buffer);
+
+  // repeated fixed32 values = 1;
+  const pw::Vector<uint32_t, 5> values = {0, 50, 100, 150, 200};
+  encoder.WriteRepeatedFixed32(1, values)
+      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+
+  // repeated fixed64 values64 = 2;
+  const pw::Vector<uint64_t, 1> values64 = {0x0102030405060708};
+  encoder.WriteRepeatedFixed64(2, values64)
+      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+
+  constexpr uint8_t encoded_proto[] = {
+      0x0a, 0x14, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x64,
+      0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00,
+      0x12, 0x08, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
+
+  ASSERT_EQ(encoder.status(), OkStatus());
+  ConstByteSpan result(encoder);
+  EXPECT_EQ(result.size(), sizeof(encoded_proto));
+  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
+            0);
+}
+
 TEST(StreamEncoder, PackedZigzag) {
   std::byte encode_buffer[32];
   MemoryEncoder encoder(encode_buffer);
@@ -372,6 +429,25 @@
             0);
 }
 
+TEST(StreamEncoder, PackedZigzagVector) {
+  std::byte encode_buffer[32];
+  MemoryEncoder encoder(encode_buffer);
+
+  // repeated sint32 values = 1;
+  const pw::Vector<int32_t, 7> values = {-100, -25, -1, 0, 1, 25, 100};
+  encoder.WriteRepeatedSint32(1, values)
+      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+
+  constexpr uint8_t encoded_proto[] = {
+      0x0a, 0x09, 0xc7, 0x01, 0x31, 0x01, 0x00, 0x02, 0x32, 0xc8, 0x01};
+
+  ASSERT_EQ(encoder.status(), OkStatus());
+  ConstByteSpan result(encoder);
+  EXPECT_EQ(result.size(), sizeof(encoded_proto));
+  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
+            0);
+}
+
 TEST(StreamEncoder, ParentUnavailable) {
   std::byte encode_buffer[32];
   MemoryEncoder parent(encode_buffer);
diff --git a/pw_protobuf/public/pw_protobuf/encoder.h b/pw_protobuf/public/pw_protobuf/encoder.h
index 41117db..f5b4273 100644
--- a/pw_protobuf/public/pw_protobuf/encoder.h
+++ b/pw_protobuf/public/pw_protobuf/encoder.h
@@ -24,6 +24,7 @@
 #include "pw_assert/assert.h"
 #include "pw_bytes/endian.h"
 #include "pw_bytes/span.h"
+#include "pw_containers/vector.h"
 #include "pw_protobuf/config.h"
 #include "pw_protobuf/wire_format.h"
 #include "pw_status/status.h"
@@ -177,6 +178,16 @@
     return WritePackedVarints(field_number, values, VarintEncodeType::kNormal);
   }
 
+  // Writes a repeated uint32 using packed encoding.
+  //
+  // Precondition: Encoder has no active child encoder.
+  Status WriteRepeatedUint32(uint32_t field_number,
+                             const pw::Vector<uint32_t>& values) {
+    return WritePackedVarints(field_number,
+                              std::span(values.data(), values.size()),
+                              VarintEncodeType::kNormal);
+  }
+
   // Writes a proto uint64 key-value pair.
   //
   // Precondition: Encoder has no active child encoder.
@@ -192,6 +203,16 @@
     return WritePackedVarints(field_number, values, VarintEncodeType::kNormal);
   }
 
+  // Writes a repeated uint64 using packed encoding.
+  //
+  // Precondition: Encoder has no active child encoder.
+  Status WriteRepeatedUint64(uint32_t field_number,
+                             const pw::Vector<uint64_t>& values) {
+    return WritePackedVarints(field_number,
+                              std::span(values.data(), values.size()),
+                              VarintEncodeType::kNormal);
+  }
+
   // Writes a proto int32 key-value pair.
   //
   // Precondition: Encoder has no active child encoder.
@@ -211,6 +232,18 @@
         VarintEncodeType::kNormal);
   }
 
+  // Writes a repeated int32 using packed encoding.
+  //
+  // Precondition: Encoder has no active child encoder.
+  Status WriteRepeatedInt32(uint32_t field_number,
+                            const pw::Vector<int32_t>& values) {
+    return WritePackedVarints(
+        field_number,
+        std::span(reinterpret_cast<const uint32_t*>(values.data()),
+                  values.size()),
+        VarintEncodeType::kNormal);
+  }
+
   // Writes a proto int64 key-value pair.
   //
   // Precondition: Encoder has no active child encoder.
@@ -230,6 +263,18 @@
         VarintEncodeType::kNormal);
   }
 
+  // Writes a repeated int64 using packed encoding.
+  //
+  // Precondition: Encoder has no active child encoder.
+  Status WriteRepeatedInt64(uint32_t field_number,
+                            const pw::Vector<int64_t>& values) {
+    return WritePackedVarints(
+        field_number,
+        std::span(reinterpret_cast<const uint64_t*>(values.data()),
+                  values.size()),
+        VarintEncodeType::kNormal);
+  }
+
   // Writes a proto sint32 key-value pair.
   //
   // Precondition: Encoder has no active child encoder.
@@ -249,6 +294,18 @@
         VarintEncodeType::kZigZag);
   }
 
+  // Writes a repeated sint32 using packed encoding.
+  //
+  // Precondition: Encoder has no active child encoder.
+  Status WriteRepeatedSint32(uint32_t field_number,
+                             const pw::Vector<int32_t>& values) {
+    return WritePackedVarints(
+        field_number,
+        std::span(reinterpret_cast<const uint32_t*>(values.data()),
+                  values.size()),
+        VarintEncodeType::kZigZag);
+  }
+
   // Writes a proto sint64 key-value pair.
   //
   // Precondition: Encoder has no active child encoder.
@@ -268,6 +325,18 @@
         VarintEncodeType::kZigZag);
   }
 
+  // Writes a repeated sint64 using packed encoding.
+  //
+  // Precondition: Encoder has no active child encoder.
+  Status WriteRepeatedSint64(uint32_t field_number,
+                             const pw::Vector<int64_t>& values) {
+    return WritePackedVarints(
+        field_number,
+        std::span(reinterpret_cast<const uint64_t*>(values.data()),
+                  values.size()),
+        VarintEncodeType::kZigZag);
+  }
+
   // Writes a proto bool key-value pair.
   //
   // Precondition: Encoder has no active child encoder.
@@ -288,6 +357,21 @@
         VarintEncodeType::kNormal);
   }
 
+  // Writes a repeated bool using packed encoding.
+  //
+  // Precondition: Encoder has no active child encoder.
+  Status WriteRepeatedBool(uint32_t field_number,
+                           const pw::Vector<bool>& values) {
+    static_assert(sizeof(bool) == sizeof(uint8_t),
+                  "bool must be same size as uint8_t");
+
+    return WritePackedVarints(
+        field_number,
+        std::span(reinterpret_cast<const uint8_t*>(values.data()),
+                  values.size()),
+        VarintEncodeType::kNormal);
+  }
+
   // Writes a proto fixed32 key-value pair.
   //
   // Precondition: Encoder has no active child encoder.
@@ -306,6 +390,17 @@
         field_number, std::as_bytes(values), sizeof(uint32_t));
   }
 
+  // Writes a repeated fixed32 field using packed encoding.
+  //
+  // Precondition: Encoder has no active child encoder.
+  Status WriteRepeatedFixed32(uint32_t field_number,
+                              const pw::Vector<uint32_t>& values) {
+    return WritePackedFixed(
+        field_number,
+        std::as_bytes(std::span(values.data(), values.size())),
+        sizeof(uint32_t));
+  }
+
   // Writes a proto fixed64 key-value pair.
   //
   // Precondition: Encoder has no active child encoder.
@@ -324,6 +419,17 @@
         field_number, std::as_bytes(values), sizeof(uint64_t));
   }
 
+  // Writes a repeated fixed64 field using packed encoding.
+  //
+  // Precondition: Encoder has no active child encoder.
+  Status WriteRepeatedFixed64(uint32_t field_number,
+                              const pw::Vector<uint64_t>& values) {
+    return WritePackedFixed(
+        field_number,
+        std::as_bytes(std::span(values.data(), values.size())),
+        sizeof(uint64_t));
+  }
+
   // Writes a proto sfixed32 key-value pair.
   //
   // Precondition: Encoder has no active child encoder.
@@ -340,6 +446,17 @@
         field_number, std::as_bytes(values), sizeof(int32_t));
   }
 
+  // Writes a repeated fixed32 field using packed encoding.
+  //
+  // Precondition: Encoder has no active child encoder.
+  Status WriteRepeatedSfixed32(uint32_t field_number,
+                               const pw::Vector<int32_t>& values) {
+    return WritePackedFixed(
+        field_number,
+        std::as_bytes(std::span(values.data(), values.size())),
+        sizeof(int32_t));
+  }
+
   // Writes a proto sfixed64 key-value pair.
   //
   // Precondition: Encoder has no active child encoder.
@@ -356,6 +473,17 @@
         field_number, std::as_bytes(values), sizeof(int64_t));
   }
 
+  // Writes a repeated fixed64 field using packed encoding.
+  //
+  // Precondition: Encoder has no active child encoder.
+  Status WriteRepeatedFixed64(uint32_t field_number,
+                              const pw::Vector<int64_t>& values) {
+    return WritePackedFixed(
+        field_number,
+        std::as_bytes(std::span(values.data(), values.size())),
+        sizeof(int64_t));
+  }
+
   // Writes a proto float key-value pair.
   //
   // Precondition: Encoder has no active child encoder.
@@ -377,6 +505,17 @@
     return WritePackedFixed(field_number, std::as_bytes(values), sizeof(float));
   }
 
+  // Writes a repeated float field using packed encoding.
+  //
+  // Precondition: Encoder has no active child encoder.
+  Status WriteRepeatedFloat(uint32_t field_number,
+                            const pw::Vector<float>& values) {
+    return WritePackedFixed(
+        field_number,
+        std::as_bytes(std::span(values.data(), values.size())),
+        sizeof(float));
+  }
+
   // Writes a proto double key-value pair.
   //
   // Precondition: Encoder has no active child encoder.
@@ -399,6 +538,17 @@
         field_number, std::as_bytes(values), sizeof(double));
   }
 
+  // Writes a repeated double field using packed encoding.
+  //
+  // Precondition: Encoder has no active child encoder.
+  Status WriteRepeatedDouble(uint32_t field_number,
+                             const pw::Vector<double>& values) {
+    return WritePackedFixed(
+        field_number,
+        std::as_bytes(std::span(values.data(), values.size())),
+        sizeof(double));
+  }
+
   // Writes a proto `bytes` field as a key-value pair. This can also be used to
   // write a pre-encoded nested submessage directly without using a nested
   // encoder.
diff --git a/pw_protobuf/py/pw_protobuf/codegen_pwpb.py b/pw_protobuf/py/pw_protobuf/codegen_pwpb.py
index ae54bd8..fc28fba 100644
--- a/pw_protobuf/py/pw_protobuf/codegen_pwpb.py
+++ b/pw_protobuf/py/pw_protobuf/codegen_pwpb.py
@@ -398,6 +398,15 @@
         return 'WritePackedDouble'
 
 
+class PackedDoubleWriteVectorMethod(PackedWriteMethod):
+    """Method which writes a packed vector of doubles."""
+    def params(self) -> List[Tuple[str, str]]:
+        return [('const ::pw::Vector<double>&', 'values')]
+
+    def _encoder_fn(self) -> str:
+        return 'WriteRepeatedDouble'
+
+
 class DoubleReadMethod(ReadMethod):
     """Method which reads a proto double value."""
     def _result_type(self) -> str:
@@ -443,6 +452,15 @@
         return 'WritePackedFloat'
 
 
+class PackedFloatWriteVectorMethod(PackedWriteMethod):
+    """Method which writes a packed vector of floats."""
+    def params(self) -> List[Tuple[str, str]]:
+        return [('const ::pw::Vector<float>&', 'values')]
+
+    def _encoder_fn(self) -> str:
+        return 'WriteRepeatedFloat'
+
+
 class FloatReadMethod(ReadMethod):
     """Method which reads a proto float value."""
     def _result_type(self) -> str:
@@ -488,6 +506,15 @@
         return 'WritePackedInt32'
 
 
+class PackedInt32WriteVectorMethod(PackedWriteMethod):
+    """Method which writes a packed vector of int32."""
+    def params(self) -> List[Tuple[str, str]]:
+        return [('const ::pw::Vector<int32_t>&', 'values')]
+
+    def _encoder_fn(self) -> str:
+        return 'WriteRepeatedInt32'
+
+
 class Int32ReadMethod(ReadMethod):
     """Method which reads a proto int32 value."""
     def _result_type(self) -> str:
@@ -533,6 +560,15 @@
         return 'WritePackedSint32'
 
 
+class PackedSint32WriteVectorMethod(PackedWriteMethod):
+    """Method which writes a packed vector of sint32."""
+    def params(self) -> List[Tuple[str, str]]:
+        return [('const ::pw::Vector<int32_t>&', 'values')]
+
+    def _encoder_fn(self) -> str:
+        return 'WriteRepeatedSint32'
+
+
 class Sint32ReadMethod(ReadMethod):
     """Method which reads a proto sint32 value."""
     def _result_type(self) -> str:
@@ -578,6 +614,15 @@
         return 'WritePackedSfixed32'
 
 
+class PackedSfixed32WriteVectorMethod(PackedWriteMethod):
+    """Method which writes a packed vector of sfixed32."""
+    def params(self) -> List[Tuple[str, str]]:
+        return [('const ::pw::Vector<int32_t>&', 'values')]
+
+    def _encoder_fn(self) -> str:
+        return 'WriteRepeatedSfixed32'
+
+
 class Sfixed32ReadMethod(ReadMethod):
     """Method which reads a proto sfixed32 value."""
     def _result_type(self) -> str:
@@ -615,7 +660,7 @@
 
 
 class PackedInt64WriteMethod(PackedWriteMethod):
-    """Method which writes a proto int64 value."""
+    """Method which writes a packed list of int64."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const int64_t>', 'values')]
 
@@ -623,6 +668,15 @@
         return 'WritePackedInt64'
 
 
+class PackedInt64WriteVectorMethod(PackedWriteMethod):
+    """Method which writes a packed vector of int64."""
+    def params(self) -> List[Tuple[str, str]]:
+        return [('const ::pw::Vector<int64_t>&', 'values')]
+
+    def _encoder_fn(self) -> str:
+        return 'WriteRepeatedInt64'
+
+
 class Int64ReadMethod(ReadMethod):
     """Method which reads a proto int64 value."""
     def _result_type(self) -> str:
@@ -660,7 +714,7 @@
 
 
 class PackedSint64WriteMethod(PackedWriteMethod):
-    """Method which writes a proto sint64 value."""
+    """Method which writes a packst list of sint64."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const int64_t>', 'values')]
 
@@ -668,6 +722,15 @@
         return 'WritePackedSint64'
 
 
+class PackedSint64WriteVectorMethod(PackedWriteMethod):
+    """Method which writes a packed vector of sint64."""
+    def params(self) -> List[Tuple[str, str]]:
+        return [('const ::pw::Vector<int64_t>&', 'values')]
+
+    def _encoder_fn(self) -> str:
+        return 'WriteRepeatedSint64'
+
+
 class Sint64ReadMethod(ReadMethod):
     """Method which reads a proto sint64 value."""
     def _result_type(self) -> str:
@@ -705,7 +768,7 @@
 
 
 class PackedSfixed64WriteMethod(PackedWriteMethod):
-    """Method which writes a proto sfixed64 value."""
+    """Method which writes a packed list of sfixed64."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const int64_t>', 'values')]
 
@@ -713,6 +776,15 @@
         return 'WritePackedSfixed4'
 
 
+class PackedSfixed64WriteVectorMethod(PackedWriteMethod):
+    """Method which writes a packed vector of sfixed64."""
+    def params(self) -> List[Tuple[str, str]]:
+        return [('const ::pw::Vector<int64_t>&', 'values')]
+
+    def _encoder_fn(self) -> str:
+        return 'WriteRepeatedSfixed4'
+
+
 class Sfixed64ReadMethod(ReadMethod):
     """Method which reads a proto sfixed64 value."""
     def _result_type(self) -> str:
@@ -750,7 +822,7 @@
 
 
 class PackedUint32WriteMethod(PackedWriteMethod):
-    """Method which writes a proto uint32 value."""
+    """Method which writes a packed list of uint32."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const uint32_t>', 'values')]
 
@@ -758,6 +830,15 @@
         return 'WritePackedUint32'
 
 
+class PackedUint32WriteVectorMethod(PackedWriteMethod):
+    """Method which writes a packed vector of uint32."""
+    def params(self) -> List[Tuple[str, str]]:
+        return [('const ::pw::Vector<uint32_t>&', 'values')]
+
+    def _encoder_fn(self) -> str:
+        return 'WriteRepeatedUint32'
+
+
 class Uint32ReadMethod(ReadMethod):
     """Method which reads a proto uint32 value."""
     def _result_type(self) -> str:
@@ -795,7 +876,7 @@
 
 
 class PackedFixed32WriteMethod(PackedWriteMethod):
-    """Method which writes a proto fixed32 value."""
+    """Method which writes a packed list of fixed32."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const uint32_t>', 'values')]
 
@@ -803,6 +884,15 @@
         return 'WritePackedFixed32'
 
 
+class PackedFixed32WriteVectorMethod(PackedWriteMethod):
+    """Method which writes a packed vector of fixed32."""
+    def params(self) -> List[Tuple[str, str]]:
+        return [('const ::pw::Vector<uint32_t>&', 'values')]
+
+    def _encoder_fn(self) -> str:
+        return 'WriteRepeatedFixed32'
+
+
 class Fixed32ReadMethod(ReadMethod):
     """Method which reads a proto fixed32 value."""
     def _result_type(self) -> str:
@@ -840,7 +930,7 @@
 
 
 class PackedUint64WriteMethod(PackedWriteMethod):
-    """Method which writes a proto uint64 value."""
+    """Method which writes a packed list of uint64."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const uint64_t>', 'values')]
 
@@ -848,6 +938,15 @@
         return 'WritePackedUint64'
 
 
+class PackedUint64WriteVectorMethod(PackedWriteMethod):
+    """Method which writes a packed vector of uint64."""
+    def params(self) -> List[Tuple[str, str]]:
+        return [('const ::pw::Vector<uint64_t>&', 'values')]
+
+    def _encoder_fn(self) -> str:
+        return 'WriteRepeatedUint64'
+
+
 class Uint64ReadMethod(ReadMethod):
     """Method which reads a proto uint64 value."""
     def _result_type(self) -> str:
@@ -885,7 +984,7 @@
 
 
 class PackedFixed64WriteMethod(PackedWriteMethod):
-    """Method which writes a proto fixed64 value."""
+    """Method which writes a packed list of fixed64."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const uint64_t>', 'values')]
 
@@ -893,6 +992,15 @@
         return 'WritePackedFixed64'
 
 
+class PackedFixed64WriteVectorMethod(PackedWriteMethod):
+    """Method which writes a packed list of fixed64."""
+    def params(self) -> List[Tuple[str, str]]:
+        return [('const ::pw::Vector<uint64_t>&', 'values')]
+
+    def _encoder_fn(self) -> str:
+        return 'WriteRepeatedFixed64'
+
+
 class Fixed64ReadMethod(ReadMethod):
     """Method which reads a proto fixed64 value."""
     def _result_type(self) -> str:
@@ -938,6 +1046,15 @@
         return 'WritePackedBool'
 
 
+class PackedBoolWriteVectorMethod(PackedWriteMethod):
+    """Method which writes a packed vector of bools."""
+    def params(self) -> List[Tuple[str, str]]:
+        return [('const ::pw::Vector<bool>&', 'values')]
+
+    def _encoder_fn(self) -> str:
+        return 'WriteRepeatedBool'
+
+
 class BoolReadMethod(ReadMethod):
     """Method which reads a proto bool value."""
     def _result_type(self) -> str:
@@ -1046,40 +1163,55 @@
 
 # Mapping of protobuf field types to their method definitions.
 PROTO_FIELD_WRITE_METHODS: Dict[int, List] = {
-    descriptor_pb2.FieldDescriptorProto.TYPE_DOUBLE:
-    [DoubleWriteMethod, PackedDoubleWriteMethod],
+    descriptor_pb2.FieldDescriptorProto.TYPE_DOUBLE: [
+        DoubleWriteMethod, PackedDoubleWriteMethod,
+        PackedDoubleWriteVectorMethod
+    ],
     descriptor_pb2.FieldDescriptorProto.TYPE_FLOAT:
-    [FloatWriteMethod, PackedFloatWriteMethod],
+    [FloatWriteMethod, PackedFloatWriteMethod, PackedFloatWriteVectorMethod],
     descriptor_pb2.FieldDescriptorProto.TYPE_INT32:
-    [Int32WriteMethod, PackedInt32WriteMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_SINT32:
-    [Sint32WriteMethod, PackedSint32WriteMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_SFIXED32:
-    [Sfixed32WriteMethod, PackedSfixed32WriteMethod],
+    [Int32WriteMethod, PackedInt32WriteMethod, PackedInt32WriteVectorMethod],
+    descriptor_pb2.FieldDescriptorProto.TYPE_SINT32: [
+        Sint32WriteMethod, PackedSint32WriteMethod,
+        PackedSint32WriteVectorMethod
+    ],
+    descriptor_pb2.FieldDescriptorProto.TYPE_SFIXED32: [
+        Sfixed32WriteMethod, PackedSfixed32WriteMethod,
+        PackedSfixed32WriteVectorMethod
+    ],
     descriptor_pb2.FieldDescriptorProto.TYPE_INT64:
-    [Int64WriteMethod, PackedInt64WriteMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_SINT64:
-    [Sint64WriteMethod, PackedSint64WriteMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_SFIXED64:
-    [Sfixed64WriteMethod, PackedSfixed64WriteMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_UINT32:
-    [Uint32WriteMethod, PackedUint32WriteMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_FIXED32:
-    [Fixed32WriteMethod, PackedFixed32WriteMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_UINT64:
-    [Uint64WriteMethod, PackedUint64WriteMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_FIXED64:
-    [Fixed64WriteMethod, PackedFixed64WriteMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_BOOL: [
-        BoolWriteMethod, PackedBoolWriteMethod
+    [Int64WriteMethod, PackedInt64WriteMethod, PackedInt64WriteVectorMethod],
+    descriptor_pb2.FieldDescriptorProto.TYPE_SINT64: [
+        Sint64WriteMethod, PackedSint64WriteMethod,
+        PackedSint64WriteVectorMethod
     ],
+    descriptor_pb2.FieldDescriptorProto.TYPE_SFIXED64: [
+        Sfixed64WriteMethod, PackedSfixed64WriteMethod,
+        PackedSfixed64WriteVectorMethod
+    ],
+    descriptor_pb2.FieldDescriptorProto.TYPE_UINT32: [
+        Uint32WriteMethod, PackedUint32WriteMethod,
+        PackedUint32WriteVectorMethod
+    ],
+    descriptor_pb2.FieldDescriptorProto.TYPE_FIXED32: [
+        Fixed32WriteMethod, PackedFixed32WriteMethod,
+        PackedFixed32WriteVectorMethod
+    ],
+    descriptor_pb2.FieldDescriptorProto.TYPE_UINT64: [
+        Uint64WriteMethod, PackedUint64WriteMethod,
+        PackedUint64WriteVectorMethod
+    ],
+    descriptor_pb2.FieldDescriptorProto.TYPE_FIXED64: [
+        Fixed64WriteMethod, PackedFixed64WriteMethod,
+        PackedFixed64WriteVectorMethod
+    ],
+    descriptor_pb2.FieldDescriptorProto.TYPE_BOOL:
+    [BoolWriteMethod, PackedBoolWriteMethod, PackedBoolWriteVectorMethod],
     descriptor_pb2.FieldDescriptorProto.TYPE_BYTES: [BytesWriteMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_STRING: [
-        StringLenWriteMethod, StringWriteMethod
-    ],
-    descriptor_pb2.FieldDescriptorProto.TYPE_MESSAGE: [
-        SubMessageEncoderMethod
-    ],
+    descriptor_pb2.FieldDescriptorProto.TYPE_STRING:
+    [StringLenWriteMethod, StringWriteMethod],
+    descriptor_pb2.FieldDescriptorProto.TYPE_MESSAGE:
+    [SubMessageEncoderMethod],
     descriptor_pb2.FieldDescriptorProto.TYPE_ENUM: [EnumWriteMethod],
 }