pw_rpc: nanopb codegen improvements

- Place generated services into a generated:: namespace, allowing
  derived classes to reuse the service name.
- Update generated headers from <file>_rpc.pb.h to <file>.rpc.pb.h.
- Add documentation about the nanopb generated code API.

Change-Id: Ibd6399cc23c98a1c25ad4018cb62216478ddd183
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/14382
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Alexei Frolov <frolv@google.com>
diff --git a/pw_protobuf_compiler/proto.gni b/pw_protobuf_compiler/proto.gni
index 935f081..373f5ef 100644
--- a/pw_protobuf_compiler/proto.gni
+++ b/pw_protobuf_compiler/proto.gni
@@ -114,7 +114,7 @@
 
   _outputs = []
   foreach(_proto, _relative_proto_paths) {
-    _output_h = string_replace(_proto, ".proto", "_rpc.pb.h")
+    _output_h = string_replace(_proto, ".proto", ".rpc.pb.h")
     _outputs += [ "$_proto_gen_dir/$_output_h" ]
   }
 
diff --git a/pw_rpc/nanopb/codegen_test.cc b/pw_rpc/nanopb/codegen_test.cc
index 5a717fa..dfb0f73 100644
--- a/pw_rpc/nanopb/codegen_test.cc
+++ b/pw_rpc/nanopb/codegen_test.cc
@@ -15,12 +15,12 @@
 #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"
