pw_rpc: Switch to fully-qualified service paths

This updates the RPC codegen and client to use the fully-qualified path
of an RPC service as its ID hash, instead of just its name, to allow
services with the same name to exist in different proto packages.

Change-Id: Ifee0799e1a9090c21cfda2b249956a32f47959b4
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/14080
Commit-Queue: Alexei Frolov <frolv@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
diff --git a/pw_protobuf/BUILD.gn b/pw_protobuf/BUILD.gn
index 5a4e800..99d2270 100644
--- a/pw_protobuf/BUILD.gn
+++ b/pw_protobuf/BUILD.gn
@@ -57,11 +57,16 @@
   ]
 }
 
-# Source files for pw_protobuf's protoc plugin.
+# Entrypoint for pw_protobuf's protoc plugin.
 pw_input_group("codegen_protoc_plugin") {
+  inputs = [ "py/pw_protobuf/plugin.py" ]
+  deps = [ ":codegen_protoc_lib" ]
+}
+
+# Source files for pw_protobuf's protoc plugin.
+pw_input_group("codegen_protoc_lib") {
   inputs = [
     "py/pw_protobuf/codegen_pwpb.py",
-    "py/pw_protobuf/plugin.py",
     "py/pw_protobuf/proto_tree.py",
   ]
 }
diff --git a/pw_protobuf/py/pw_protobuf/proto_tree.py b/pw_protobuf/py/pw_protobuf/proto_tree.py
index e2a2c8a..95abf44 100644
--- a/pw_protobuf/py/pw_protobuf/proto_tree.py
+++ b/pw_protobuf/py/pw_protobuf/proto_tree.py
@@ -70,6 +70,11 @@
         return '::'.join(
             self._attr_hierarchy(lambda node: node.cpp_name(), root))
 
+    def proto_path(self) -> str:
+        """Fully-qualified package path of the node."""
+        path = '.'.join(self._attr_hierarchy(lambda node: node.name(), None))
+        return path.lstrip('.')
+
     def nanopb_name(self) -> str:
         """Full nanopb-style name of the node."""
         name = '_'.join(self._attr_hierarchy(lambda node: node.name(), None))
diff --git a/pw_rpc/BUILD.gn b/pw_rpc/BUILD.gn
index 83b2cc7..14aa95c 100644
--- a/pw_rpc/BUILD.gn
+++ b/pw_rpc/BUILD.gn
@@ -128,6 +128,7 @@
     dir_pw_containers,
     dir_pw_unit_test,
   ]
+  friend = [ "nanopb:*" ]
 }
 
 config("private_includes") {
@@ -146,6 +147,7 @@
     "py/pw_rpc/plugin.py",
     "py/pw_rpc/ids.py",
   ]
+  deps = [ "$dir_pw_protobuf:codegen_protoc_lib" ]
 }
 
 pw_doc_group("docs") {
diff --git a/pw_rpc/nanopb/codegen_test.cc b/pw_rpc/nanopb/codegen_test.cc
index 9e57ded..1cd40f0 100644
--- a/pw_rpc/nanopb/codegen_test.cc
+++ b/pw_rpc/nanopb/codegen_test.cc
@@ -13,6 +13,7 @@
 // the License.
 
 #include "gtest/gtest.h"
+#include "pw_rpc/internal/hash.h"
 #include "pw_rpc/test_method_context.h"
 #include "pw_rpc_test_protos/test_rpc.pb.h"
 
@@ -44,7 +45,7 @@
 
 TEST(NanopbCodegen, CompilesProperly) {
   test::TestService service;
-  EXPECT_EQ(service.id(), 0x105b6ac8u);
+  EXPECT_EQ(service.id(), Hash("pw.rpc.test.TestService"));
   EXPECT_STREQ(service.name(), "TestService");
 }
 
diff --git a/pw_rpc/py/pw_rpc/codegen_nanopb.py b/pw_rpc/py/pw_rpc/codegen_nanopb.py
index 5928ca2..7512cce 100644
--- a/pw_rpc/py/pw_rpc/codegen_nanopb.py
+++ b/pw_rpc/py/pw_rpc/codegen_nanopb.py
@@ -127,11 +127,11 @@
         for method in service.methods():
             _generate_code_for_method(method, output)
 
-    service_name_hash = pw_rpc.ids.calculate(service.name())
+    service_name_hash = pw_rpc.ids.calculate(service.proto_path())
     output.write_line('\n private:')
 
     with output.indent():
-        output.write_line(f'// 65599 hash of "{service.name()}".')
+        output.write_line(f'// 65599 hash of "{service.proto_path()}".')
         output.write_line(
             f'static constexpr uint32_t kServiceId = {hex(service_name_hash)};'
         )