aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexei Frolov <frolv@google.com>2021-04-12 14:53:06 -0700
committerCQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>2021-04-14 00:17:06 +0000
commit3e28092cd59477cf09727d6e6357cc993d565f9b (patch)
treeb8f6aba55024f0d4789a100c866eff12f87a08e4
parent4ea2de8fa56cd6fc5e622a229c4d94d77bcc2fe9 (diff)
downloadpigweed-3e28092cd59477cf09727d6e6357cc993d565f9b.tar.gz
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>
-rw-r--r--pw_rpc/BUILD19
-rw-r--r--pw_rpc/BUILD.gn20
-rw-r--r--pw_rpc/CMakeLists.txt8
-rw-r--r--pw_rpc/client_server.cc30
-rw-r--r--pw_rpc/client_server_test.cc85
-rw-r--r--pw_rpc/docs.rst23
-rw-r--r--pw_rpc/public/pw_rpc/client_server.h40
7 files changed, 225 insertions, 0 deletions
diff --git a/pw_rpc/BUILD b/pw_rpc/BUILD
index 996a8a0a3..b2cd78946 100644
--- a/pw_rpc/BUILD
+++ b/pw_rpc/BUILD
@@ -63,6 +63,16 @@ pw_cc_library(
)
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(
],
)
+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 5f98883bb..9f4a95fba 100644
--- a/pw_rpc/BUILD.gn
+++ b/pw_rpc/BUILD.gn
@@ -82,6 +82,16 @@ pw_source_set("client") {
]
}
+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 @@ pw_test_group("tests") {
":base_server_writer_test",
":channel_test",
":client_test",
+ ":client_server_test",
":ids_test",
":packet_test",
":server_test",
@@ -262,6 +273,15 @@ pw_test("client_test") {
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 b929d5a1c..c3635fe5e 100644
--- a/pw_rpc/CMakeLists.txt
+++ b/pw_rpc/CMakeLists.txt
@@ -43,6 +43,14 @@ pw_add_module_library(pw_rpc.client
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 000000000..f0c34ab8d
--- /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 000000000..5104c66c7
--- /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 fc041656a..daece792f 100644
--- a/pw_rpc/docs.rst
+++ b/pw_rpc/docs.rst
@@ -848,3 +848,26 @@ interfaces for working with RPCs.
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 000000000..219ed676c
--- /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