pw_rpc: Check RPC method signature; improve errors
- Fix issue where an RPC method could be implemented with the signature
of a different RPC type. Pass the expected method type when methods
are created and check that it matches the type deduced from the method
signature.
- Reduce code duplication to limit the number of places where
compilation errors occur.
- Combine variable RPC method traits classes into MethodTraits.
- Move all Method creation functionality into the method classes.
- Put the final method invocation logic into one function that
supports member functions or static functions.
- Consolidate error messages for incorrect method signatures into one
place. Expand the messages and describe the expected function
signature.
Change-Id: Iae3c6d32df715e877e8c977ccd2232814ef590a7
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/25641
Commit-Queue: Wyatt Hepler <hepler@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_protobuf/py/pw_protobuf/proto_tree.py b/pw_protobuf/py/pw_protobuf/proto_tree.py
index 95abf44..de5a4e5 100644
--- a/pw_protobuf/py/pw_protobuf/proto_tree.py
+++ b/pw_protobuf/py/pw_protobuf/proto_tree.py
@@ -324,10 +324,14 @@
class ProtoServiceMethod:
"""A method defined in a protobuf service."""
class Type(enum.Enum):
- UNARY = 0
- SERVER_STREAMING = 1
- CLIENT_STREAMING = 2
- BIDIRECTIONAL_STREAMING = 3
+ UNARY = 'kUnary'
+ SERVER_STREAMING = 'kServerStreaming'
+ CLIENT_STREAMING = 'kClientStreaming'
+ BIDIRECTIONAL_STREAMING = 'kBidirectionalStreaming'
+
+ def cc_enum(self) -> str:
+ """Returns the pw_rpc MethodType C++ enum for this method type."""
+ return '::pw::rpc::internal::MethodType::' + self.value
def __init__(self, name: str, method_type: Type, request_type: ProtoNode,
response_type: ProtoNode):
diff --git a/pw_rpc/BUILD b/pw_rpc/BUILD
index a7c4a83..260f2d3 100644
--- a/pw_rpc/BUILD
+++ b/pw_rpc/BUILD
@@ -25,8 +25,8 @@
pw_cc_library(
name = "client",
srcs = [
- "client.cc",
"base_client_call.cc",
+ "client.cc",
],
hdrs = [
"public/pw_rpc/client.h",
@@ -34,7 +34,7 @@
],
deps = [
":common",
- ]
+ ],
)
pw_cc_library(
@@ -83,15 +83,6 @@
)
pw_cc_library(
- name = "service_method_traits",
- hdrs = [
- "public/pw_rpc/internal/service_method_traits.h",
- ],
- includes = ["public"],
- deps = [ ":server" ],
-)
-
-pw_cc_library(
name = "internal_test_utils",
hdrs = [
"public/pw_rpc/internal/test_method.h",
@@ -121,11 +112,9 @@
"nanopb/public/pw_rpc/internal/nanopb_common.h",
"nanopb/public/pw_rpc/internal/nanopb_method.h",
"nanopb/public/pw_rpc/internal/nanopb_method_union.h",
- "nanopb/public/pw_rpc/internal/nanopb_service_method_traits.h",
"nanopb/public/pw_rpc/nanopb_client_call.h",
"nanopb/public/pw_rpc/nanopb_test_method_context.h",
"nanopb/pw_rpc_nanopb_private/internal_test_utils.h",
- "nanopb/nanopb_service_method_traits_test.cc",
"nanopb/test.pb.c",
"nanopb/test.pb.h",
"nanopb/test_rpc.pb.h",
diff --git a/pw_rpc/BUILD.gn b/pw_rpc/BUILD.gn
index 01228ad..a04c2e6 100644
--- a/pw_rpc/BUILD.gn
+++ b/pw_rpc/BUILD.gn
@@ -86,12 +86,6 @@
friend = [ "./*" ]
}
-pw_source_set("service_method_traits") {
- public = [ "public/pw_rpc/internal/service_method_traits.h" ]
- public_deps = [ ":server" ]
- visibility = [ "./*" ]
-}
-
pw_source_set("test_utils") {
public = [
"public/pw_rpc/internal/test_method.h",
diff --git a/pw_rpc/docs.rst b/pw_rpc/docs.rst
index 2e62c31..0d5d029 100644
--- a/pw_rpc/docs.rst
+++ b/pw_rpc/docs.rst
@@ -604,26 +604,18 @@
^^^^^^^^^^^^^^^^
The RPC Server depends on the ``pw::rpc::internal::Method`` class. ``Method``
serves as the bridge between the ``pw_rpc`` server library and the user-defined
-RPC functions. ``Method`` takes an RPC packet, decodes it using a protobuf
-library (if applicable), and calls the RPC function. Since ``Method`` interacts
-directly with the protobuf library, it must be implemented separately for each
-protobuf library.
+RPC functions. Each supported protobuf implementation extends ``Method`` to
+implement its request and response proto handling. The ``pw_rpc`` server
+calls into the ``Method`` implementation through the base class's ``Invoke``
+function.
-``pw::rpc::internal::Method`` is not implemented as a facade with different
-backends. Instead, there is a separate instance of the ``pw_rpc`` server library
-for each ``Method`` implementation. There are a few reasons for this.
+``Method`` implementations store metadata about each method, including a
+function pointer to the user-defined method implementation. They also provide
+``static constexpr`` functions for creating each type of method. ``Method``
+implementations must satisfy the ``MethodImplTester`` test class in
+``pw_rpc_private/method_impl_tester.h``.
-* ``Method`` is entirely internal to ``pw_rpc``. Users will never implement a
- custom backend. Exposing a facade would unnecessarily expose implementation
- details and make ``pw_rpc`` more difficult to use.
-* There is no common interface between ``pw_rpc`` / ``Method`` implementations.
- It's not possible to swap between e.g. a Nanopb and a ``pw_protobuf`` RPC
- server because the interface for the user-implemented RPCs changes completely.
- This nullifies the primary benefit of facades.
-* The different ``Method`` implementations can be built easily alongside one
- another in a cross-platform way. This makes testing simpler, since the tests
- build with any backend configuration. Users can select which ``Method``
- implementation to use simply by depending on the corresponding server library.
+See ``pw_rpc/internal/method.h`` for more details about ``Method``.
Packet flow
^^^^^^^^^^^
diff --git a/pw_rpc/nanopb/BUILD.gn b/pw_rpc/nanopb/BUILD.gn
index 39edd13..b3ee46a 100644
--- a/pw_rpc/nanopb/BUILD.gn
+++ b/pw_rpc/nanopb/BUILD.gn
@@ -65,21 +65,11 @@
}
}
-pw_source_set("service_method_traits") {
- public_configs = [ ":public" ]
- public = [ "public/pw_rpc/internal/nanopb_service_method_traits.h" ]
- public_deps = [
- ":method_union",
- "..:service_method_traits",
- ]
-}
-
pw_source_set("test_method_context") {
public_configs = [ ":public" ]
public = [ "public/pw_rpc/nanopb_test_method_context.h" ]
public_deps = [
- ":service_method_traits",
- "..:server",
+ ":method",
dir_pw_assert,
dir_pw_containers,
]
@@ -110,7 +100,6 @@
":echo_service_test",
":nanopb_method_test",
":nanopb_method_union_test",
- ":nanopb_service_method_traits_test",
]
}
@@ -170,15 +159,3 @@
sources = [ "echo_service_test.cc" ]
enable_if = dir_pw_third_party_nanopb != ""
}
-
-pw_test("nanopb_service_method_traits_test") {
- deps = [
- ":echo_service",
- ":method",
- ":service_method_traits",
- ":test_method_context",
- "..:test_protos.nanopb_rpc",
- ]
- sources = [ "nanopb_service_method_traits_test.cc" ]
- enable_if = dir_pw_third_party_nanopb != ""
-}
diff --git a/pw_rpc/nanopb/codegen_test.cc b/pw_rpc/nanopb/codegen_test.cc
index 7c7667e..c43ccef 100644
--- a/pw_rpc/nanopb/codegen_test.cc
+++ b/pw_rpc/nanopb/codegen_test.cc
@@ -31,9 +31,10 @@
return static_cast<Status::Code>(request.status_code);
}
- void TestStreamRpc(ServerContext&,
- const pw_rpc_test_TestRequest& request,
- ServerWriter<pw_rpc_test_TestStreamResponse>& writer) {
+ static void TestStreamRpc(
+ ServerContext&,
+ const pw_rpc_test_TestRequest& request,
+ ServerWriter<pw_rpc_test_TestStreamResponse>& writer) {
for (int i = 0; i < request.integer; ++i) {
writer.Write({.chunk = {}, .number = static_cast<uint32_t>(i)});
}
diff --git a/pw_rpc/nanopb/nanopb_method_test.cc b/pw_rpc/nanopb/nanopb_method_test.cc
index fa0072c..6c0b736 100644
--- a/pw_rpc/nanopb/nanopb_method_test.cc
+++ b/pw_rpc/nanopb/nanopb_method_test.cc
@@ -32,7 +32,7 @@
pw_rpc_test_TestRequest last_request;
ServerWriter<pw_rpc_test_TestResponse> last_writer;
-Status AddFive(ServerCall&,
+Status AddFive(ServerContext&,
const pw_rpc_test_TestRequest& request,
pw_rpc_test_TestResponse& response) {
last_request = request;
@@ -40,11 +40,11 @@
return Status::Unauthenticated();
}
-Status DoNothing(ServerCall&, const pw_rpc_test_Empty&, pw_rpc_test_Empty&) {
+Status DoNothing(ServerContext&, const pw_rpc_test_Empty&, pw_rpc_test_Empty&) {
return Status::Unknown();
}
-void StartStream(ServerCall&,
+void StartStream(ServerContext&,
const pw_rpc_test_TestRequest& request,
ServerWriter<pw_rpc_test_TestResponse>& writer) {
last_request = request;
diff --git a/pw_rpc/nanopb/nanopb_method_union_test.cc b/pw_rpc/nanopb/nanopb_method_union_test.cc
index ccd8493..5df140d 100644
--- a/pw_rpc/nanopb/nanopb_method_union_test.cc
+++ b/pw_rpc/nanopb/nanopb_method_union_test.cc
@@ -32,13 +32,15 @@
constexpr FakeGeneratedService(uint32_t id) : Service(id, kMethods) {}
static constexpr std::array<NanopbMethodUnion, 4> kMethods = {
- GetNanopbOrRawMethodFor<&Implementation::DoNothing>(
+ GetNanopbOrRawMethodFor<&Implementation::DoNothing, MethodType::kUnary>(
10u, pw_rpc_test_Empty_fields, pw_rpc_test_Empty_fields),
- GetNanopbOrRawMethodFor<&Implementation::RawStream>(
+ GetNanopbOrRawMethodFor<&Implementation::RawStream,
+ MethodType::kServerStreaming>(
11u, pw_rpc_test_TestRequest_fields, pw_rpc_test_TestResponse_fields),
- GetNanopbOrRawMethodFor<&Implementation::AddFive>(
+ GetNanopbOrRawMethodFor<&Implementation::AddFive, MethodType::kUnary>(
12u, pw_rpc_test_TestRequest_fields, pw_rpc_test_TestResponse_fields),
- GetNanopbOrRawMethodFor<&Implementation::StartStream>(
+ GetNanopbOrRawMethodFor<&Implementation::StartStream,
+ MethodType::kServerStreaming>(
13u, pw_rpc_test_TestRequest_fields, pw_rpc_test_TestResponse_fields),
};
};
diff --git a/pw_rpc/nanopb/nanopb_service_method_traits_test.cc b/pw_rpc/nanopb/nanopb_service_method_traits_test.cc
deleted file mode 100644
index 8043e12..0000000
--- a/pw_rpc/nanopb/nanopb_service_method_traits_test.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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/internal/nanopb_service_method_traits.h"
-
-#include <type_traits>
-
-#include "pw_rpc/echo_service_nanopb.h"
-#include "pw_rpc/internal/hash.h"
-
-namespace pw::rpc::internal {
-namespace {
-
-static_assert(
- std::is_same_v<decltype(NanopbServiceMethodTraits<&EchoService::Echo,
- Hash("Echo")>::method()),
- const NanopbMethod&>);
-
-} // namespace
-} // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method.h b/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method.h
index 1808b34..33cbaf7 100644
--- a/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method.h
+++ b/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method.h
@@ -53,22 +53,14 @@
namespace internal {
+class NanopbMethod;
class Packet;
-// Templated false value for use in static_assert(false) statements.
-template <typename...>
-constexpr std::false_type kFalseValue{};
-
-// Extracts the request and response proto types from a method.
-template <typename Method>
-struct RpcTraits {
- static_assert(kFalseValue<Method>,
- "The selected function is not an RPC service method");
-};
-
-// Specialization for unary RPCs.
+// MethodTraits specialization for a static unary method.
template <typename RequestType, typename ResponseType>
-struct RpcTraits<Status (*)(ServerCall&, const RequestType&, ResponseType&)> {
+struct MethodTraits<Status (*)(
+ ServerContext&, const RequestType&, ResponseType&)> {
+ using Implementation = NanopbMethod;
using Request = RequestType;
using Response = ResponseType;
@@ -77,10 +69,20 @@
static constexpr bool kClientStreaming = false;
};
-// Specialization for server streaming RPCs.
+// MethodTraits specialization for a unary method.
+template <typename T, typename RequestType, typename ResponseType>
+struct MethodTraits<Status (T::*)(
+ ServerContext&, const RequestType&, ResponseType&)>
+ : public MethodTraits<Status (*)(
+ ServerContext&, const RequestType&, ResponseType&)> {
+ using Service = T;
+};
+
+// MethodTraits specialization for a static server streaming method.
template <typename RequestType, typename ResponseType>
-struct RpcTraits<void (*)(
- ServerCall&, const RequestType&, ServerWriter<ResponseType>&)> {
+struct MethodTraits<void (*)(
+ ServerContext&, const RequestType&, ServerWriter<ResponseType>&)> {
+ using Implementation = NanopbMethod;
using Request = RequestType;
using Response = ResponseType;
@@ -89,29 +91,20 @@
static constexpr bool kClientStreaming = false;
};
-// Member function specialization for unary RPCs.
+// MethodTraits specialization for a server streaming method.
template <typename T, typename RequestType, typename ResponseType>
-struct RpcTraits<Status (T::*)(
- ServerContext&, const RequestType&, ResponseType&)>
- : public RpcTraits<Status (*)(
- ServerCall&, const RequestType&, ResponseType&)> {
- using Service = T;
-};
-
-// Member function specialization for server streaming RPCs.
-template <typename T, typename RequestType, typename ResponseType>
-struct RpcTraits<void (T::*)(
+struct MethodTraits<void (T::*)(
ServerContext&, const RequestType&, ServerWriter<ResponseType>&)>
- : public RpcTraits<void (*)(
- ServerCall&, const RequestType&, ServerWriter<ResponseType>&)> {
+ : public MethodTraits<void (*)(
+ ServerContext&, const RequestType&, ServerWriter<ResponseType>&)> {
using Service = T;
};
template <auto method>
-using Request = typename RpcTraits<decltype(method)>::Request;
+using Request = typename MethodTraits<decltype(method)>::Request;
template <auto method>
-using Response = typename RpcTraits<decltype(method)>::Response;
+using Response = typename MethodTraits<decltype(method)>::Response;
// The NanopbMethod class invokes user-defined service methods. When a
// pw::rpc::Server receives an RPC request packet, it looks up the matching
@@ -125,6 +118,11 @@
// structs.
class NanopbMethod : public Method {
public:
+ template <auto method>
+ static constexpr bool matches() {
+ return std::is_same_v<MethodImplementation<method>, NanopbMethod>;
+ }
+
// Creates a NanopbMethod for a unary RPC.
template <auto method>
static constexpr NanopbMethod Unary(uint32_t id,
@@ -136,18 +134,19 @@
//
// In optimized builds, the compiler inlines the user-defined function into
// this wrapper, elminating any overhead.
- return NanopbMethod(
- id,
- UnaryInvoker<AllocateSpaceFor<Request<method>>(),
- AllocateSpaceFor<Response<method>>()>,
- Function{.unary =
- [](ServerCall& call, const void* req, void* resp) {
- return method(call,
- *static_cast<const Request<method>*>(req),
- *static_cast<Response<method>*>(resp));
- }},
- request,
- response);
+ constexpr UnaryFunction wrapper =
+ [](ServerCall& call, const void* req, void* resp) {
+ return CallMethodImplFunction<method>(
+ call,
+ *static_cast<const Request<method>*>(req),
+ *static_cast<Response<method>*>(resp));
+ };
+ return NanopbMethod(id,
+ UnaryInvoker<AllocateSpaceFor<Request<method>>(),
+ AllocateSpaceFor<Response<method>>()>,
+ Function{.unary = wrapper},
+ request,
+ response);
}
// Creates a NanopbMethod for a server-streaming RPC.
@@ -160,22 +159,26 @@
// struct as void* and a BaseServerWriter instead of the templated
// ServerWriter class. This wrapper is stored generically in the Function
// union, defined below.
+ constexpr ServerStreamingFunction wrapper =
+ [](ServerCall& call, const void* req, BaseServerWriter& writer) {
+ return CallMethodImplFunction<method>(
+ call,
+ *static_cast<const Request<method>*>(req),
+ static_cast<ServerWriter<Response<method>>&>(writer));
+ };
return NanopbMethod(
id,
ServerStreamingInvoker<AllocateSpaceFor<Request<method>>()>,
- Function{.server_streaming =
- [](ServerCall& call,
- const void* req,
- BaseServerWriter& writer) {
- method(call,
- *static_cast<const Request<method>*>(req),
- static_cast<ServerWriter<Response<method>>&>(
- writer));
- }},
+ Function{.server_streaming = wrapper},
request,
response);
}
+ // Represents an invalid method. Used to reduce error message verbosity.
+ static constexpr NanopbMethod Invalid() {
+ return {0, InvalidInvoker, {}, nullptr, nullptr};
+ }
+
// Encodes a response protobuf with Nanopb to the provided buffer.
StatusWithSize EncodeResponse(const void* proto_struct,
std::span<std::byte> buffer) const {
diff --git a/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method_union.h b/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method_union.h
index eef6e47..d73813b 100644
--- a/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method_union.h
+++ b/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method_union.h
@@ -40,86 +40,20 @@
} impl_;
};
-// Specialization for a nanopb unary method.
-template <typename T, typename RequestType, typename ResponseType>
-struct MethodTraits<Status (T::*)(
- ServerContext&, const RequestType&, ResponseType&)> {
- static constexpr MethodType kType = MethodType::kUnary;
-
- using Service = T;
- using Implementation = NanopbMethod;
- using Request = RequestType;
- using Response = ResponseType;
-};
-
-// Specialization for a nanopb server streaming method.
-template <typename T, typename RequestType, typename ResponseType>
-struct MethodTraits<void (T::*)(
- ServerContext&, const RequestType&, ServerWriter<ResponseType>&)> {
- static constexpr MethodType kType = MethodType::kServerStreaming;
-
- using Service = T;
- using Implementation = NanopbMethod;
- using Request = RequestType;
- using Response = ResponseType;
-};
-
-template <auto method>
-constexpr bool kIsNanopb =
- std::is_same_v<MethodImplementation<method>, NanopbMethod>;
-
-// Deduces the type of an implemented nanopb service method from its signature,
-// and returns the appropriate Method object to invoke it.
-template <auto method>
-constexpr NanopbMethod GetNanopbMethodFor(
- uint32_t id,
- NanopbMessageDescriptor request_fields,
- NanopbMessageDescriptor response_fields) {
- static_assert(
- kIsNanopb<method>,
- "GetNanopbMethodFor should only be called on nanopb RPC methods");
-
- using Traits = MethodTraits<decltype(method)>;
- using ServiceImpl = typename Traits::Service;
-
- if constexpr (Traits::kType == MethodType::kUnary) {
- constexpr auto invoker = +[](ServerCall& call,
- const typename Traits::Request& request,
- typename Traits::Response& response) {
- return (static_cast<ServiceImpl&>(call.service()).*method)(
- call.context(), request, response);
- };
- return NanopbMethod::Unary<invoker>(id, request_fields, response_fields);
- }
-
- if constexpr (Traits::kType == MethodType::kServerStreaming) {
- constexpr auto invoker =
- +[](ServerCall& call,
- const typename Traits::Request& request,
- ServerWriter<typename Traits::Response>& writer) {
- (static_cast<ServiceImpl&>(call.service()).*method)(
- call.context(), request, writer);
- };
- return NanopbMethod::ServerStreaming<invoker>(
- id, request_fields, response_fields);
- }
-
- constexpr auto fake_invoker =
- +[](ServerCall&, const int&, ServerWriter<int>&) {};
- return NanopbMethod::ServerStreaming<fake_invoker>(0, nullptr, nullptr);
-}
-
-// Returns either a raw or nanopb method object, depending on an implemented
+// Returns either a raw or nanopb method object, depending on the implemented
// function's signature.
-template <auto method>
+template <auto method, MethodType type>
constexpr auto GetNanopbOrRawMethodFor(
uint32_t id,
[[maybe_unused]] NanopbMessageDescriptor request_fields,
[[maybe_unused]] NanopbMessageDescriptor response_fields) {
- if constexpr (kIsRaw<method>) {
- return GetRawMethodFor<method>(id);
+ if constexpr (RawMethod::matches<method>()) {
+ return GetMethodFor<method, RawMethod, type>(id);
+ } else if constexpr (NanopbMethod::matches<method>()) {
+ return GetMethodFor<method, NanopbMethod, type>(
+ id, request_fields, response_fields);
} else {
- return GetNanopbMethodFor<method>(id, request_fields, response_fields);
+ return InvalidMethod<method, type, RawMethod>(id);
}
};
diff --git a/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_service_method_traits.h b/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_service_method_traits.h
deleted file mode 100644
index 866ceef..0000000
--- a/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_service_method_traits.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// 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_rpc/internal/nanopb_method_union.h"
-#include "pw_rpc/internal/service_method_traits.h"
-
-namespace pw::rpc::internal {
-
-template <auto impl_method, uint32_t method_id>
-using NanopbServiceMethodTraits =
- ServiceMethodTraits<&MethodBaseService<impl_method>::NanopbMethodFor,
- impl_method,
- method_id>;
-
-} // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb_test_method_context.h b/pw_rpc/nanopb/public/pw_rpc/nanopb_test_method_context.h
index 7909429..0ee76fe 100644
--- a/pw_rpc/nanopb/public/pw_rpc/nanopb_test_method_context.h
+++ b/pw_rpc/nanopb/public/pw_rpc/nanopb_test_method_context.h
@@ -22,7 +22,6 @@
#include "pw_rpc/channel.h"
#include "pw_rpc/internal/hash.h"
#include "pw_rpc/internal/nanopb_method.h"
-#include "pw_rpc/internal/nanopb_service_method_traits.h"
#include "pw_rpc/internal/packet.h"
#include "pw_rpc/internal/server.h"
@@ -71,12 +70,13 @@
// PW_NANOPB_TEST_METHOD_CONTEXT(MyService, BestMethod, 3, 256) context;
// ASSERT_EQ(3u, context.responses().max_size());
//
-
#define PW_NANOPB_TEST_METHOD_CONTEXT(service, method, ...) \
- ::pw::rpc::NanopbTestMethodContext<&service::method, \
+ ::pw::rpc::NanopbTestMethodContext<service, \
+ &service::method, \
::pw::rpc::internal::Hash(#method), \
##__VA_ARGS__>
-template <auto method,
+template <typename Service,
+ auto method,
uint32_t method_id,
size_t max_responses = 4,
size_t output_size_bytes = 128>
@@ -121,8 +121,18 @@
Status last_status_;
};
+template <typename Service, uint32_t method_id>
+constexpr const internal::NanopbMethod& GetNanopbMethod() {
+ constexpr const internal::NanopbMethod* nanopb_method =
+ GeneratedService<Service>::NanopbMethodFor(method_id);
+ static_assert(nanopb_method != nullptr,
+ "The selected function is not an RPC service method");
+ return *nanopb_method;
+}
+
// Collects everything needed to invoke a particular RPC.
-template <auto method,
+template <typename Service,
+ auto method,
uint32_t method_id,
size_t max_responses,
size_t output_size>
@@ -132,22 +142,20 @@
template <typename... Args>
InvocationContext(Args&&... args)
- : output(NanopbServiceMethodTraits<method, method_id>::method(),
- responses,
- buffer),
+ : output(GetNanopbMethod<Service, method_id>(), responses, buffer),
channel(Channel::Create<123>(&output)),
server(std::span(&channel, 1)),
service(std::forward<Args>(args)...),
call(static_cast<internal::Server&>(server),
static_cast<internal::Channel&>(channel),
service,
- NanopbServiceMethodTraits<method, method_id>::method()) {}
+ GetNanopbMethod<Service, method_id>()) {}
MessageOutput<Response> output;
rpc::Channel channel;
rpc::Server server;
- typename NanopbServiceMethodTraits<method, method_id>::Service service;
+ Service service;
Vector<Response, max_responses> responses;
std::array<std::byte, output_size> buffer = {};
@@ -156,10 +164,10 @@
// Method invocation context for a unary RPC. Returns the status in call() and
// provides the response through the response() method.
-template <auto method, uint32_t method_id, size_t output_size>
+template <typename Service, auto method, uint32_t method_id, size_t output_size>
class UnaryContext {
private:
- InvocationContext<method, method_id, 1, output_size> ctx_;
+ InvocationContext<Service, method, method_id, 1, output_size> ctx_;
public:
using Request = typename decltype(ctx_)::Request;
@@ -173,8 +181,8 @@
ctx_.output.clear();
ctx_.responses.emplace_back();
ctx_.responses.back() = {};
- return (ctx_.service.*method)(
- ctx_.call.context(), request, ctx_.responses.back());
+ return CallMethodImplFunction<method>(
+ ctx_.call, request, ctx_.responses.back());
}
// Gives access to the RPC's response.
@@ -185,13 +193,15 @@
};
// Method invocation context for a server streaming RPC.
-template <auto method,
+template <typename Service,
+ auto method,
uint32_t method_id,
size_t max_responses,
size_t output_size>
class ServerStreamingContext {
private:
- InvocationContext<method, method_id, max_responses, output_size> ctx_;
+ InvocationContext<Service, method, method_id, max_responses, output_size>
+ ctx_;
public:
using Request = typename decltype(ctx_)::Request;
@@ -204,8 +214,8 @@
void call(const Request& request) {
ctx_.output.clear();
internal::BaseServerWriter server_writer(ctx_.call);
- return (ctx_.service.*method)(
- ctx_.call.context(),
+ return CallMethodImplFunction<method>(
+ ctx_.call,
request,
static_cast<ServerWriter<Response>&>(server_writer));
}
@@ -239,11 +249,19 @@
// Alias to select the type of the context object to use based on which type of
// RPC it is for.
-template <auto method, uint32_t method_id, size_t responses, size_t output_size>
+template <typename Service,
+ auto method,
+ uint32_t method_id,
+ size_t responses,
+ size_t output_size>
using Context = std::tuple_element_t<
- static_cast<size_t>(internal::RpcTraits<decltype(method)>::kType),
- std::tuple<UnaryContext<method, method_id, output_size>,
- ServerStreamingContext<method, method_id, responses, output_size>
+ static_cast<size_t>(internal::MethodTraits<decltype(method)>::kType),
+ std::tuple<UnaryContext<Service, method, method_id, output_size>,
+ ServerStreamingContext<Service,
+ method,
+ method_id,
+ responses,
+ output_size>
// TODO(hepler): Support client and bidi streaming
>>;
@@ -290,20 +308,27 @@
} // namespace internal::test::nanopb
-template <auto method,
+template <typename Service,
+ auto method,
uint32_t method_id,
size_t max_responses,
size_t output_size_bytes>
class NanopbTestMethodContext
- : public internal::test::nanopb::
- Context<method, method_id, max_responses, output_size_bytes> {
+ : public internal::test::nanopb::Context<Service,
+ method,
+ method_id,
+ max_responses,
+ output_size_bytes> {
public:
// Forwards constructor arguments to the service class.
template <typename... ServiceArgs>
NanopbTestMethodContext(ServiceArgs&&... service_args)
- : internal::test::nanopb::
- Context<method, method_id, max_responses, output_size_bytes>(
- std::forward<ServiceArgs>(service_args)...) {}
+ : internal::test::nanopb::Context<Service,
+ method,
+ method_id,
+ max_responses,
+ output_size_bytes>(
+ std::forward<ServiceArgs>(service_args)...) {}
};
} // namespace pw::rpc
diff --git a/pw_rpc/public/pw_rpc/internal/method.h b/pw_rpc/public/pw_rpc/internal/method.h
index cd9155b..aae02df 100644
--- a/pw_rpc/public/pw_rpc/internal/method.h
+++ b/pw_rpc/public/pw_rpc/internal/method.h
@@ -15,6 +15,7 @@
#include <cstddef>
#include <cstdint>
+#include <utility>
#include "pw_rpc/internal/call.h"
@@ -22,7 +23,36 @@
class Packet;
-// RPC server implementations provide a class that dervies from Method.
+// Each supported protobuf implementation provides a class that derives from
+// Method. The implementation classes provide the following public interface:
+/*
+class MethodImpl : public Method {
+ // True if the provided function signature is valid for this method impl.
+ template <auto method>
+ static constexpr bool matches();
+
+ // Creates a unary method instance.
+ template <auto method>
+ static constexpr MethodImpl Unary(uint32_t id, [optional args]);
+
+ // Creates a server streaming method instance.
+ template <auto method>
+ static constexpr MethodImpl ServerStreaming(uint32_t id, [optional args]);
+
+ // Creates a client streaming method instance.
+ static constexpr MethodImpl ClientStreaming(uint32_t id, [optional args]);
+
+ // Creates a bidirectional streaming method instance.
+ static constexpr MethodImpl BidirectionalStreaming(uint32_t id,
+ [optional args]);
+
+ // Creates a method instance for when the method implementation function has
+ // an incorrect signature. Having this helps reduce error message verbosity.
+ static constexpr MethodImpl Invalid();
+};
+*/
+// Method implementations must pass a test that uses the MethodImplTester class
+// in pw_rpc_private/method_impl_tester.h.
class Method {
public:
constexpr uint32_t id() const { return id_; }
@@ -38,6 +68,10 @@
protected:
using Invoker = void (&)(const Method&, ServerCall&, const Packet&);
+ static constexpr void InvalidInvoker(const Method&,
+ ServerCall&,
+ const Packet&) {}
+
constexpr Method(uint32_t id, Invoker invoker) : id_(id), invoker_(invoker) {}
private:
@@ -45,4 +79,50 @@
Invoker invoker_;
};
+// Traits struct that determines the type of an RPC service method from its
+// signature. Derived Methods should provide specializations for their method
+// types.
+template <typename Method>
+struct MethodTraits {
+ // Specializations must set Implementation as an alias for their method
+ // implementation class.
+ using Implementation = Method;
+
+ // Specializations must set kType to the MethodType.
+ // static constexpr MethodType kType = (method type);
+
+ // Specializations for member function types must set Service as an alias to
+ // the implemented service class.
+ using Service = rpc::Service;
+};
+
+template <auto method>
+using MethodImplementation =
+ typename MethodTraits<decltype(method)>::Implementation;
+
+// Function that calls a user-defined method implementation function from a
+// ServerCall object.
+template <auto method, typename... Args>
+constexpr auto CallMethodImplFunction(ServerCall& call, Args&&... args) {
+ // If the method impl is a member function, deduce the type of the
+ // user-defined service from it, then call the method on the service.
+ if constexpr (std::is_member_function_pointer_v<decltype(method)>) {
+ return (static_cast<typename MethodTraits<decltype(method)>::Service&>(
+ call.service()).*
+ method)(call.context(), std::forward<Args>(args)...);
+ } else {
+ return method(call.context(), std::forward<Args>(args)...);
+ }
+}
+
+// Identifies a base class from a member function it defines. This should be
+// used with decltype to retrieve the base service class.
+template <typename T, typename U>
+T BaseFromMember(U T::*);
+
+// The base generated service of an RPC service class.
+template <typename Service>
+using GeneratedService =
+ decltype(BaseFromMember(&Service::_PwRpcInternalGeneratedBase));
+
} // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/method_union.h b/pw_rpc/public/pw_rpc/internal/method_union.h
index 9d491af..154667d 100644
--- a/pw_rpc/public/pw_rpc/internal/method_union.h
+++ b/pw_rpc/public/pw_rpc/internal/method_union.h
@@ -14,8 +14,10 @@
#pragma once
#include <type_traits>
+#include <utility>
#include "pw_rpc/internal/method.h"
+#include "pw_rpc/internal/method_type.h"
namespace pw::rpc::internal {
@@ -27,44 +29,6 @@
constexpr const Method& method() const;
};
-// Templated false value for use in static_assert(false) statements.
-template <typename...>
-constexpr std::false_type kFalse{};
-
-// Traits struct that determines the type of an RPC service method from its
-// signature. Derived MethodUnions should provide specializations for their
-// method types.
-template <typename Method>
-struct MethodTraits {
- static_assert(kFalse<Method>,
- "The selected function is not an RPC service method");
-
- // Specializations must set Implementation as an alias for their method
- // implementation class.
- using Implementation = Method;
-
- // Specializations must set Service as an alias to the implemented service
- // class.
- using Service = rpc::Service;
-};
-
-template <auto method>
-using MethodImplementation =
- typename MethodTraits<decltype(method)>::Implementation;
-
-template <auto method>
-using MethodService = typename MethodTraits<decltype(method)>::Service;
-
-// Identifies a base class from a member function it defines. This should be
-// used with decltype to retrieve the base class.
-template <typename T, typename U>
-T BaseFromMember(U T::*);
-
-// The base generated service of an implemented RPC method.
-template <auto method>
-using MethodBaseService = decltype(
- BaseFromMember(&MethodService<method>::_PwRpcInternalGeneratedBase));
-
class CoreMethodUnion : public MethodUnion {
public:
constexpr const Method& method() const { return impl_.method; }
@@ -84,4 +48,84 @@
return static_cast<const CoreMethodUnion*>(this)->method();
}
+// Templated false value for use in static_assert(false) statements.
+template <typename...>
+constexpr std::false_type kCheckMethodSignature{};
+
+// In static_assert messages, use newlines in GCC since it displays them
+// correctly. Clang displays \n, which is not helpful.
+#ifdef __clang__
+#define _PW_RPC_FORMAT_ERROR_MESSAGE(msg, signature) msg " " signature
+#else
+#define _PW_RPC_FORMAT_ERROR_MESSAGE(msg, signature) \
+ "\n" msg "\n\n " signature "\n"
+#endif // __clang__
+
+#define _PW_RPC_FUNCTION_ERROR(type, return_type, args) \
+ _PW_RPC_FORMAT_ERROR_MESSAGE( \
+ "This RPC is a " type \
+ " RPC, but its function signature is not correct. The function " \
+ "signature is determined by the protobuf library in use, but " type \
+ " RPC implementations generally take the form:", \
+ return_type " MethodName(ServerContext&, " args ")")
+
+// This function is called if an RPC method implementation's signature is not
+// correct. It triggers a static_assert with an error message tailored to the
+// expected RPC type.
+template <auto method,
+ MethodType expected,
+ typename InvalidImpl = MethodImplementation<method>>
+constexpr auto InvalidMethod(uint32_t) {
+ if constexpr (expected == MethodType::kUnary) {
+ static_assert(
+ kCheckMethodSignature<decltype(method)>,
+ _PW_RPC_FUNCTION_ERROR("unary", "Status", "Request, Response"));
+ } else if constexpr (expected == MethodType::kServerStreaming) {
+ static_assert(
+ kCheckMethodSignature<decltype(method)>,
+ _PW_RPC_FUNCTION_ERROR(
+ "server streaming", "void", "Request, ServerWriter<Response>&"));
+ } else if constexpr (expected == MethodType::kClientStreaming) {
+ static_assert(
+ kCheckMethodSignature<decltype(method)>,
+ _PW_RPC_FUNCTION_ERROR(
+ "client streaming", "Status", "ServerReader<Request>&, Response"));
+ } else if constexpr (expected == MethodType::kBidirectionalStreaming) {
+ static_assert(kCheckMethodSignature<decltype(method)>,
+ _PW_RPC_FUNCTION_ERROR(
+ "bidirectional streaming",
+ "void",
+ "ServerReader<Request>&, ServerWriter<Response>&"));
+ } else {
+ static_assert(kCheckMethodSignature<decltype(method)>,
+ "Unsupported MethodType");
+ }
+ return InvalidImpl::Invalid();
+}
+
+#undef _PW_RPC_FORMAT_ERROR_MESSAGE
+#undef _PW_RPC_FUNCTION_ERROR
+
+// This function checks the type of the method and calls the appropriate
+// function to create the method instance.
+template <auto method, typename MethodImpl, MethodType type, typename... Args>
+constexpr auto GetMethodFor(uint32_t id, Args&&... args) {
+ if constexpr (MethodTraits<decltype(method)>::kType != type) {
+ return InvalidMethod<method, type>(id);
+ } else if constexpr (type == MethodType::kUnary) {
+ return MethodImpl::template Unary<method>(id, std::forward<Args>(args)...);
+ } else if constexpr (type == MethodType::kServerStreaming) {
+ return MethodImpl::template ServerStreaming<method>(
+ id, std::forward<Args>(args)...);
+ } else if constexpr (type == MethodType::kClientStreaming) {
+ return MethodImpl::template ClientStreaming<method>(
+ id, std::forward<Args>(args)...);
+ } else if constexpr (type == MethodType::kBidirectionalStreaming) {
+ return MethodImpl::template BidirectionalStreaming<method>(
+ id, std::forward<Args>(args)...);
+ } else {
+ static_assert(kCheckMethodSignature<MethodImpl>, "Invalid MethodType");
+ }
+}
+
} // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/service_method_traits.h b/pw_rpc/public/pw_rpc/internal/service_method_traits.h
deleted file mode 100644
index 0e26b8d..0000000
--- a/pw_rpc/public/pw_rpc/internal/service_method_traits.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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_rpc/internal/method_union.h"
-
-namespace pw::rpc::internal {
-
-// Gets information about a service and method at compile-time. Uses a pointer
-// to a member function of the service implementation to identify the service
-// class, generated service class, and Method object.
-template <auto lookup_function, auto impl_method, uint32_t method_id>
-class ServiceMethodTraits {
- public:
- ServiceMethodTraits() = delete;
-
- // Type of the service implementation derived class.
- using Service = MethodService<impl_method>;
-
- using MethodImpl = MethodImplementation<impl_method>;
-
- // Reference to the Method object corresponding to this method.
- static constexpr const MethodImpl& method() {
- return *lookup_function(method_id);
- }
-
- static_assert(lookup_function(method_id) != nullptr,
- "The selected function is not an RPC service method");
-};
-
-} // namespace pw::rpc::internal
diff --git a/pw_rpc/py/pw_rpc/codegen_nanopb.py b/pw_rpc/py/pw_rpc/codegen_nanopb.py
index 9e5ca78..0e33498 100644
--- a/pw_rpc/py/pw_rpc/codegen_nanopb.py
+++ b/pw_rpc/py/pw_rpc/codegen_nanopb.py
@@ -53,7 +53,8 @@
impl_method = f'&Implementation::{method.name()}'
output.write_line(
- f'{RPC_NAMESPACE}::internal::GetNanopbOrRawMethodFor<{impl_method}>(')
+ f'{RPC_NAMESPACE}::internal::GetNanopbOrRawMethodFor<{impl_method}, '
+ f'{method.type().cc_enum()}>(')
with output.indent(4):
output.write_line(f'0x{method_id:08x}, // Hash of "{method.name()}"')
output.write_line(f'{req_fields},')
diff --git a/pw_rpc/py/pw_rpc/codegen_raw.py b/pw_rpc/py/pw_rpc/codegen_raw.py
index 6babe1c..6a271cf 100644
--- a/pw_rpc/py/pw_rpc/codegen_raw.py
+++ b/pw_rpc/py/pw_rpc/codegen_raw.py
@@ -44,7 +44,8 @@
impl_method = f'&Implementation::{method.name()}'
output.write_line(
- f'{RPC_NAMESPACE}::internal::GetRawMethodFor<{impl_method}>(')
+ f'{RPC_NAMESPACE}::internal::GetRawMethodFor<{impl_method}, '
+ f'{method.type().cc_enum()}>(')
output.write_line(f' 0x{method_id:08x}), // Hash of "{method.name()}"')
diff --git a/pw_rpc/raw/BUILD b/pw_rpc/raw/BUILD
index 229718e..193ef17 100644
--- a/pw_rpc/raw/BUILD
+++ b/pw_rpc/raw/BUILD
@@ -33,7 +33,7 @@
deps = [
"//pw_bytes",
"//pw_rpc:server",
- ]
+ ],
)
pw_cc_library(
@@ -43,18 +43,7 @@
],
deps = [
":method",
- ]
-)
-
-pw_cc_library(
- name = "service_method_traits",
- hdrs = [
- "public/pw_rpc/internal/raw_service_method_traits.h",
],
- deps = [
- ":method_union",
- "//pw_rpc:service_method_traits",
- ]
)
pw_cc_library(
@@ -66,7 +55,7 @@
":method_union",
"//pw_assert",
"//pw_containers",
- ]
+ ],
)
pw_cc_test(
diff --git a/pw_rpc/raw/BUILD.gn b/pw_rpc/raw/BUILD.gn
index 89d0492..e39214e 100644
--- a/pw_rpc/raw/BUILD.gn
+++ b/pw_rpc/raw/BUILD.gn
@@ -40,20 +40,11 @@
public_deps = [ ":method" ]
}
-pw_source_set("service_method_traits") {
- public_configs = [ ":public" ]
- public = [ "public/pw_rpc/internal/raw_service_method_traits.h" ]
- public_deps = [
- ":method_union",
- "..:service_method_traits",
- ]
-}
-
pw_source_set("test_method_context") {
public_configs = [ ":public" ]
public = [ "public/pw_rpc/raw_test_method_context.h" ]
public_deps = [
- ":service_method_traits",
+ ":method",
dir_pw_assert,
dir_pw_containers,
]
@@ -79,8 +70,10 @@
pw_test("raw_method_test") {
deps = [
+ ":method",
":method_union",
"..:test_protos.pwpb",
+ "..:test_protos.raw_rpc",
"..:test_utils",
dir_pw_protobuf,
]
diff --git a/pw_rpc/raw/codegen_test.cc b/pw_rpc/raw/codegen_test.cc
index 6194031..722c5de 100644
--- a/pw_rpc/raw/codegen_test.cc
+++ b/pw_rpc/raw/codegen_test.cc
@@ -24,9 +24,9 @@
class TestService final : public generated::TestService<TestService> {
public:
- StatusWithSize TestRpc(ServerContext&,
- ConstByteSpan request,
- ByteSpan response) {
+ static StatusWithSize TestRpc(ServerContext&,
+ ConstByteSpan request,
+ ByteSpan response) {
int64_t integer;
Status status;
DecodeRequest(request, integer, status);
@@ -57,7 +57,9 @@
}
private:
- void DecodeRequest(ConstByteSpan request, int64_t& integer, Status& status) {
+ static void DecodeRequest(ConstByteSpan request,
+ int64_t& integer,
+ Status& status) {
protobuf::Decoder decoder(request);
while (decoder.Next().ok()) {
diff --git a/pw_rpc/raw/public/pw_rpc/internal/raw_method.h b/pw_rpc/raw/public/pw_rpc/internal/raw_method.h
index a1e3b66..6d2e898 100644
--- a/pw_rpc/raw/public/pw_rpc/internal/raw_method.h
+++ b/pw_rpc/raw/public/pw_rpc/internal/raw_method.h
@@ -49,26 +49,33 @@
class RawMethod : public Method {
public:
template <auto method>
- constexpr static RawMethod Unary(uint32_t id) {
- return RawMethod(
- id,
- UnaryInvoker,
- {.unary = [](ServerCall& call, ConstByteSpan req, ByteSpan res) {
- return method(call, req, res);
- }});
+ static constexpr bool matches() {
+ return std::is_same_v<MethodImplementation<method>, RawMethod>;
}
template <auto method>
- constexpr static RawMethod ServerStreaming(uint32_t id) {
- return RawMethod(id,
- ServerStreamingInvoker,
- Function{.server_streaming = [](ServerCall& call,
- ConstByteSpan req,
- BaseServerWriter& writer) {
- method(call, req, static_cast<RawServerWriter&>(writer));
- }});
+ static constexpr RawMethod Unary(uint32_t id) {
+ constexpr UnaryFunction wrapper =
+ [](ServerCall& call, ConstByteSpan req, ByteSpan res) {
+ return CallMethodImplFunction<method>(call, req, res);
+ };
+ return RawMethod(id, UnaryInvoker, Function{.unary = wrapper});
}
+ template <auto method>
+ static constexpr RawMethod ServerStreaming(uint32_t id) {
+ constexpr ServerStreamingFunction wrapper =
+ [](ServerCall& call, ConstByteSpan request, BaseServerWriter& writer) {
+ CallMethodImplFunction<method>(
+ call, request, static_cast<RawServerWriter&>(writer));
+ };
+ return RawMethod(
+ id, ServerStreamingInvoker, Function{.server_streaming = wrapper});
+ }
+
+ // Represents an invalid method. Used to reduce error message verbosity.
+ static constexpr RawMethod Invalid() { return {0, InvalidInvoker, {}}; }
+
private:
using UnaryFunction = StatusWithSize (*)(ServerCall&,
ConstByteSpan,
@@ -105,5 +112,38 @@
Function function_;
};
+// MethodTraits specialization for a static raw unary method.
+template <>
+struct MethodTraits<StatusWithSize (*)(
+ ServerContext&, ConstByteSpan, ByteSpan)> {
+ using Implementation = RawMethod;
+ static constexpr MethodType kType = MethodType::kUnary;
+};
+
+// MethodTraits specialization for a raw unary method.
+template <typename T>
+struct MethodTraits<StatusWithSize (T::*)(
+ ServerContext&, ConstByteSpan, ByteSpan)> {
+ using Implementation = RawMethod;
+ static constexpr MethodType kType = MethodType::kUnary;
+ using Service = T;
+};
+
+// MethodTraits specialization for a static raw server streaming method.
+template <>
+struct MethodTraits<void (*)(ServerContext&, ConstByteSpan, RawServerWriter&)> {
+ using Implementation = RawMethod;
+ static constexpr MethodType kType = MethodType::kServerStreaming;
+};
+
+// MethodTraits specialization for a raw server streaming method.
+template <typename T>
+struct MethodTraits<void (T::*)(
+ ServerContext&, ConstByteSpan, RawServerWriter&)> {
+ using Implementation = RawMethod;
+ static constexpr MethodType kType = MethodType::kServerStreaming;
+ using Service = T;
+};
+
} // namespace internal
} // namespace pw::rpc
diff --git a/pw_rpc/raw/public/pw_rpc/internal/raw_method_union.h b/pw_rpc/raw/public/pw_rpc/internal/raw_method_union.h
index 90267c5..7b377d7 100644
--- a/pw_rpc/raw/public/pw_rpc/internal/raw_method_union.h
+++ b/pw_rpc/raw/public/pw_rpc/internal/raw_method_union.h
@@ -36,58 +36,15 @@
} impl_;
};
-// MethodTraits specialization for a unary method.
-template <typename T>
-struct MethodTraits<StatusWithSize (T::*)(
- ServerContext&, ConstByteSpan, ByteSpan)> {
- static constexpr MethodType kType = MethodType::kUnary;
- using Service = T;
- using Implementation = RawMethod;
-};
-
-// MethodTraits specialization for a raw server streaming method.
-template <typename T>
-struct MethodTraits<void (T::*)(
- ServerContext&, ConstByteSpan, RawServerWriter&)> {
- static constexpr MethodType kType = MethodType::kServerStreaming;
- using Service = T;
- using Implementation = RawMethod;
-};
-
-template <auto method>
-constexpr bool kIsRaw = std::is_same_v<MethodImplementation<method>, RawMethod>;
-
// Deduces the type of an implemented service method from its signature, and
// returns the appropriate MethodUnion object to invoke it.
-template <auto method>
+template <auto method, MethodType type>
constexpr RawMethod GetRawMethodFor(uint32_t id) {
- static_assert(kIsRaw<method>,
- "GetRawMethodFor should only be called on raw RPC methods");
-
- using Traits = MethodTraits<decltype(method)>;
- using ServiceImpl = typename Traits::Service;
-
- if constexpr (Traits::kType == MethodType::kUnary) {
- constexpr auto invoker =
- +[](ServerCall& call, ConstByteSpan request, ByteSpan response) {
- return (static_cast<ServiceImpl&>(call.service()).*method)(
- call.context(), request, response);
- };
- return RawMethod::Unary<invoker>(id);
+ if constexpr (RawMethod::matches<method>()) {
+ return GetMethodFor<method, RawMethod, type>(id);
+ } else {
+ return InvalidMethod<method, type, RawMethod>(id);
}
-
- if constexpr (Traits::kType == MethodType::kServerStreaming) {
- constexpr auto invoker =
- +[](ServerCall& call, ConstByteSpan request, RawServerWriter& writer) {
- (static_cast<ServiceImpl&>(call.service()).*method)(
- call.context(), request, writer);
- };
- return RawMethod::ServerStreaming<invoker>(id);
- }
-
- constexpr auto fake_invoker =
- +[](ServerCall&, ConstByteSpan, RawServerWriter&) {};
- return RawMethod::ServerStreaming<fake_invoker>(0);
};
} // namespace pw::rpc::internal
diff --git a/pw_rpc/raw/public/pw_rpc/internal/raw_service_method_traits.h b/pw_rpc/raw/public/pw_rpc/internal/raw_service_method_traits.h
deleted file mode 100644
index e304fc2..0000000
--- a/pw_rpc/raw/public/pw_rpc/internal/raw_service_method_traits.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// 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_rpc/internal/raw_method_union.h"
-#include "pw_rpc/internal/service_method_traits.h"
-
-namespace pw::rpc::internal {
-
-template <auto impl_method, uint32_t method_id>
-using RawServiceMethodTraits =
- ServiceMethodTraits<&MethodBaseService<impl_method>::RawMethodFor,
- impl_method,
- method_id>;
-
-} // namespace pw::rpc::internal
diff --git a/pw_rpc/raw/public/pw_rpc/raw_test_method_context.h b/pw_rpc/raw/public/pw_rpc/raw_test_method_context.h
index 14e831e..371e0f5 100644
--- a/pw_rpc/raw/public/pw_rpc/raw_test_method_context.h
+++ b/pw_rpc/raw/public/pw_rpc/raw_test_method_context.h
@@ -21,7 +21,7 @@
#include "pw_rpc/channel.h"
#include "pw_rpc/internal/hash.h"
#include "pw_rpc/internal/packet.h"
-#include "pw_rpc/internal/raw_service_method_traits.h"
+#include "pw_rpc/internal/raw_method.h"
#include "pw_rpc/internal/server.h"
namespace pw::rpc {
@@ -73,10 +73,12 @@
// ASSERT_EQ(3u, context.responses().max_size());
//
#define PW_RAW_TEST_METHOD_CONTEXT(service, method, ...) \
- ::pw::rpc::RawTestMethodContext<&service::method, \
+ ::pw::rpc::RawTestMethodContext<service, \
+ &service::method, \
::pw::rpc::internal::Hash(#method), \
##__VA_ARGS__>
-template <auto method,
+template <typename Service,
+ auto method,
uint32_t method_id,
size_t max_responses = 4,
size_t output_size_bytes = 128>
@@ -129,8 +131,17 @@
Status last_status_;
};
+template <typename Service, uint32_t method_id>
+constexpr const internal::RawMethod& GetRawMethod() {
+ constexpr const internal::RawMethod* raw_method =
+ GeneratedService<Service>::RawMethodFor(method_id);
+ static_assert(raw_method != nullptr,
+ "The selected function is not an RPC service method");
+ return *raw_method;
+}
+
// Collects everything needed to invoke a particular RPC.
-template <auto method,
+template <typename Service,
uint32_t method_id,
size_t max_responses,
size_t output_size>
@@ -144,10 +155,9 @@
call(static_cast<internal::Server&>(server),
static_cast<internal::Channel&>(channel),
service,
- RawServiceMethodTraits<method, method_id>::method()) {}
+ GetRawMethod<Service, method_id>()) {}
using ResponseBuffer = std::array<std::byte, output_size>;
- using Service = typename RawServiceMethodTraits<method, method_id>::Service;
MessageOutput<output_size> output;
rpc::Channel channel;
@@ -161,17 +171,17 @@
// Method invocation context for a unary RPC. Returns the status in call() and
// provides the response through the response() method.
-template <auto method, uint32_t method_id, size_t output_size>
+template <typename Service, auto method, uint32_t method_id, size_t output_size>
class UnaryContext {
private:
- using Context = InvocationContext<method, method_id, 1, output_size>;
+ using Context = InvocationContext<Service, method_id, 1, output_size>;
Context ctx_;
public:
template <typename... Args>
UnaryContext(Args&&... args) : ctx_(std::forward<Args>(args)...) {}
- typename Context::Service& service() { return ctx_.service; }
+ Service& service() { return ctx_.service; }
// Invokes the RPC with the provided request. Returns RPC's StatusWithSize.
StatusWithSize call(ConstByteSpan request) {
@@ -181,7 +191,7 @@
ctx_.responses.emplace_back();
auto& response = ctx_.responses.back();
response = {ctx_.buffers.back().data(), ctx_.buffers.back().size()};
- auto sws = (ctx_.service.*method)(ctx_.call.context(), request, response);
+ auto sws = CallMethodImplFunction<method>(ctx_.call, request, response);
response = response.first(sws.size());
return sws;
}
@@ -194,29 +204,29 @@
};
// Method invocation context for a server streaming RPC.
-template <auto method,
+template <typename Service,
+ auto method,
uint32_t method_id,
size_t max_responses,
size_t output_size>
class ServerStreamingContext {
private:
using Context =
- InvocationContext<method, method_id, max_responses, output_size>;
+ InvocationContext<Service, method_id, max_responses, output_size>;
Context ctx_;
public:
template <typename... Args>
ServerStreamingContext(Args&&... args) : ctx_(std::forward<Args>(args)...) {}
- typename Context::Service& service() { return ctx_.service; }
+ Service& service() { return ctx_.service; }
// Invokes the RPC with the provided request.
void call(ConstByteSpan request) {
ctx_.output.clear();
BaseServerWriter server_writer(ctx_.call);
- return (ctx_.service.*method)(ctx_.call.context(),
- request,
- static_cast<RawServerWriter&>(server_writer));
+ return CallMethodImplFunction<method>(
+ ctx_.call, request, static_cast<RawServerWriter&>(server_writer));
}
// Returns a server writer which writes responses into the context's buffer.
@@ -248,11 +258,19 @@
// Alias to select the type of the context object to use based on which type of
// RPC it is for.
-template <auto method, uint32_t method_id, size_t responses, size_t output_size>
+template <typename Service,
+ auto method,
+ uint32_t method_id,
+ size_t responses,
+ size_t output_size>
using Context = std::tuple_element_t<
static_cast<size_t>(MethodTraits<decltype(method)>::kType),
- std::tuple<UnaryContext<method, method_id, output_size>,
- ServerStreamingContext<method, method_id, responses, output_size>
+ std::tuple<UnaryContext<Service, method, method_id, output_size>,
+ ServerStreamingContext<Service,
+ method,
+ method_id,
+ responses,
+ output_size>
// TODO(hepler): Support client and bidi streaming
>>;
@@ -294,20 +312,27 @@
} // namespace internal::test::raw
-template <auto method,
+template <typename Service,
+ auto method,
uint32_t method_id,
size_t max_responses,
size_t output_size_bytes>
class RawTestMethodContext
- : public internal::test::raw::
- Context<method, method_id, max_responses, output_size_bytes> {
+ : public internal::test::raw::Context<Service,
+ method,
+ method_id,
+ max_responses,
+ output_size_bytes> {
public:
// Forwards constructor arguments to the service class.
template <typename... ServiceArgs>
RawTestMethodContext(ServiceArgs&&... service_args)
- : internal::test::raw::
- Context<method, method_id, max_responses, output_size_bytes>(
- std::forward<ServiceArgs>(service_args)...) {}
+ : internal::test::raw::Context<Service,
+ method,
+ method_id,
+ max_responses,
+ output_size_bytes>(
+ std::forward<ServiceArgs>(service_args)...) {}
};
} // namespace pw::rpc
diff --git a/pw_rpc/raw/raw_method_test.cc b/pw_rpc/raw/raw_method_test.cc
index 7679660..c2dfb3e 100644
--- a/pw_rpc/raw/raw_method_test.cc
+++ b/pw_rpc/raw/raw_method_test.cc
@@ -53,7 +53,9 @@
}
};
-StatusWithSize AddFive(ServerCall&, ConstByteSpan request, ByteSpan response) {
+StatusWithSize AddFive(ServerContext&,
+ ConstByteSpan request,
+ ByteSpan response) {
DecodeRawTestRequest(request);
protobuf::NestedEncoder encoder(response);
@@ -65,7 +67,9 @@
return StatusWithSize::Unauthenticated(payload.size());
}
-void StartStream(ServerCall&, ConstByteSpan request, RawServerWriter& writer) {
+void StartStream(ServerContext&,
+ ConstByteSpan request,
+ RawServerWriter& writer) {
DecodeRawTestRequest(request);
last_writer = std::move(writer);
}
diff --git a/pw_rpc/raw/raw_method_union_test.cc b/pw_rpc/raw/raw_method_union_test.cc
index 599601e..77cfd5d 100644
--- a/pw_rpc/raw/raw_method_union_test.cc
+++ b/pw_rpc/raw/raw_method_union_test.cc
@@ -34,9 +34,10 @@
constexpr FakeGeneratedService(uint32_t id) : Service(id, kMethods) {}
static constexpr std::array<RawMethodUnion, 3> kMethods = {
- GetRawMethodFor<&Implementation::DoNothing>(10u),
- GetRawMethodFor<&Implementation::AddFive>(11u),
- GetRawMethodFor<&Implementation::StartStream>(12u),
+ GetRawMethodFor<&Implementation::DoNothing, MethodType::kUnary>(10u),
+ GetRawMethodFor<&Implementation::AddFive, MethodType::kUnary>(11u),
+ GetRawMethodFor<&Implementation::StartStream,
+ MethodType::kServerStreaming>(12u),
};
};