1 // Copyright 2020 The Pigweed Authors
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
7 // https://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
20 #include <type_traits>
22 #include "pw_rpc/internal/base_server_writer.h"
23 #include "pw_rpc/internal/config.h"
24 #include "pw_rpc/internal/method.h"
25 #include "pw_rpc/internal/method_type.h"
26 #include "pw_rpc/internal/nanopb_common.h"
27 #include "pw_rpc/server_context.h"
28 #include "pw_status/status.h"
29 #include "pw_status/status_with_size.h"
33 // Define the Nanopb version of the the ServerWriter class.
35 class ServerWriter : public internal::BaseServerWriter {
37 // Allow default construction so that users can declare a variable into which
38 // to move ServerWriters from RPC calls.
39 constexpr ServerWriter() = default;
41 ServerWriter(ServerWriter&&) = default;
42 ServerWriter& operator=(ServerWriter&&) = default;
44 // Writes a response struct. Returns the following Status codes:
46 // OK - the response was successfully sent
47 // FAILED_PRECONDITION - the writer is closed
48 // INTERNAL - pw_rpc was unable to encode the Nanopb protobuf
49 // other errors - the ChannelOutput failed to send the packet; the error
50 // codes are determined by the ChannelOutput implementation
52 Status Write(const T& response);
60 // MethodTraits specialization for a static unary method.
61 template <typename RequestType, typename ResponseType>
62 struct MethodTraits<Status (*)(
63 ServerContext&, const RequestType&, ResponseType&)> {
64 using Implementation = NanopbMethod;
65 using Request = RequestType;
66 using Response = ResponseType;
68 static constexpr MethodType kType = MethodType::kUnary;
69 static constexpr bool kServerStreaming = false;
70 static constexpr bool kClientStreaming = false;
73 // MethodTraits specialization for a unary method.
74 template <typename T, typename RequestType, typename ResponseType>
75 struct MethodTraits<Status (T::*)(
76 ServerContext&, const RequestType&, ResponseType&)>
77 : public MethodTraits<Status (*)(
78 ServerContext&, const RequestType&, ResponseType&)> {
82 // MethodTraits specialization for a static server streaming method.
83 template <typename RequestType, typename ResponseType>
84 struct MethodTraits<void (*)(
85 ServerContext&, const RequestType&, ServerWriter<ResponseType>&)> {
86 using Implementation = NanopbMethod;
87 using Request = RequestType;
88 using Response = ResponseType;
90 static constexpr MethodType kType = MethodType::kServerStreaming;
91 static constexpr bool kServerStreaming = true;
92 static constexpr bool kClientStreaming = false;
95 // MethodTraits specialization for a server streaming method.
96 template <typename T, typename RequestType, typename ResponseType>
97 struct MethodTraits<void (T::*)(
98 ServerContext&, const RequestType&, ServerWriter<ResponseType>&)>
99 : public MethodTraits<void (*)(
100 ServerContext&, const RequestType&, ServerWriter<ResponseType>&)> {
104 template <auto method>
105 using Request = typename MethodTraits<decltype(method)>::Request;
107 template <auto method>
108 using Response = typename MethodTraits<decltype(method)>::Response;
110 // The NanopbMethod class invokes user-defined service methods. When a
111 // pw::rpc::Server receives an RPC request packet, it looks up the matching
112 // NanopbMethod instance and calls its Invoke method, which eventually calls
113 // into the user-defined RPC function.
115 // A NanopbMethod instance is created for each user-defined RPC in the pw_rpc
116 // generated code. The NanopbMethod stores a pointer to the RPC function, a
117 // pointer to an "invoker" function that calls that function, and pointers to
118 // the Nanopb descriptors used to encode and decode request and response
120 class NanopbMethod : public Method {
122 template <auto method>
123 static constexpr bool matches() {
124 return std::is_same_v<MethodImplementation<method>, NanopbMethod>;
127 // Creates a NanopbMethod for a unary RPC.
128 template <auto method>
129 static constexpr NanopbMethod Unary(uint32_t id,
130 NanopbMessageDescriptor request,
131 NanopbMessageDescriptor response) {
132 // Define a wrapper around the user-defined function that takes the
133 // request and response protobuf structs as void*. This wrapper is stored
134 // generically in the Function union, defined below.
136 // In optimized builds, the compiler inlines the user-defined function into
137 // this wrapper, elminating any overhead.
138 constexpr UnaryFunction wrapper =
139 [](ServerCall& call, const void* req, void* resp) {
140 return CallMethodImplFunction<method>(
142 *static_cast<const Request<method>*>(req),
143 *static_cast<Response<method>*>(resp));
145 return NanopbMethod(id,
146 UnaryInvoker<AllocateSpaceFor<Request<method>>(),
147 AllocateSpaceFor<Response<method>>()>,
148 Function{.unary = wrapper},
153 // Creates a NanopbMethod for a server-streaming RPC.
154 template <auto method>
155 static constexpr NanopbMethod ServerStreaming(
157 NanopbMessageDescriptor request,
158 NanopbMessageDescriptor response) {
159 // Define a wrapper around the user-defined function that takes the request
160 // struct as void* and a BaseServerWriter instead of the templated
161 // ServerWriter class. This wrapper is stored generically in the Function
162 // union, defined below.
163 constexpr ServerStreamingFunction wrapper =
164 [](ServerCall& call, const void* req, BaseServerWriter& writer) {
165 return CallMethodImplFunction<method>(
167 *static_cast<const Request<method>*>(req),
168 static_cast<ServerWriter<Response<method>>&>(writer));
172 ServerStreamingInvoker<AllocateSpaceFor<Request<method>>()>,
173 Function{.server_streaming = wrapper},
178 // Represents an invalid method. Used to reduce error message verbosity.
179 static constexpr NanopbMethod Invalid() {
180 return {0, InvalidInvoker, {}, nullptr, nullptr};
183 // Encodes a response protobuf with Nanopb to the provided buffer.
184 StatusWithSize EncodeResponse(const void* proto_struct,
185 std::span<std::byte> buffer) const {
186 return serde_.EncodeResponse(buffer, proto_struct);
189 // Decodes a response protobuf with Nanopb to the provided buffer. For testing
191 bool DecodeResponse(std::span<const std::byte> response,
192 void* proto_struct) const {
193 return serde_.DecodeResponse(proto_struct, response);
197 // Generic version of the unary RPC function signature:
199 // Status(ServerCall&, const Request&, Response&)
201 using UnaryFunction = Status (*)(ServerCall&,
205 // Generic version of the server streaming RPC function signature:
207 // Status(ServerCall&, const Request&, ServerWriter<Response>&)
209 using ServerStreamingFunction = void (*)(ServerCall&,
211 BaseServerWriter& writer);
213 // The Function union stores a pointer to a generic version of the
214 // user-defined RPC function. Using a union instead of void* avoids
215 // reinterpret_cast, which keeps this class fully constexpr.
218 ServerStreamingFunction server_streaming;
219 // TODO(hepler): Add client_streaming and bidi_streaming
222 // Allocates space for a struct. Rounds up to a reasonable minimum size to
223 // avoid generating unnecessary copies of the invoker functions.
224 template <typename T>
225 static constexpr size_t AllocateSpaceFor() {
226 return std::max(sizeof(T), cfg::kNanopbStructMinBufferSize);
229 constexpr NanopbMethod(uint32_t id,
232 NanopbMessageDescriptor request,
233 NanopbMessageDescriptor response)
234 : Method(id, invoker), function_(function), serde_(request, response) {}
236 void CallUnary(ServerCall& call,
237 const Packet& request,
238 void* request_struct,
239 void* response_struct) const;
241 void CallServerStreaming(ServerCall& call,
242 const Packet& request,
243 void* request_struct) const;
245 // TODO(hepler): Add CallClientStreaming and CallBidiStreaming
247 // Invoker function for unary RPCs. Allocates request and response structs by
248 // size, with maximum alignment, to avoid generating unnecessary copies of
249 // this function for each request/response type.
250 template <size_t request_size, size_t response_size>
251 static void UnaryInvoker(const Method& method,
253 const Packet& request) {
254 _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
255 std::aligned_storage_t<request_size, alignof(std::max_align_t)>
257 _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
258 std::aligned_storage_t<response_size, alignof(std::max_align_t)>
261 static_cast<const NanopbMethod&>(method).CallUnary(
262 call, request, &request_struct, &response_struct);
265 // Invoker function for server streaming RPCs. Allocates space for a request
266 // struct. Ignores the payload buffer since resposnes are sent through the
268 template <size_t request_size>
269 static void ServerStreamingInvoker(const Method& method,
271 const Packet& request) {
272 _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
273 std::aligned_storage_t<request_size, alignof(std::max_align_t)>
276 static_cast<const NanopbMethod&>(method).CallServerStreaming(
277 call, request, &request_struct);
280 // Decodes a request protobuf with Nanopb to the provided buffer. Sends an
281 // error packet if the request failed to decode.
282 bool DecodeRequest(Channel& channel,
283 const Packet& request,
284 void* proto_struct) const;
286 // Encodes a response and sends it over the provided channel.
287 void SendResponse(Channel& channel,
288 const Packet& request,
289 const void* response_struct,
290 Status status) const;
292 // Stores the user-defined RPC in a generic wrapper.
295 // Serde used to encode and decode Nanopb structs.
296 NanopbMethodSerde serde_;
299 } // namespace internal
301 template <typename T>
302 Status ServerWriter<T>::Write(const T& response) {
304 return Status::FailedPrecondition();
307 std::span<std::byte> buffer = AcquirePayloadBuffer();
310 static_cast<const internal::NanopbMethod&>(method()).EncodeResponse(
313 return ReleasePayloadBuffer(buffer.first(result.size()));
316 ReleasePayloadBuffer();
317 return Status::Internal();
320 } // namespace pw::rpc