pw_rpc: Generic method invocations and tests
- Restructure the BUILD.gn file to define the pw_rpc server library with
a template. This makes it simple to build different versions of the
pw_rpc server library.
- Define the ServerContext class, which provides context for a
particular RPC invocation.
- Have the server invoke RPCs if it finds a method with matching IDs.
- Implement a test version of the Method class and expand the server
tests.
Change-Id: Id1f46a88f2618a649d72f8c694ee829aef80588c
diff --git a/pw_rpc/BUILD b/pw_rpc/BUILD
index 4d5936e..9c03beb 100644
--- a/pw_rpc/BUILD
+++ b/pw_rpc/BUILD
@@ -24,26 +24,61 @@
pw_cc_library(
name = "pw_rpc",
+ srcs = [
+ "public/pw_rpc/internal/service.h",
+ "public/pw_rpc/internal/service_registry.h",
+ "server.cc",
+ "service.cc",
+ "service_registry.cc",
+ ],
+ hdrs = [
+ "public/pw_rpc/server.h",
+ "public/pw_rpc/server_context.h",
+ # TODO(hepler): Only building the test version of the server for now.
+ "test_public/pw_rpc/internal/method.h",
+ ],
+ includes = [
+ "public",
+ "test_public",
+ ],
+ deps = [
+ ":common",
+ "//pw_assert",
+ "//pw_log",
+ "//pw_span",
+ "//pw_status",
+ ],
+)
+
+pw_cc_library(
+ name = "common",
+ srcs = [
+ "channel.cc",
+ "packet.cc",
+ ],
hdrs = [
"public/pw_rpc/channel.h",
+ "public/pw_rpc/internal/base_method.h",
+ "public/pw_rpc/internal/packet.h",
"public/pw_rpc/server.h",
],
includes = ["public"],
+ visibility = ["//visibility:private"],
deps = [
"//pw_assert",
"//pw_log",
"//pw_span",
"//pw_status",
],
+)
+
+pw_cc_test(
+ name = "packet_test",
srcs = [
- "channel.cc",
- "packet.cc",
- "public/pw_rpc/internal/packet.h",
- "public/pw_rpc/internal/service.h",
- "public/pw_rpc/internal/service_registry.h",
- "server.cc",
- "service.cc",
- "service_registry.cc",
+ "packet_test.cc",
+ ],
+ deps = [
+ ":common",
],
)
@@ -54,15 +89,6 @@
],
deps = [
":pw_rpc",
- ],
-)
-
-pw_cc_test(
- name = "packet_test",
- srcs = [
- "packet_test.cc",
- ],
- deps = [
- ":pw_rpc",
+ "//pw_assert",
],
)
diff --git a/pw_rpc/BUILD.gn b/pw_rpc/BUILD.gn
index 5226243..37aeba1 100644
--- a/pw_rpc/BUILD.gn
+++ b/pw_rpc/BUILD.gn
@@ -18,32 +18,69 @@
config("default_config") {
include_dirs = [ "public" ]
+ visibility = [ ":*" ]
}
-source_set("pw_rpc") {
- deps = [ ":protos_pwpb" ]
+# pw_rpc servers depend on the protobuf library used to encode and decode
+# requests and responses when invoking methods. This template is used to create
+# instances of the pw_rpc server library with different implementations.
+#
+# The implementation parameter must refer to a library that provides the
+# definition of the Method class in pw_rpc/internal/method.h. The Method class
+# provides the Invoke function, which handles the server use to call into the
+# RPC functions themselves.
+template("_pw_rpc_server_library") {
+ assert(defined(invoker.implementation),
+ "_pw_rpc_server_library requires an implementation to be set")
+
+ source_set(target_name) {
+ forward_variables_from(invoker, "*")
+
+ public_configs = [ ":default_config" ]
+ public_deps = [
+ ":common",
+ dir_pw_span,
+ dir_pw_status,
+ implementation,
+ ]
+ deps = [
+ dir_pw_assert,
+ dir_pw_log,
+ ]
+ public = [
+ "public/pw_rpc/server.h",
+ "public/pw_rpc/server_context.h",
+ ]
+ sources = [
+ "public/pw_rpc/internal/service.h",
+ "public/pw_rpc/internal/service_registry.h",
+ "server.cc",
+ "service.cc",
+ "service_registry.cc",
+ ]
+ allow_circular_includes_from = [ implementation ]
+ friend = [ ":*" ]
+ }
+}
+
+# Classes with no dependencies on the protobuf library for method invocations.
+source_set("common") {
public_configs = [ ":default_config" ]
public_deps = [
+ ":protos_pwpb",
dir_pw_assert,
- dir_pw_log,
dir_pw_span,
dir_pw_status,
]
- public = [
- "public/pw_rpc/channel.h",
- "public/pw_rpc/server.h",
- ]
+ public = [ "public/pw_rpc/channel.h" ]
sources = [
"channel.cc",
"packet.cc",
+ "public/pw_rpc/internal/base_method.h",
"public/pw_rpc/internal/packet.h",
- "public/pw_rpc/internal/service.h",
- "public/pw_rpc/internal/service_registry.h",
- "server.cc",
- "service.cc",
- "service_registry.cc",
]
friend = [ ":*" ]
+ visibility = [ ":*" ]
}
pw_proto_library("protos") {
@@ -61,18 +98,37 @@
]
}
-pw_test("server_test") {
- deps = [
- ":protos_pwpb",
- ":pw_rpc",
- ]
- sources = [ "server_test.cc" ]
+# RPC server for tests only. A mock method implementation is used.
+_pw_rpc_server_library("test_server") {
+ implementation = ":test_method"
+ visibility = [ ":*" ]
+}
+
+config("test_config") {
+ include_dirs = [ "test_public" ]
+ visibility = [ ":test_method" ]
+}
+
+source_set("test_method") {
+ public_configs = [ ":test_config" ]
+ public = [ "test_public/pw_rpc/internal/method.h" ]
+ public_deps = [ ":common" ]
+ visibility = [ ":test_server" ]
}
pw_test("packet_test") {
deps = [
- ":pw_rpc",
+ ":common",
dir_pw_protobuf,
]
sources = [ "packet_test.cc" ]
}
+
+pw_test("server_test") {
+ deps = [
+ ":protos_pwpb",
+ ":test_server",
+ dir_pw_assert,
+ ]
+ sources = [ "server_test.cc" ]
+}
diff --git a/pw_rpc/public/pw_rpc/channel.h b/pw_rpc/public/pw_rpc/channel.h
index 68fceca..133aa4f 100644
--- a/pw_rpc/public/pw_rpc/channel.h
+++ b/pw_rpc/public/pw_rpc/channel.h
@@ -58,14 +58,14 @@
constexpr uint32_t id() const { return id_; }
constexpr bool assigned() const { return id_ != kUnassignedChannelId; }
+ private:
+ friend class Server;
+
span<std::byte> AcquireBuffer() const { return output_->AcquireBuffer(); }
void SendAndReleaseBuffer(size_t size) const {
output_->SendAndReleaseBuffer(size);
}
- private:
- friend class Server;
-
constexpr Channel(uint32_t id, ChannelOutput* output)
: id_(id), output_(output) {
PW_CHECK_UINT_NE(id, kUnassignedChannelId);
diff --git a/pw_rpc/public/pw_rpc/internal/base_method.h b/pw_rpc/public/pw_rpc/internal/base_method.h
new file mode 100644
index 0000000..bf6c95f
--- /dev/null
+++ b/pw_rpc/public/pw_rpc/internal/base_method.h
@@ -0,0 +1,40 @@
+// 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.
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+
+namespace pw::rpc::internal {
+
+// RPC server implementations provide a Method class in the
+// pw_rpc/internal/method.h header that is derived from BaseMethod.
+class BaseMethod {
+ public:
+ constexpr uint32_t id() const { return id_; }
+
+ // Implementations must provide the Invoke method, which the Server calls:
+ //
+ // StatusWithSize Invoke(ServerContext& context,
+ // span<const std::byte> request,
+ // span<std::byte> payload_buffer) const;
+
+ protected:
+ constexpr BaseMethod(uint32_t id) : id_(id) {}
+
+ private:
+ uint32_t id_;
+};
+
+} // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/service.h b/pw_rpc/public/pw_rpc/internal/service.h
index 377b6c1..fb42585 100644
--- a/pw_rpc/public/pw_rpc/internal/service.h
+++ b/pw_rpc/public/pw_rpc/internal/service.h
@@ -14,39 +14,29 @@
#pragma once
#include <cstdint>
+#include <utility>
+#include "pw_rpc/internal/method.h"
#include "pw_span/span.h"
-#include "pw_status/status.h"
namespace pw::rpc::internal {
-// Forward-declare Packet instead of including it to avoid exposing the internal
-// dependency on pw_protobuf.
-class Packet;
-
-class ServiceRegistry;
-
-// Base class for all RPC services. This is not meant to be instantiated
-// directly; use a generated subclass instead.
+// Base class for all RPC services. This cannot be instantiated directly; use a
+// generated subclass instead.
class Service {
public:
- struct Method {
- uint32_t id;
- };
-
- Service(uint32_t id, span<const Method> methods)
- : id_(id), methods_(methods), next_(nullptr) {}
-
uint32_t id() const { return id_; }
- // Handles an incoming packet and populates a response. Errors that occur
- // should be set within the response packet.
- void ProcessPacket(const internal::Packet& request,
- internal::Packet& response,
- span<std::byte> payload_buffer);
+ // Finds the method with the provided method_id. Returns nullptr if no match.
+ const Method* FindMethod(uint32_t method_id) const;
+
+ protected:
+ template <typename T>
+ constexpr Service(uint32_t id, T&& methods)
+ : id_(id), methods_(std::forward<T>(methods)), next_(nullptr) {}
private:
- friend class internal::ServiceRegistry;
+ friend class ServiceRegistry;
uint32_t id_;
span<const Method> methods_;
diff --git a/pw_rpc/public/pw_rpc/server.h b/pw_rpc/public/pw_rpc/server.h
index 9a63946..dd55c93 100644
--- a/pw_rpc/public/pw_rpc/server.h
+++ b/pw_rpc/public/pw_rpc/server.h
@@ -19,6 +19,12 @@
#include "pw_rpc/internal/service_registry.h"
namespace pw::rpc {
+namespace internal {
+
+class Method;
+class Packet;
+
+} // namespace internal
class Server {
public:
@@ -42,7 +48,12 @@
using Service = internal::Service;
using ServiceRegistry = internal::ServiceRegistry;
- void SendResponse(const Channel& channel,
+ void InvokeMethod(const internal::Packet& request,
+ Channel& channel,
+ internal::Packet& response,
+ span<std::byte> buffer) const;
+
+ void SendResponse(const Channel& output,
const internal::Packet& response,
span<std::byte> response_buffer) const;
diff --git a/pw_rpc/public/pw_rpc/server_context.h b/pw_rpc/public/pw_rpc/server_context.h
new file mode 100644
index 0000000..2dcc3e0
--- /dev/null
+++ b/pw_rpc/public/pw_rpc/server_context.h
@@ -0,0 +1,55 @@
+// 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.
+#pragma once
+
+#include "pw_assert/assert.h"
+#include "pw_rpc/channel.h"
+
+namespace pw::rpc {
+namespace internal {
+
+class Method;
+class Service;
+
+} // namespace internal
+
+// The ServerContext collects context for an RPC being invoked on a server.
+class ServerContext {
+ public:
+ constexpr ServerContext()
+ : channel_(nullptr), service_(nullptr), method_(nullptr) {}
+
+ uint32_t channel_id() const {
+ PW_DCHECK_NOTNULL(channel_);
+ return channel_->id();
+ }
+
+ private:
+ friend class Server;
+
+ constexpr ServerContext(Channel& channel,
+ internal::Service& service,
+ const internal::Method& method)
+ : channel_(&channel), service_(&service), method_(&method) {
+ // Temporarily silence unused private field warnings.
+ (void)service_;
+ (void)method_;
+ }
+
+ Channel* channel_;
+ internal::Service* service_;
+ const internal::Method* method_;
+};
+
+} // namespace pw::rpc
diff --git a/pw_rpc/server.cc b/pw_rpc/server.cc
index a797234..879cae4 100644
--- a/pw_rpc/server.cc
+++ b/pw_rpc/server.cc
@@ -15,15 +15,19 @@
#include "pw_rpc/server.h"
#include "pw_log/log.h"
+#include "pw_rpc/internal/method.h"
#include "pw_rpc/internal/packet.h"
+#include "pw_rpc/server_context.h"
namespace pw::rpc {
+using std::byte;
+
+using internal::Method;
using internal::Packet;
using internal::PacketType;
-void Server::ProcessPacket(span<const std::byte> data,
- ChannelOutput& interface) {
+void Server::ProcessPacket(span<const byte> data, ChannelOutput& interface) {
Packet packet = Packet::FromBuffer(data);
if (packet.is_control()) {
// TODO(frolv): Handle control packets.
@@ -55,22 +59,46 @@
}
}
- span<std::byte> response_buffer = channel->AcquireBuffer();
- span<std::byte> payload_buffer = packet.PayloadUsableSpace(response_buffer);
-
response.set_channel_id(channel->id());
+ const span<byte> response_buffer = channel->AcquireBuffer();
- Service* service = services_.Find(packet.service_id());
+ // Invoke the method with matching service and method IDs, if any.
+ InvokeMethod(packet, *channel, response, response_buffer);
+ SendResponse(*channel, response, response_buffer);
+}
+
+void Server::InvokeMethod(const Packet& request,
+ Channel& channel,
+ internal::Packet& response,
+ span<std::byte> buffer) const {
+ Service* service = services_.Find(request.service_id());
if (service == nullptr) {
// Couldn't find the requested service. Reply with a NOT_FOUND response
- // without the server_id field set.
+ // without the service_id field set.
response.set_status(Status::NOT_FOUND);
- SendResponse(*channel, response, response_buffer);
return;
}
- service->ProcessPacket(packet, response, payload_buffer);
- SendResponse(*channel, response, response_buffer);
+ response.set_service_id(service->id());
+
+ const internal::Method* method = service->FindMethod(request.method_id());
+
+ if (method == nullptr) {
+ // Couldn't find the requested method. Reply with a NOT_FOUND response
+ // without the method_id field set.
+ response.set_status(Status::NOT_FOUND);
+ return;
+ }
+
+ response.set_method_id(method->id());
+
+ ServerContext context(channel, *service, *method);
+
+ span<byte> response_buffer = request.PayloadUsableSpace(buffer);
+ StatusWithSize result =
+ method->Invoke(context, request.payload(), response_buffer);
+ response.set_status(result.status());
+ response.set_payload(response_buffer.first(result.size()));
}
Channel* Server::FindChannel(uint32_t id) const {
@@ -94,7 +122,7 @@
void Server::SendResponse(const Channel& channel,
const Packet& response,
- span<std::byte> response_buffer) const {
+ span<byte> response_buffer) const {
StatusWithSize sws = response.Encode(response_buffer);
if (!sws.ok()) {
// TODO(frolv): What should be done here?
@@ -106,4 +134,8 @@
channel.SendAndReleaseBuffer(sws.size());
}
+static_assert(std::is_base_of<internal::BaseMethod, internal::Method>(),
+ "The Method implementation must be derived from "
+ "pw::rpc::internal::BaseMethod");
+
} // namespace pw::rpc
diff --git a/pw_rpc/server_test.cc b/pw_rpc/server_test.cc
index 984f5d1..b242088 100644
--- a/pw_rpc/server_test.cc
+++ b/pw_rpc/server_test.cc
@@ -14,15 +14,22 @@
#include "pw_rpc/server.h"
+#include <array>
+#include <cstdint>
+
#include "gtest/gtest.h"
+#include "pw_assert/assert.h"
#include "pw_rpc/internal/packet.h"
+#include "pw_rpc/internal/service.h"
namespace pw::rpc {
namespace {
+using std::byte;
+
+using internal::Method;
using internal::Packet;
using internal::PacketType;
-using std::byte;
template <size_t buffer_size>
class TestOutput : public ChannelOutput {
@@ -42,106 +49,153 @@
span<const byte> sent_packet_;
};
-Packet MakePacket(uint32_t channel_id,
- uint32_t service_id,
- uint32_t method_id,
- span<const byte> payload) {
- Packet packet(PacketType::RPC);
- packet.set_channel_id(channel_id);
- packet.set_service_id(service_id);
- packet.set_method_id(method_id);
- packet.set_payload(payload);
- return packet;
+class TestService : public internal::Service {
+ public:
+ TestService(uint32_t service_id)
+ : internal::Service(service_id, methods_),
+ methods_{
+ Method(100),
+ Method(200),
+ } {}
+
+ Method& method(uint32_t id) {
+ for (Method& method : methods_) {
+ if (method.id() == id) {
+ return method;
+ }
+ }
+
+ PW_CRASH("Invalid method ID %u", static_cast<unsigned>(id));
+ }
+
+ private:
+ std::array<Method, 2> methods_;
+};
+
+class BasicServer : public ::testing::Test {
+ protected:
+ static constexpr byte kDefaultPayload[] = {
+ byte(0x82), byte(0x02), byte(0xff), byte(0xff)};
+
+ BasicServer()
+ : output_(1),
+ channels_{
+ Channel::Create<1>(&output_),
+ Channel::Create<2>(&output_),
+ Channel(), // available for assignment
+ },
+ server_(channels_),
+ service_(42) {
+ server_.RegisterService(service_);
+ }
+
+ TestOutput<128> output_;
+ std::array<Channel, 3> channels_;
+ Server server_;
+ TestService service_;
+
+ span<const byte> EncodeRequest(PacketType type,
+ uint32_t channel_id,
+ uint32_t service_id,
+ uint32_t method_id,
+ span<const byte> payload = kDefaultPayload) {
+ auto sws = Packet(type, channel_id, service_id, method_id, payload)
+ .Encode(request_buffer_);
+ EXPECT_EQ(Status::OK, sws.status());
+ return span(request_buffer_, sws.size());
+ }
+
+ private:
+ byte request_buffer_[64];
+};
+
+TEST_F(BasicServer, ProcessPacket_ValidMethod_InvokesMethod) {
+ server_.ProcessPacket(EncodeRequest(PacketType::RPC, 1, 42, 100), output_);
+
+ const Method& method = service_.method(100);
+ EXPECT_EQ(1u, method.last_channel_id());
+ EXPECT_EQ(sizeof(kDefaultPayload), method.last_request().size());
+ EXPECT_EQ(std::memcmp(kDefaultPayload,
+ method.last_request().data(),
+ method.last_request().size()),
+ 0);
}
-TEST(Server, ProcessPacket_SendsResponse) {
- TestOutput<128> output(1);
- Channel channels[] = {
- Channel::Create<1>(&output),
- Channel::Create<2>(&output),
- };
- Server server(channels);
- internal::Service service(42, {});
- server.RegisterService(service);
+TEST_F(BasicServer, ProcessPacket_ValidMethod_SendsOkResponse) {
+ server_.ProcessPacket(EncodeRequest(PacketType::RPC, 1, 42, 100), output_);
- byte encoded_packet[64];
- constexpr byte payload[] = {byte(0x82), byte(0x02), byte(0xff), byte(0xff)};
- Packet request = MakePacket(1, 42, 27, payload);
- auto sws = request.Encode(encoded_packet);
-
- server.ProcessPacket(span(encoded_packet, sws.size()), output);
- Packet packet = Packet::FromBuffer(output.sent_packet());
- EXPECT_EQ(packet.status(), Status::OK);
+ Packet packet = Packet::FromBuffer(output_.sent_packet());
EXPECT_EQ(packet.channel_id(), 1u);
EXPECT_EQ(packet.service_id(), 42u);
+ EXPECT_EQ(packet.method_id(), 100u);
+ EXPECT_TRUE(packet.payload().empty());
+ EXPECT_EQ(packet.status(), Status::OK);
}
-TEST(Server, ProcessPacket_SendsNotFoundOnInvalidService) {
- TestOutput<128> output(1);
- Channel channels[] = {
- Channel::Create<1>(&output),
- Channel::Create<2>(&output),
- };
- Server server(channels);
- internal::Service service(42, {});
- server.RegisterService(service);
+TEST_F(BasicServer, ProcessPacket_ValidMethod_SendsErrorResponse) {
+ constexpr byte resp[] = {byte{0xf0}, byte{0x0d}};
+ service_.method(200).set_response(resp);
+ service_.method(200).set_status(Status::FAILED_PRECONDITION);
- byte encoded_packet[64];
- constexpr byte payload[] = {byte(0x82), byte(0x02), byte(0xff), byte(0xff)};
- Packet request = MakePacket(1, 43, 27, payload);
- auto sws = request.Encode(encoded_packet);
+ server_.ProcessPacket(EncodeRequest(PacketType::RPC, 2, 42, 200), output_);
- server.ProcessPacket(span(encoded_packet, sws.size()), output);
- Packet packet = Packet::FromBuffer(output.sent_packet());
+ Packet packet = Packet::FromBuffer(output_.sent_packet());
+ EXPECT_EQ(packet.channel_id(), 2u);
+ EXPECT_EQ(packet.service_id(), 42u);
+ EXPECT_EQ(packet.method_id(), 200u);
+ EXPECT_EQ(packet.status(), Status::FAILED_PRECONDITION);
+ ASSERT_EQ(sizeof(resp), packet.payload().size());
+ EXPECT_EQ(std::memcmp(packet.payload().data(), resp, sizeof(resp)), 0);
+}
+
+TEST_F(BasicServer, ProcessPacket_InvalidMethod_NothingIsInvoked) {
+ server_.ProcessPacket(EncodeRequest(PacketType::RPC, 1, 42, 101), output_);
+
+ EXPECT_EQ(0u, service_.method(100).last_channel_id());
+ EXPECT_EQ(0u, service_.method(200).last_channel_id());
+}
+
+TEST_F(BasicServer, ProcessPacket_InvalidMethod_SendsNotFound) {
+ server_.ProcessPacket(EncodeRequest(PacketType::RPC, 1, 42, 27), output_);
+
+ Packet packet = Packet::FromBuffer(output_.sent_packet());
+ EXPECT_EQ(packet.channel_id(), 1u);
+ EXPECT_EQ(packet.service_id(), 42u);
+ EXPECT_EQ(packet.method_id(), 0u); // No method ID 27
+ EXPECT_EQ(packet.status(), Status::NOT_FOUND);
+}
+
+TEST_F(BasicServer, ProcessPacket_InvalidService_SendsNotFound) {
+ server_.ProcessPacket(EncodeRequest(PacketType::RPC, 1, 43, 27), output_);
+
+ Packet packet = Packet::FromBuffer(output_.sent_packet());
EXPECT_EQ(packet.status(), Status::NOT_FOUND);
EXPECT_EQ(packet.channel_id(), 1u);
EXPECT_EQ(packet.service_id(), 0u);
}
-TEST(Server, ProcessPacket_AssignsAnUnassignedChannel) {
- TestOutput<128> output(1);
- Channel channels[] = {
- Channel::Create<1>(&output),
- Channel::Create<2>(&output),
- Channel(),
- };
- Server server(channels);
- internal::Service service(42, {});
- server.RegisterService(service);
-
- byte encoded_packet[64];
- constexpr byte payload[] = {byte(0x82), byte(0x02), byte(0xff), byte(0xff)};
- Packet request = MakePacket(/*channel_id=*/99, 42, 27, payload);
- auto sws = request.Encode(encoded_packet);
-
+TEST_F(BasicServer, ProcessPacket_UnassignedChannel_AssignsToAvalableSlot) {
TestOutput<128> unassigned_output(2);
- server.ProcessPacket(span(encoded_packet, sws.size()), unassigned_output);
- ASSERT_EQ(channels[2].id(), 99u);
+ server_.ProcessPacket(
+ EncodeRequest(PacketType::RPC, /*channel_id=*/99, 42, 27),
+ unassigned_output);
+ ASSERT_EQ(channels_[2].id(), 99u);
Packet packet = Packet::FromBuffer(unassigned_output.sent_packet());
- EXPECT_EQ(packet.status(), Status::OK);
EXPECT_EQ(packet.channel_id(), 99u);
EXPECT_EQ(packet.service_id(), 42u);
+ EXPECT_EQ(packet.method_id(), 0u); // No method ID 27
+ EXPECT_EQ(packet.status(), Status::NOT_FOUND);
}
-TEST(Server, ProcessPacket_SendsResourceExhaustedWhenChannelCantBeAssigned) {
- TestOutput<128> output(1);
- Channel channels[] = {
- Channel::Create<1>(&output),
- Channel::Create<2>(&output),
- };
- Server server(channels);
- internal::Service service(42, {});
- server.RegisterService(service);
+TEST_F(BasicServer,
+ ProcessPacket_UnassignedChannel_SendsResourceExhaustedIfCannotAssign) {
+ channels_[2] = Channel::Create<3>(&output_); // Occupy only available channel
- byte encoded_packet[64];
- constexpr byte payload[] = {byte(0x82), byte(0x02), byte(0xff), byte(0xff)};
- Packet request = MakePacket(/*channel_id=*/99, 42, 27, payload);
- auto sws = request.Encode(encoded_packet);
+ server_.ProcessPacket(
+ EncodeRequest(PacketType::RPC, /*channel_id=*/99, 42, 27), output_);
- server.ProcessPacket(span(encoded_packet, sws.size()), output);
-
- Packet packet = Packet::FromBuffer(output.sent_packet());
+ Packet packet = Packet::FromBuffer(output_.sent_packet());
EXPECT_EQ(packet.status(), Status::RESOURCE_EXHAUSTED);
EXPECT_EQ(packet.channel_id(), 0u);
EXPECT_EQ(packet.service_id(), 0u);
diff --git a/pw_rpc/service.cc b/pw_rpc/service.cc
index 819e6cd..b0ddd23 100644
--- a/pw_rpc/service.cc
+++ b/pw_rpc/service.cc
@@ -14,23 +14,18 @@
#include "pw_rpc/internal/service.h"
-#include "pw_rpc/internal/packet.h"
+#include <type_traits>
namespace pw::rpc::internal {
-void Service::ProcessPacket(const Packet& request,
- Packet& response,
- span<std::byte> payload_buffer) {
- response.set_service_id(id_);
-
+const Method* Service::FindMethod(uint32_t method_id) const {
for (const Method& method : methods_) {
- if (method.id == request.method_id()) {
- // TODO(frolv): call the method i guess
- response.set_method_id(method.id);
+ if (method.id() == method_id) {
+ return &method;
}
}
- (void)payload_buffer;
+ return nullptr;
}
} // namespace pw::rpc::internal
diff --git a/pw_rpc/test_public/pw_rpc/internal/method.h b/pw_rpc/test_public/pw_rpc/internal/method.h
new file mode 100644
index 0000000..ecd8f02
--- /dev/null
+++ b/pw_rpc/test_public/pw_rpc/internal/method.h
@@ -0,0 +1,64 @@
+// 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.
+#pragma once
+
+#include <cstdint>
+#include <cstring>
+
+#include "pw_rpc/internal/base_method.h"
+#include "pw_rpc/server_context.h"
+#include "pw_span/span.h"
+#include "pw_status/status_with_size.h"
+
+namespace pw::rpc::internal {
+
+// This is a fake RPC method implementation for testing only. It stores the
+// channel ID, request, and payload buffer, and optionally provides a response.
+class Method : public BaseMethod {
+ public:
+ constexpr Method(uint32_t id) : BaseMethod(id), last_channel_id_(0) {}
+
+ StatusWithSize Invoke(ServerContext& context,
+ span<const std::byte> request,
+ span<std::byte> payload_buffer) const {
+ last_channel_id_ = context.channel_id();
+ last_request_ = request;
+ last_payload_buffer_ = payload_buffer;
+
+ std::memcpy(payload_buffer.data(),
+ response_.data(),
+ std::min(response_.size(), payload_buffer.size()));
+ return StatusWithSize(response_status_, response_.size());
+ }
+
+ uint32_t last_channel_id() const { return last_channel_id_; }
+ span<const std::byte> last_request() const { return last_request_; }
+ span<std::byte> last_payload_buffer() const { return last_payload_buffer_; }
+
+ void set_response(span<const std::byte> payload) { response_ = payload; }
+ void set_status(Status status) { response_status_ = status; }
+
+ private:
+ // Make these mutable so they can be set in the Invoke method, which is const.
+ // The Method class is used exclusively in tests. Having these members mutable
+ // allows tests to verify that the Method is invoked correctly.
+ mutable uint32_t last_channel_id_;
+ mutable span<const std::byte> last_request_;
+ mutable span<std::byte> last_payload_buffer_;
+
+ span<const std::byte> response_;
+ Status response_status_;
+};
+
+} // namespace pw::rpc::internal