pw_protobuf: Extended decoder type support

This change adds decoder support for protobuf bool, fixed32,
fixed64, float, double, bytes, and string value.

Change-Id: Ie8369f5802d07290c0d6c12b5049c6bd8cc7a5da
diff --git a/pw_protobuf/decoder.cc b/pw_protobuf/decoder.cc
index 0e803e6..220c608 100644
--- a/pw_protobuf/decoder.cc
+++ b/pw_protobuf/decoder.cc
@@ -14,6 +14,8 @@
 
 #include "pw_protobuf/decoder.h"
 
+#include <cstring>
+
 #include "pw_varint/varint.h"
 
 namespace pw::protobuf {
@@ -66,6 +68,67 @@
 }
 
 Status Decoder::ReadVarint(uint32_t field_number, uint64_t* out) {
+  Status status = ConsumeKey(field_number, WireType::kVarint);
+  if (!status.ok()) {
+    return status;
+  }
+
+  size_t bytes_read = varint::Decode(proto_, out);
+  if (bytes_read == 0) {
+    state_ = kDecodeFailed;
+    return Status::DATA_LOSS;
+  }
+
+  // Advance to the next field.
+  proto_ = proto_.subspan(bytes_read);
+  return Status::OK;
+}
+
+Status Decoder::ReadFixed(uint32_t field_number, std::byte* out, size_t size) {
+  WireType expected_wire_type =
+      size == sizeof(uint32_t) ? WireType::kFixed32 : WireType::kFixed64;
+  Status status = ConsumeKey(field_number, expected_wire_type);
+  if (!status.ok()) {
+    return status;
+  }
+
+  if (proto_.size() < size) {
+    return Status::DATA_LOSS;
+  }
+
+  std::memcpy(out, proto_.data(), size);
+  proto_ = proto_.subspan(size);
+
+  return Status::OK;
+}
+
+Status Decoder::ReadDelimited(uint32_t field_number,
+                              span<const std::byte>* out) {
+  Status status = ConsumeKey(field_number, WireType::kDelimited);
+  if (!status.ok()) {
+    return status;
+  }
+
+  uint64_t length;
+  size_t bytes_read = varint::Decode(proto_, &length);
+  if (bytes_read == 0) {
+    state_ = kDecodeFailed;
+    return Status::DATA_LOSS;
+  }
+
+  proto_ = proto_.subspan(bytes_read);
+  if (proto_.size() < length) {
+    state_ = kDecodeFailed;
+    return Status::DATA_LOSS;
+  }
+
+  *out = proto_.first(length);
+  proto_ = proto_.subspan(length);
+
+  return Status::OK;
+}
+
+Status Decoder::ConsumeKey(uint32_t field_number, WireType expected_type) {
   if (state_ != kDecodeInProgress) {
     return Status::FAILED_PRECONDITION;
   }
@@ -80,21 +143,12 @@
   uint32_t field = key >> kFieldNumberShift;
   WireType wire_type = static_cast<WireType>(key & kWireTypeMask);
 
-  if (field != field_number || wire_type != WireType::kVarint) {
+  if (field != field_number || wire_type != expected_type) {
     state_ = kDecodeFailed;
     return Status::FAILED_PRECONDITION;
   }
 
-  // Advance to the varint value.
-  proto_ = proto_.subspan(bytes_read);
-
-  bytes_read = varint::Decode(proto_, out);
-  if (bytes_read == 0) {
-    state_ = kDecodeFailed;
-    return Status::DATA_LOSS;
-  }
-
-  // Advance to the next field.
+  // Advance past the key.
   proto_ = proto_.subspan(bytes_read);
   return Status::OK;
 }
diff --git a/pw_protobuf/decoder_test.cc b/pw_protobuf/decoder_test.cc
index 45fe93d..c655b09 100644
--- a/pw_protobuf/decoder_test.cc
+++ b/pw_protobuf/decoder_test.cc
@@ -23,6 +23,8 @@
 class TestDecodeHandler : public DecodeHandler {
  public:
   Status ProcessField(Decoder* decoder, uint32_t field_number) override {
+    std::string_view str;
+
     switch (field_number) {
       case 1:
         decoder->ReadInt32(field_number, &test_int32);
@@ -30,6 +32,20 @@
       case 2:
         decoder->ReadSint32(field_number, &test_sint32);
         break;
+      case 3:
+        decoder->ReadBool(field_number, &test_bool);
+        break;
+      case 4:
+        decoder->ReadDouble(field_number, &test_double);
+        break;
+      case 5:
+        decoder->ReadFixed32(field_number, &test_fixed32);
+        break;
+      case 6:
+        decoder->ReadString(field_number, &str);
+        std::memcpy(test_string, str.data(), str.size());
+        test_string[str.size()] = '\0';
+        break;
     }
 
     called = true;
@@ -39,6 +55,10 @@
   bool called = false;
   int32_t test_int32 = 0;
   int32_t test_sint32 = 0;
+  bool test_bool = true;
+  double test_double = 0;
+  uint32_t test_fixed32 = 0;
+  char test_string[16];
 };
 
 TEST(Decoder, Decode) {
@@ -57,6 +77,8 @@
     0x21, 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
     // type=fixed32, k=5, v=0xdeadbeef
     0x2d, 0xef, 0xbe, 0xad, 0xde,
+    // type=string, k=6, v="Hello world"
+    0x32, 0x0b, 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd',
   };
   // clang-format on
 
@@ -65,6 +87,10 @@
   EXPECT_TRUE(handler.called);
   EXPECT_EQ(handler.test_int32, 42);
   EXPECT_EQ(handler.test_sint32, -13);
+  EXPECT_FALSE(handler.test_bool);
+  EXPECT_EQ(handler.test_double, 3.14159);
+  EXPECT_EQ(handler.test_fixed32, 0xdeadbeef);
+  EXPECT_STREQ(handler.test_string, "Hello world");
 }
 
 TEST(Decoder, Decode_OverridesDuplicateFields) {
diff --git a/pw_protobuf/public/pw_protobuf/decoder.h b/pw_protobuf/public/pw_protobuf/decoder.h
index e923c66..7e5feed 100644
--- a/pw_protobuf/public/pw_protobuf/decoder.h
+++ b/pw_protobuf/public/pw_protobuf/decoder.h
@@ -13,6 +13,8 @@
 // the License.
 #pragma once
 
+#include <string_view>
+
 #include "pw_protobuf/wire_format.h"
 #include "pw_span/span.h"
 #include "pw_status/status.h"
@@ -140,6 +142,72 @@
     return Status::OK;
   }
 
+  // Reads a proto bool value from the current cursor.
+  Status ReadBool(uint32_t field_number, bool* out) {
+    uint64_t value = 0;
+    Status status = ReadUint64(field_number, &value);
+    if (!status.ok()) {
+      return status;
+    }
+    *out = value;
+    return Status::OK;
+  }
+
+  // Reads a proto fixed32 value from the current cursor.
+  Status ReadFixed32(uint32_t field_number, uint32_t* out) {
+    return ReadFixed(field_number, out);
+  }
+
+  // Reads a proto fixed64 value from the current cursor.
+  Status ReadFixed64(uint32_t field_number, uint64_t* out) {
+    return ReadFixed(field_number, out);
+  }
+
+  // Reads a proto sfixed32 value from the current cursor.
+  Status ReadSfixed32(uint32_t field_number, int32_t* out) {
+    return ReadFixed32(field_number, reinterpret_cast<uint32_t*>(out));
+  }
+
+  // Reads a proto sfixed64 value from the current cursor.
+  Status ReadSfixed64(uint32_t field_number, int64_t* out) {
+    return ReadFixed64(field_number, reinterpret_cast<uint64_t*>(out));
+  }
+
+  // Reads a proto float value from the current cursor.
+  Status ReadFloat(uint32_t field_number, float* out) {
+    static_assert(sizeof(float) == sizeof(uint32_t),
+                  "Float and uint32_t must be the same size for protobufs");
+    return ReadFixed(field_number, out);
+  }
+
+  // Reads a proto double value from the current cursor.
+  Status ReadDouble(uint32_t field_number, double* out) {
+    static_assert(sizeof(double) == sizeof(uint64_t),
+                  "Double and uint64_t must be the same size for protobufs");
+    return ReadFixed(field_number, out);
+  }
+
+  // Reads a proto string value from the current cursor and returns a view of it
+  // in `out`. The raw protobuf data must outlive `out`. If the string field is
+  // invalid, `out` is not modified.
+  Status ReadString(uint32_t field_number, std::string_view* out) {
+    span<const std::byte> bytes;
+    Status status = ReadDelimited(field_number, &bytes);
+    if (!status.ok()) {
+      return status;
+    }
+    *out = std::string_view(reinterpret_cast<const char*>(bytes.data()),
+                            bytes.size());
+    return Status::OK;
+  }
+
+  // Reads a proto bytes value from the current cursor and returns a view of it
+  // in `out`. The raw protobuf data must outlive the `out` span. If the bytes
+  // field is invalid, `out` is not modified.
+  Status ReadBytes(uint32_t field_number, span<const std::byte>* out) {
+    return ReadDelimited(field_number, out);
+  }
+
  private:
   enum State {
     kReady,
@@ -150,6 +218,30 @@
   // Reads a varint key-value pair from the current cursor position.
   Status ReadVarint(uint32_t field_number, uint64_t* out);
 
+  // Reads a fixed-size key-value pair from the current cursor position.
+  Status ReadFixed(uint32_t field_number, std::byte* out, size_t size);
+
+  template <typename T>
+  Status ReadFixed(uint32_t field_number, T* out) {
+    static_assert(
+        sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t),
+        "Protobuf fixed-size fields must be 32- or 64-bit");
+    union {
+      T value;
+      std::byte bytes[sizeof(T)];
+    };
+    Status status = ReadFixed(field_number, bytes, sizeof(bytes));
+    if (!status.ok()) {
+      return status;
+    }
+    *out = value;
+    return Status::OK;
+  }
+
+  Status ReadDelimited(uint32_t field_number, span<const std::byte>* out);
+
+  Status ConsumeKey(uint32_t field_number, WireType expected_type);
+
   // Advances the cursor to the next field in the proto.
   void SkipField();
 
diff --git a/pw_protobuf/size_report/decoder_full.cc b/pw_protobuf/size_report/decoder_full.cc
index 37ac958..d879012 100644
--- a/pw_protobuf/size_report/decoder_full.cc
+++ b/pw_protobuf/size_report/decoder_full.cc
@@ -12,6 +12,8 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
+#include <cstring>
+
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_protobuf/decoder.h"
 
@@ -30,6 +32,8 @@
  public:
   pw::Status ProcessField(pw::protobuf::Decoder* decoder,
                           uint32_t field_number) override {
+    std::string_view str;
+
     switch (field_number) {
       case 1:
         if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
@@ -41,6 +45,29 @@
           test_sint32 = 0;
         }
         break;
+      case 3:
+        if (!decoder->ReadBool(field_number, &test_bool).ok()) {
+          test_bool = false;
+        }
+        break;
+      case 4:
+        if (!decoder->ReadDouble(field_number, &test_double).ok()) {
+          test_double = 0;
+        }
+        break;
+      case 5:
+        if (!decoder->ReadFixed32(field_number, &test_fixed32).ok()) {
+          test_fixed32 = 0;
+        }
+        break;
+      case 6:
+        if (decoder->ReadString(field_number, &str).ok()) {
+          // In real code:
+          // assert(str.size() < sizeof(test_string));
+          std::memcpy(test_string, str.data(), str.size());
+          test_string[str.size()] = '\0';
+        }
+        break;
     }
 
     return pw::Status::OK;
@@ -48,6 +75,10 @@
 
   int32_t test_int32 = 0;
   int32_t test_sint32 = 0;
+  bool test_bool = false;
+  double test_double = 0;
+  uint32_t test_fixed32 = 0;
+  char test_string[16];
 };
 
 int* volatile non_optimizable_pointer;
