• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Pigweed Authors
2 //
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
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
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
13 // the License.
14 #pragma once
15 
16 #include <algorithm>
17 #include <cstddef>
18 #include <cstdint>
19 #include <span>
20 #include <type_traits>
21 
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"
30 
31 namespace pw::rpc {
32 
33 // Define the Nanopb version of the the ServerWriter class.
34 template <typename T>
35 class ServerWriter : public internal::BaseServerWriter {
36  public:
37   // Allow default construction so that users can declare a variable into which
38   // to move ServerWriters from RPC calls.
39   constexpr ServerWriter() = default;
40 
41   ServerWriter(ServerWriter&&) = default;
42   ServerWriter& operator=(ServerWriter&&) = default;
43 
44   // Writes a response struct. Returns the following Status codes:
45   //
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
51   //
52   Status Write(const T& response);
53 };
54 
55 namespace internal {
56 
57 class NanopbMethod;
58 class Packet;
59 
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;
67 
68   static constexpr MethodType kType = MethodType::kUnary;
69   static constexpr bool kServerStreaming = false;
70   static constexpr bool kClientStreaming = false;
71 };
72 
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&)> {
79   using Service = T;
80 };
81 
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;
89 
90   static constexpr MethodType kType = MethodType::kServerStreaming;
91   static constexpr bool kServerStreaming = true;
92   static constexpr bool kClientStreaming = false;
93 };
94 
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>&)> {
101   using Service = T;
102 };
103 
104 template <auto method>
105 using Request = typename MethodTraits<decltype(method)>::Request;
106 
107 template <auto method>
108 using Response = typename MethodTraits<decltype(method)>::Response;
109 
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.
114 //
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
119 // structs.
120 class NanopbMethod : public Method {
121  public:
122   template <auto method, typename RequestType, typename ResponseType>
123   static constexpr bool matches() {
124     return std::is_same_v<MethodImplementation<method>, NanopbMethod> &&
125            std::is_same_v<RequestType, Request<method>> &&
126            std::is_same_v<ResponseType, Response<method>>;
127   }
128 
129   // Creates a NanopbMethod for a unary RPC.
130   template <auto method>
131   static constexpr NanopbMethod Unary(uint32_t id,
132                                       NanopbMessageDescriptor request,
133                                       NanopbMessageDescriptor response) {
134     // Define a wrapper around the user-defined function that takes the
135     // request and response protobuf structs as void*. This wrapper is stored
136     // generically in the Function union, defined below.
137     //
138     // In optimized builds, the compiler inlines the user-defined function into
139     // this wrapper, elminating any overhead.
140     constexpr UnaryFunction wrapper =
141         [](ServerCall& call, const void* req, void* resp) {
142           return CallMethodImplFunction<method>(
143               call,
144               *static_cast<const Request<method>*>(req),
145               *static_cast<Response<method>*>(resp));
146         };
147     return NanopbMethod(id,
148                         UnaryInvoker<AllocateSpaceFor<Request<method>>(),
149                                      AllocateSpaceFor<Response<method>>()>,
150                         Function{.unary = wrapper},
151                         request,
152                         response);
153   }
154 
155   // Creates a NanopbMethod for a server-streaming RPC.
156   template <auto method>
157   static constexpr NanopbMethod ServerStreaming(
158       uint32_t id,
159       NanopbMessageDescriptor request,
160       NanopbMessageDescriptor response) {
161     // Define a wrapper around the user-defined function that takes the request
162     // struct as void* and a BaseServerWriter instead of the templated
163     // ServerWriter class. This wrapper is stored generically in the Function
164     // union, defined below.
165     constexpr ServerStreamingFunction wrapper =
166         [](ServerCall& call, const void* req, BaseServerWriter& writer) {
167           return CallMethodImplFunction<method>(
168               call,
169               *static_cast<const Request<method>*>(req),
170               static_cast<ServerWriter<Response<method>>&>(writer));
171         };
172     return NanopbMethod(
173         id,
174         ServerStreamingInvoker<AllocateSpaceFor<Request<method>>()>,
175         Function{.server_streaming = wrapper},
176         request,
177         response);
178   }
179 
180   // Represents an invalid method. Used to reduce error message verbosity.
181   static constexpr NanopbMethod Invalid() {
182     return {0, InvalidInvoker, {}, nullptr, nullptr};
183   }
184 
185   // Encodes a response protobuf with Nanopb to the provided buffer.
186   StatusWithSize EncodeResponse(const void* proto_struct,
187                                 std::span<std::byte> buffer) const {
188     return serde_.EncodeResponse(buffer, proto_struct);
189   }
190 
191   // Decodes a response protobuf with Nanopb to the provided buffer. For testing
192   // use.
193   bool DecodeResponse(std::span<const std::byte> response,
194                       void* proto_struct) const {
195     return serde_.DecodeResponse(proto_struct, response);
196   }
197 
198  private:
199   // Generic version of the unary RPC function signature:
200   //
201   //   Status(ServerCall&, const Request&, Response&)
202   //
203   using UnaryFunction = Status (*)(ServerCall&,
204                                    const void* request,
205                                    void* response);
206 
207   // Generic version of the server streaming RPC function signature:
208   //
209   //   Status(ServerCall&, const Request&, ServerWriter<Response>&)
210   //
211   using ServerStreamingFunction = void (*)(ServerCall&,
212                                            const void* request,
213                                            BaseServerWriter& writer);
214 
215   // The Function union stores a pointer to a generic version of the
216   // user-defined RPC function. Using a union instead of void* avoids
217   // reinterpret_cast, which keeps this class fully constexpr.
218   union Function {
219     UnaryFunction unary;
220     ServerStreamingFunction server_streaming;
221     // TODO(hepler): Add client_streaming and bidi_streaming
222   };
223 
224   // Allocates space for a struct. Rounds up to a reasonable minimum size to
225   // avoid generating unnecessary copies of the invoker functions.
226   template <typename T>
227   static constexpr size_t AllocateSpaceFor() {
228     return std::max(sizeof(T), cfg::kNanopbStructMinBufferSize);
229   }
230 
231   constexpr NanopbMethod(uint32_t id,
232                          Invoker invoker,
233                          Function function,
234                          NanopbMessageDescriptor request,
235                          NanopbMessageDescriptor response)
236       : Method(id, invoker), function_(function), serde_(request, response) {}
237 
238   void CallUnary(ServerCall& call,
239                  const Packet& request,
240                  void* request_struct,
241                  void* response_struct) const;
242 
243   void CallServerStreaming(ServerCall& call,
244                            const Packet& request,
245                            void* request_struct) const;
246 
247   // TODO(hepler): Add CallClientStreaming and CallBidiStreaming
248 
249   // Invoker function for unary RPCs. Allocates request and response structs by
250   // size, with maximum alignment, to avoid generating unnecessary copies of
251   // this function for each request/response type.
252   template <size_t kRequestSize, size_t kResponseSize>
253   static void UnaryInvoker(const Method& method,
254                            ServerCall& call,
255                            const Packet& request) {
256     _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
257     std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)>
258         request_struct{};
259     _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
260     std::aligned_storage_t<kResponseSize, alignof(std::max_align_t)>
261         response_struct{};
262 
263     static_cast<const NanopbMethod&>(method).CallUnary(
264         call, request, &request_struct, &response_struct);
265   }
266 
267   // Invoker function for server streaming RPCs. Allocates space for a request
268   // struct. Ignores the payload buffer since resposnes are sent through the
269   // ServerWriter.
270   template <size_t kRequestSize>
271   static void ServerStreamingInvoker(const Method& method,
272                                      ServerCall& call,
273                                      const Packet& request) {
274     _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
275     std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)>
276         request_struct{};
277 
278     static_cast<const NanopbMethod&>(method).CallServerStreaming(
279         call, request, &request_struct);
280   }
281 
282   // Decodes a request protobuf with Nanopb to the provided buffer. Sends an
283   // error packet if the request failed to decode.
284   bool DecodeRequest(Channel& channel,
285                      const Packet& request,
286                      void* proto_struct) const;
287 
288   // Encodes a response and sends it over the provided channel.
289   void SendResponse(Channel& channel,
290                     const Packet& request,
291                     const void* response_struct,
292                     Status status) const;
293 
294   // Stores the user-defined RPC in a generic wrapper.
295   Function function_;
296 
297   // Serde used to encode and decode Nanopb structs.
298   NanopbMethodSerde serde_;
299 };
300 
301 }  // namespace internal
302 
303 template <typename T>
304 Status ServerWriter<T>::Write(const T& response) {
305   if (!open()) {
306     return Status::FailedPrecondition();
307   }
308 
309   std::span<std::byte> buffer = AcquirePayloadBuffer();
310 
311   if (auto result =
312           static_cast<const internal::NanopbMethod&>(method()).EncodeResponse(
313               &response, buffer);
314       result.ok()) {
315     return ReleasePayloadBuffer(buffer.first(result.size()));
316   }
317 
318   ReleasePayloadBuffer();
319   return Status::Internal();
320 }
321 
322 }  // namespace pw::rpc
323