pw_rpc: Support Nanopb versions 3 or 4

The type of the fields parameter changed between versions. Store the
fields as const void* and deduce the type to cast them to from the
pb_decode function.

Change-Id: I31cf3ee2225b0c21a774031b34aa52bf7d8f3ea7
diff --git a/pw_rpc/nanopb/method.cc b/pw_rpc/nanopb/method.cc
index e17d7ee..366e53a 100644
--- a/pw_rpc/nanopb/method.cc
+++ b/pw_rpc/nanopb/method.cc
@@ -18,6 +18,22 @@
 #include "pb_encode.h"
 
 namespace pw::rpc::internal {
+namespace {
+
+// Nanopb 3 uses pb_field_s and Nanopb 4 uses pb_msgdesc_s for fields. The
+// Nanopb version macro is difficult to use, so deduce the correct type from the
+// pb_decode function.
+template <typename DecodeFunction>
+struct NanopbTraits;
+
+template <typename FieldsType>
+struct NanopbTraits<bool(pb_istream_t*, FieldsType, void*)> {
+  using Fields = FieldsType;
+};
+
+using Fields = typename NanopbTraits<decltype(pb_decode)>::Fields;
+
+}  // namespace
 
 using std::byte;
 
@@ -25,15 +41,16 @@
                              void* proto_struct) const {
   auto input = pb_istream_from_buffer(
       reinterpret_cast<const pb_byte_t*>(buffer.data()), buffer.size());
-  return pb_decode(&input, request_fields_, proto_struct) ? Status::OK
-                                                          : Status::INTERNAL;
+  return pb_decode(&input, static_cast<Fields>(request_fields_), proto_struct)
+             ? Status::OK
+             : Status::INTERNAL;
 }
 
 StatusWithSize Method::EncodeResponse(const void* proto_struct,
                                       span<byte> buffer) const {
   auto output = pb_ostream_from_buffer(
       reinterpret_cast<pb_byte_t*>(buffer.data()), buffer.size());
-  if (pb_encode(&output, response_fields_, proto_struct)) {
+  if (pb_encode(&output, static_cast<Fields>(response_fields_), proto_struct)) {
     return StatusWithSize(output.bytes_written);
   }
   return StatusWithSize::INTERNAL;
diff --git a/pw_rpc/nanopb/public_overrides/pw_rpc/internal/method.h b/pw_rpc/nanopb/public_overrides/pw_rpc/internal/method.h
index 330d97b..8dd7764 100644
--- a/pw_rpc/nanopb/public_overrides/pw_rpc/internal/method.h
+++ b/pw_rpc/nanopb/public_overrides/pw_rpc/internal/method.h
@@ -24,10 +24,6 @@
 #include "pw_status/status.h"
 #include "pw_status/status_with_size.h"
 
-// Forward declare Nanopb types to avoid exposing Nanopb headers.
-extern "C" struct pb_field_s;
-extern "C" struct pb_msgdesc_s;
-
 namespace pw::rpc {
 
 // Define the Nanopb version of the the ServerWriter class.
@@ -48,9 +44,8 @@
 
 namespace internal {
 
-// TODO(hepler): Nanopb 4 uses pb_msgdesc_s instead of pb_field_s.
-// using NanopbMessageDescriptor = const pb_msgdesc_s*;
-using NanopbMessageDescriptor = const pb_field_s*;
+// Use a void* to cover both Nanopb 3's pb_field_s and Nanopb 4's pb_msgdesc_s.
+using NanopbMessageDescriptor = const void*;
 
 // Extracts the request and response proto types from a method.
 template <typename Method>