diff --git a/pw_protobuf/size_report/decoder_incremental.cc b/pw_protobuf/size_report/decoder_incremental.cc
index 3955aa7..ef6f2db 100644
--- a/pw_protobuf/size_report/decoder_incremental.cc
+++ b/pw_protobuf/size_report/decoder_incremental.cc
@@ -12,6 +12,8 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
+#include <cstring>
+
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_protobuf/decoder.h"
 
@@ -30,6 +32,8 @@
  public:
   pw::Status ProcessField(pw::protobuf::Decoder* decoder,
                           uint32_t field_number) override {
+    std::string_view str;
+
     switch (field_number) {
       case 1:
         if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
@@ -42,26 +46,51 @@
         }
         break;
       case 3:
-        if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
-          test_int32 = 0;
+        if (!decoder->ReadBool(field_number, &test_bool).ok()) {
+          test_bool = false;
         }
         break;
       case 4:
-        if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
-          test_int32 = 0;
+        if (!decoder->ReadDouble(field_number, &test_double).ok()) {
+          test_double = 0;
         }
         break;
       case 5:
+        if (!decoder->ReadFixed32(field_number, &test_fixed32).ok()) {
+          test_fixed32 = 0;
+        }
+        break;
+      case 6:
+        if (decoder->ReadString(field_number, &str).ok()) {
+          // In real code:
+          // assert(str.size() < sizeof(test_string));
+          std::memcpy(test_string, str.data(), str.size());
+          test_string[str.size()] = '\0';
+        }
+        break;
+
+      // Extra fields.
+      case 21:
         if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
           test_int32 = 0;
         }
         break;
-      case 6:
+      case 22:
+        if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
+          test_int32 = 0;
+        }
+        break;
+      case 23:
+        if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
+          test_int32 = 0;
+        }
+        break;
+      case 24:
         if (!decoder->ReadSint32(field_number, &test_sint32).ok()) {
           test_sint32 = 0;
         }
         break;
-      case 7:
+      case 25:
         if (!decoder->ReadSint32(field_number, &test_sint32).ok()) {
           test_sint32 = 0;
         }
@@ -73,6 +102,10 @@
 
   int32_t test_int32 = 0;
   int32_t test_sint32 = 0;
+  bool test_bool = false;
+  double test_double = 0;
+  uint32_t test_fixed32 = 0;
+  char test_string[16];
 };
 
 int* volatile non_optimizable_pointer;