+#include "pw_rpc_test_protos/test.rpc.pb.h"
 
 namespace pw::rpc {
 namespace test {
 
-class TestServiceImpl : public TestService<TestServiceImpl> {
+class TestService final : public generated::TestService<TestService> {
  public:
   Status TestRpc(ServerContext&,
                  const pw_rpc_test_TestRequest& request,
@@ -46,13 +46,13 @@
 namespace {
 
 TEST(NanopbCodegen, CompilesProperly) {
-  test::TestServiceImpl service;
+  test::TestService service;
   EXPECT_EQ(service.id(), Hash("pw.rpc.test.TestService"));
   EXPECT_STREQ(service.name(), "TestService");
 }
 
 TEST(NanopbCodegen, InvokeUnaryRpc) {
-  PW_RPC_TEST_METHOD_CONTEXT(test::TestServiceImpl, TestRpc) context;
+  PW_RPC_TEST_METHOD_CONTEXT(test::TestService, TestRpc) context;
 
   EXPECT_EQ(Status::OK,
             context.call({.integer = 123, .status_code = Status::OK}));
@@ -66,7 +66,7 @@
 }
 
 TEST(NanopbCodegen, InvokeStreamingRpc) {
-  PW_RPC_TEST_METHOD_CONTEXT(test::TestServiceImpl, TestStreamRpc) context;
+  PW_RPC_TEST_METHOD_CONTEXT(test::TestService, TestStreamRpc) context;
 
   context.call({.integer = 0, .status_code = Status::ABORTED});
 
@@ -88,7 +88,7 @@
 }
 
 TEST(NanopbCodegen, InvokeStreamingRpc_ContextKeepsFixedNumberOfResponses) {
-  PW_RPC_TEST_METHOD_CONTEXT(test::TestServiceImpl, TestStreamRpc, 3) context;
+  PW_RPC_TEST_METHOD_CONTEXT(test::TestService, TestStreamRpc, 3) context;
 
   ASSERT_EQ(3u, context.responses().max_size());
 
diff --git a/pw_rpc/nanopb/docs.rst b/pw_rpc/nanopb/docs.rst
index 99f88ec..32f87fb 100644
--- a/pw_rpc/nanopb/docs.rst
+++ b/pw_rpc/nanopb/docs.rst
@@ -7,7 +7,147 @@
 ------
 nanopb
 ------
+``pw_rpc`` can generate services which encode/decode RPC requests and responses
+as nanopb message structs.
 
-.. admonition:: TODO
+Usage
+=====
+To enable nanopb code generation, add ``nanopb_rpc`` as a generator to your
+Pigweed target's ``pw_protobuf_GENERATORS`` list. Refer to
+:ref:`chapter-pw-protobuf-compiler` for additional information.
 
-  Document the nanopb generated code API
+.. code::
+
+  # my_target/target_toolchains.gni
+
+  defaults = {
+    pw_protobuf_GENERATORS = [
+      "pwpb",
+      "nanopb_rpc",  # Enable RPC codegen
+    ]
+  }
+
+Define a ``pw_proto_library`` containing the .proto file defining your service
+(and optionally other related protos), then depend on the ``_nanopb_rpc``
+version of that library in the code implementing the service.
+
+.. code::
+
+  # chat/BUILD.gn
+
+  import("$dir_pw_build/target_types.gni")
+  import("$dir_pw_protobuf_compiler/proto.gni")
+
+  pw_proto_library("chat_protos") {
+    sources = [ "chat_protos/chat_service.proto" ]
+  }
+
+  # Library that implements the ChatService.
+  pw_source_set("chat_service") {
+    sources = [
+      "chat_service.cc",
+      "chat_service.h",
+    ]
+    public_deps = [ ":chat_protos_nanopb_rpc" ]
+  }
+
+A C++ header file is generated for each input .proto file, with the ``.proto``
+extension replaced by ``.rpc.pb.h``. For example, given the input file
+``chat_protos/chat_service.proto``, the generated header file will be placed
+at the include path ``"chat_protos/chat_service.rpc.pb.h"``.
+
+Generated code API
+==================
+Take the following RPC service as an example.
+
+.. code:: protobuf
+
+  // chat/chat_protos/chat_service.proto
+
+  syntax = "proto3";
+
+  service ChatService {
+    // Returns information about a chatroom.
+    rpc GetRoomInformation(RoomInfoRequest) returns (RoomInfoResponse) {}
+
+    // Lists all of the users in a chatroom. The response is streamed as there
+    // may be a large amount of users.
+    rpc ListUsersInRoom(ListUsersRequest) returns (stream ListUsersResponse) {}
+
+    // Uploads a file, in chunks, to a chatroom.
+    rpc UploadFile(stream UploadFileRequest) returns (UploadFileResponse) {}
+
+    // Sends messages to a chatroom while receiving messages from other users.
+    rpc Chat(stream ChatMessage) returns (stream ChatMessage) {}
+  }
+
+Server-side
+-----------
+A C++ class is generated for each service in the .proto file. The class is
+located within a special ``generated`` sub-namespace of the file's package.
+
+The generated class is a base class which must be derived to implement the
+service's methods. The base class is templated on the derived class.
+
+.. code:: c++
+
+  #include "chat_protos/chat_service.rpc.pb.h"
+
+  class ChatService final : public generated::ChatService<ChatService> {
+   public:
+    // Implementations of the service's RPC methods; see below.
+  };
+
+Unary RPC
+^^^^^^^^^
+A unary RPC is implemented as a function which takes in the RPC's request struct
+and populates a response struct to send back, with a status indicating whether
+the request succeeded.
+
+.. code:: c++
+
+  pw::Status GetRoomInformation(pw::rpc::ServerContext& ctx,
+                                const RoomInfoRequest& request,
+                                RoomInfoResponse& response);
+
+Server streaming RPC
+^^^^^^^^^^^^^^^^^^^^
+A server streaming RPC receives the client's request message alongside a
+``ServerWriter``, used to stream back responses.
+
+.. code:: c++
+
+  void ListUsersInRoom(pw::rpc::ServerContext& ctx,
+                       const ListUsersRequest& request,
+                       pw::rpc::ServerWriter<ListUsersResponse>& writer);
+
+The ``ServerWriter`` object is movable, and remains active until it is manually
+closed or goes out of scope. The writer has a simple API to return responses:
+
+.. cpp:function:: Status ServerWriter::Write(const T& response)
+
+  Writes a single response message to the stream. The returned status indicates
+  whether the write was successful.
+
+.. cpp:function:: void ServerWriter::Finish(Status status = Status::OK)
+
+  Closes the stream and sends back the RPC's overall status to the client.
+
+Once a ``ServerWriter`` has been closed, all future ``Write`` calls will fail.
+
+.. attention::
+
+  Make sure to use ``std::move`` when passing the ``ServerWriter`` around to
+  avoid accidentally closing it and ending the RPC.
+
+Client streaming RPC
+^^^^^^^^^^^^^^^^^^^^
+.. attention::
+
+  ``pw_rpc`` does not yet support client streaming RPCs.
+
+Bidirectional streaming RPC
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+.. attention::
+
+  ``pw_rpc`` does not yet support bidirectional streaming RPCs.
diff --git a/pw_rpc/nanopb/echo_service_test.cc b/pw_rpc/nanopb/echo_service_test.cc
index 54b4dda..490ebff 100644
--- a/pw_rpc/nanopb/echo_service_test.cc
+++ b/pw_rpc/nanopb/echo_service_test.cc
@@ -20,13 +20,13 @@
 namespace {
 
 TEST(EchoService, Echo_EchoesRequestMessage) {
-  PW_RPC_TEST_METHOD_CONTEXT(EchoServiceImpl, Echo) context;
+  PW_RPC_TEST_METHOD_CONTEXT(EchoService, Echo) context;
   ASSERT_EQ(context.call({.msg = "Hello, world"}), Status::OK);
   EXPECT_STREQ(context.response().msg, "Hello, world");
 }
 
 TEST(EchoService, Echo_EmptyRequest) {
-  PW_RPC_TEST_METHOD_CONTEXT(EchoServiceImpl, Echo) context;
+  PW_RPC_TEST_METHOD_CONTEXT(EchoService, Echo) context;
   ASSERT_EQ(context.call({.msg = ""}), Status::OK);
   EXPECT_STREQ(context.response().msg, "");
 }
diff --git a/pw_rpc/nanopb/public/pw_rpc/echo_service_nanopb.h b/pw_rpc/nanopb/public/pw_rpc/echo_service_nanopb.h
index 938a9d9..0ce388c 100644
--- a/pw_rpc/nanopb/public/pw_rpc/echo_service_nanopb.h
+++ b/pw_rpc/nanopb/public/pw_rpc/echo_service_nanopb.h
@@ -15,11 +15,11 @@
 
 #include <cstring>
 
-#include "pw_rpc_protos/echo_rpc.pb.h"
+#include "pw_rpc_protos/echo.rpc.pb.h"
 
 namespace pw::rpc {
 
-class EchoServiceImpl : public EchoService<EchoServiceImpl> {
+class EchoService final : public generated::EchoService<EchoService> {
  public:
   Status Echo(ServerContext&,
               const pw_rpc_EchoMessage& request,
diff --git a/pw_rpc/py/pw_rpc/codegen_nanopb.py b/pw_rpc/py/pw_rpc/codegen_nanopb.py
index 6e8b997..c8db83e 100644
--- a/pw_rpc/py/pw_rpc/codegen_nanopb.py
+++ b/pw_rpc/py/pw_rpc/codegen_nanopb.py
@@ -40,7 +40,7 @@
 def _proto_filename_to_generated_header(proto_file: str) -> str:
     """Returns the generated C++ RPC header name for a .proto file."""
     filename = os.path.splitext(proto_file)[0]
-    return f'{filename}_rpc{PROTO_H_EXTENSION}'
+    return f'{filename}.rpc{PROTO_H_EXTENSION}'
 
 
 def _generate_method_descriptor(method: ProtoServiceMethod,
@@ -201,21 +201,25 @@
     output.write_line('namespace pw::rpc::test_internal {\n')
     output.write_line('template <typename, uint32_t>')
     output.write_line('class ServiceTestUtilities;')
-    output.write_line('\n}  // namespace pw::rpc::test_internal')
+    output.write_line('\n}  // namespace pw::rpc::test_internal\n')
 
     if package.cpp_namespace():
         file_namespace = package.cpp_namespace()
         if file_namespace.startswith('::'):
             file_namespace = file_namespace[2:]
 
-        output.write_line(f'\nnamespace {file_namespace} {{')
+        output.write_line(f'namespace {file_namespace} {{')
+
+    output.write_line('namespace generated {')
 
     for node in package:
         if node.type() == ProtoNode.Type.SERVICE:
             _generate_code_for_service(node, package, output)
 
+    output.write_line('\n}  // namespace generated')
+
     if package.cpp_namespace():
-        output.write_line(f'\n}}  // namespace {file_namespace}')
+        output.write_line(f'}}  // namespace {file_namespace}')
 
 
 def process_proto_file(proto_file) -> Iterable[OutputFile]: