pw_rpc: Add ClientServer combination
This adds a class which wraps both an RPC client and server, simplifying
setup and usage in systems that require both.
Change-Id: I00e3cbeef91b8703c432800f58a96db5faff63f4
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/40624
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Alexei Frolov <frolv@google.com>
diff --git a/pw_rpc/BUILD b/pw_rpc/BUILD
index 996a8a0..b2cd789 100644
--- a/pw_rpc/BUILD
+++ b/pw_rpc/BUILD
@@ -63,6 +63,16 @@
)
pw_cc_library(
+ name = "client_server",
+ srcs = ["client_server.cc"],
+ hdrs = ["public/pw_rpc/client_server.h"],
+ deps = [
+ ":client",
+ ":server",
+ ],
+)
+
+pw_cc_library(
name = "common",
srcs = [
"channel.cc",
@@ -186,6 +196,15 @@
],
)
+pw_cc_test(
+ name = "client_server_test",
+ srcs = ["client_server_test.cc"],
+ deps = [
+ ":client_server",
+ "//pw_rpc/raw:method_union",
+ ],
+)
+
proto_library(
name = "packet_proto",
srcs = [
diff --git a/pw_rpc/BUILD.gn b/pw_rpc/BUILD.gn
index 5f98883..9f4a95f 100644
--- a/pw_rpc/BUILD.gn
+++ b/pw_rpc/BUILD.gn
@@ -82,6 +82,16 @@
]
}
+pw_source_set("client_server") {
+ public_configs = [ ":public_include_path" ]
+ public_deps = [
+ ":client",
+ ":server",
+ ]
+ public = [ "public/pw_rpc/client_server.h" ]
+ sources = [ "client_server.cc" ]
+}
+
# Classes shared by the server and client.
pw_source_set("common") {
public_configs = [ ":public_include_path" ]
@@ -184,6 +194,7 @@
":base_server_writer_test",
":channel_test",
":client_test",
+ ":client_server_test",
":ids_test",
":packet_test",
":server_test",
@@ -262,6 +273,15 @@
sources = [ "client_test.cc" ]
}
+pw_test("client_server_test") {
+ deps = [
+ ":client_server",
+ ":test_utils",
+ "raw:method_union",
+ ]
+ sources = [ "client_server_test.cc" ]
+}
+
pw_test("base_client_call_test") {
deps = [
":client",
diff --git a/pw_rpc/CMakeLists.txt b/pw_rpc/CMakeLists.txt
index b929d5a..c3635fe 100644
--- a/pw_rpc/CMakeLists.txt
+++ b/pw_rpc/CMakeLists.txt
@@ -43,6 +43,14 @@
pw_log
)
+pw_add_module_library(pw_rpc.client_server
+ SOURCES
+ client_server.cc
+ PUBLIC_DEPS
+ pw_rpc.client
+ pw_rpc.server
+)
+
pw_add_module_library(pw_rpc.common
SOURCES
channel.cc
diff --git a/pw_rpc/client_server.cc b/pw_rpc/client_server.cc
new file mode 100644
index 0000000..f0c34ab
--- /dev/null
+++ b/pw_rpc/client_server.cc
@@ -0,0 +1,30 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pw_rpc/client_server.h"
+
+namespace pw::rpc {
+
+Status ClientServer::ProcessPacket(std::span<const std::byte> packet,
+ ChannelOutput& interface) {
+ Status status = server_.ProcessPacket(packet, interface);
+ if (status.IsInvalidArgument()) {
+ // INVALID_ARGUMENT indicates the packet is intended for a client.
+ status = client_.ProcessPacket(packet);
+ }
+
+ return status;
+}
+
+} // namespace pw::rpc
diff --git a/pw_rpc/client_server_test.cc b/pw_rpc/client_server_test.cc
new file mode 100644
index 0000000..5104c66
--- /dev/null
+++ b/pw_rpc/client_server_test.cc
@@ -0,0 +1,85 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pw_rpc/client_server.h"
+
+#include "gtest/gtest.h"
+#include "pw_rpc/internal/packet.h"
+#include "pw_rpc/internal/raw_method_union.h"
+#include "pw_rpc/server_context.h"
+#include "pw_rpc/service.h"
+#include "pw_rpc_private/internal_test_utils.h"
+
+namespace pw::rpc::internal {
+namespace {
+
+constexpr uint32_t kFakeChannelId = 1;
+constexpr uint32_t kFakeServiceId = 3;
+constexpr uint32_t kFakeMethodId = 10;
+
+TestOutput<32> output;
+rpc::Channel channels[] = {Channel::Create<kFakeChannelId>(&output)};
+
+StatusWithSize FakeMethod(ServerContext&, ConstByteSpan, ByteSpan) {
+ return StatusWithSize::Unimplemented();
+}
+
+class FakeService : public Service {
+ public:
+ FakeService(uint32_t id) : Service(id, kMethods) {}
+
+ static constexpr std::array<RawMethodUnion, 1> kMethods = {
+ RawMethod::Unary<FakeMethod>(kFakeMethodId),
+ };
+};
+
+FakeService service(kFakeServiceId);
+
+TEST(ClientServer, ProcessPacket_CallsServer) {
+ ClientServer client_server(channels);
+ client_server.server().RegisterService(service);
+
+ Packet packet(
+ PacketType::REQUEST, kFakeChannelId, kFakeServiceId, kFakeMethodId);
+ std::array<std::byte, 32> buffer;
+ Result result = packet.Encode(buffer);
+ EXPECT_EQ(result.status(), OkStatus());
+
+ EXPECT_EQ(client_server.ProcessPacket(result.value(), output), OkStatus());
+}
+
+TEST(ClientServer, ProcessPacket_CallsClient) {
+ ClientServer client_server(channels);
+ client_server.server().RegisterService(service);
+
+ // Same packet as above, but type RESPONSE will skip the server and call into
+ // the client.
+ Packet packet(
+ PacketType::RESPONSE, kFakeChannelId, kFakeServiceId, kFakeMethodId);
+ std::array<std::byte, 32> buffer;
+ Result result = packet.Encode(buffer);
+ EXPECT_EQ(result.status(), OkStatus());
+
+ // No calls are registered on the client, so this should fail.
+ EXPECT_EQ(client_server.ProcessPacket(result.value(), output),
+ Status::NotFound());
+}
+
+TEST(ClientServer, ProcessPacket_BadData) {
+ ClientServer client_server(channels);
+ EXPECT_EQ(client_server.ProcessPacket({}, output), Status::DataLoss());
+}
+
+} // namespace
+} // namespace pw::rpc::internal
diff --git a/pw_rpc/docs.rst b/pw_rpc/docs.rst
index fc04165..daece79 100644
--- a/pw_rpc/docs.rst
+++ b/pw_rpc/docs.rst
@@ -848,3 +848,26 @@
The RPC server stores a list of all of active ``ClientCall`` objects. When an
incoming packet is recieved, it dispatches to one of its active calls, which
then decodes the payload and presents it to the user.
+
+ClientServer
+============
+Sometimes, a device needs to both process RPCs as a server, as well as making
+calls to another device as a client. To do this, both a client and server must
+be set up, and incoming packets must be sent to both of them.
+
+Pigweed simplifies this setup by providing a ``ClientServer`` class which wraps
+an RPC client and server with the same set of channels.
+
+.. code-block:: cpp
+
+ pw::rpc::Channel channels[] = {
+ pw::rpc::Channel::Create<1>(&channel_output)};
+
+ // Creates both a client and a server.
+ pw::rpc::ClientServer client_server(channels);
+
+ void ProcessRpcData(pw::ConstByteSpan packet) {
+ // Calls into both the client and the server, sending the packet to the
+ // appropriate one.
+ client_server.ProcessPacket(packet, output);
+ }
diff --git a/pw_rpc/public/pw_rpc/client_server.h b/pw_rpc/public/pw_rpc/client_server.h
new file mode 100644
index 0000000..219ed67
--- /dev/null
+++ b/pw_rpc/public/pw_rpc/client_server.h
@@ -0,0 +1,40 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "pw_rpc/client.h"
+#include "pw_rpc/server.h"
+
+namespace pw::rpc {
+
+// Class that wraps both an RPC client and a server, simplifying RPC setup when
+// a device needs to function as both.
+class ClientServer {
+ public:
+ constexpr ClientServer(std::span<Channel> channels)
+ : client_(channels), server_(channels) {}
+
+ // Sends a packet to either the client or the server, depending on its type.
+ Status ProcessPacket(std::span<const std::byte> packet,
+ ChannelOutput& interface);
+
+ constexpr Client& client() { return client_; }
+ constexpr Server& server() { return server_; }
+
+ private:
+ Client client_;
+ Server server_;
+};
+
+} // namespace pw::rpc