xref: /aosp_15_r20/external/pigweed/pw_rpc/nanopb/public/pw_rpc/nanopb/internal/method.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2021 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 <type_traits>
20 
21 #include "pw_function/function.h"
22 #include "pw_rpc/internal/config.h"
23 #include "pw_rpc/internal/lock.h"
24 #include "pw_rpc/internal/method.h"
25 #include "pw_rpc/method_type.h"
26 #include "pw_rpc/nanopb/internal/common.h"
27 #include "pw_rpc/nanopb/server_reader_writer.h"
28 #include "pw_span/span.h"
29 #include "pw_status/status.h"
30 #include "pw_status/status_with_size.h"
31 
32 namespace pw::rpc::internal {
33 
34 class NanopbMethod;
35 class Packet;
36 
37 // Expected function signatures for user-implemented RPC functions.
38 template <typename Request, typename Response>
39 using NanopbSynchronousUnary = Status(const Request&, Response&);
40 
41 template <typename Request, typename Response>
42 using NanopbAsynchronousUnary = void(const Request&,
43                                      NanopbUnaryResponder<Response>&);
44 
45 template <typename Request, typename Response>
46 using NanopbServerStreaming = void(const Request&,
47                                    NanopbServerWriter<Response>&);
48 
49 template <typename Request, typename Response>
50 using NanopbClientStreaming = void(NanopbServerReader<Request, Response>&);
51 
52 template <typename Request, typename Response>
53 using NanopbBidirectionalStreaming =
54     void(NanopbServerReaderWriter<Request, Response>&);
55 
56 // MethodTraits specialization for a static synchronous unary method.
57 template <typename Req, typename Resp>
58 struct MethodTraits<NanopbSynchronousUnary<Req, Resp>*> {
59   using Implementation = NanopbMethod;
60   using Request = Req;
61   using Response = Resp;
62 
63   static constexpr MethodType kType = MethodType::kUnary;
64   static constexpr bool kSynchronous = true;
65 
66   static constexpr bool kServerStreaming = false;
67   static constexpr bool kClientStreaming = false;
68 };
69 
70 // MethodTraits specialization for a synchronous unary method.
71 template <typename T, typename Req, typename Resp>
72 struct MethodTraits<NanopbSynchronousUnary<Req, Resp>(T::*)>
73     : MethodTraits<NanopbSynchronousUnary<Req, Resp>*> {
74   using Service = T;
75 };
76 
77 // MethodTraits specialization for a static asynchronous unary method.
78 template <typename Req, typename Resp>
79 struct MethodTraits<NanopbAsynchronousUnary<Req, Resp>*>
80     : MethodTraits<NanopbSynchronousUnary<Req, Resp>*> {
81   static constexpr bool kSynchronous = false;
82 };
83 
84 // MethodTraits specialization for an asynchronous unary method.
85 template <typename T, typename Req, typename Resp>
86 struct MethodTraits<NanopbAsynchronousUnary<Req, Resp>(T::*)>
87     : MethodTraits<NanopbSynchronousUnary<Req, Resp>(T::*)> {
88   static constexpr bool kSynchronous = false;
89 };
90 
91 // MethodTraits specialization for a static server streaming method.
92 template <typename Req, typename Resp>
93 struct MethodTraits<NanopbServerStreaming<Req, Resp>*> {
94   using Implementation = NanopbMethod;
95   using Request = Req;
96   using Response = Resp;
97 
98   static constexpr MethodType kType = MethodType::kServerStreaming;
99   static constexpr bool kServerStreaming = true;
100   static constexpr bool kClientStreaming = false;
101 };
102 
103 // MethodTraits specialization for a server streaming method.
104 template <typename T, typename Req, typename Resp>
105 struct MethodTraits<NanopbServerStreaming<Req, Resp>(T::*)>
106     : MethodTraits<NanopbServerStreaming<Req, Resp>*> {
107   using Service = T;
108 };
109 
110 // MethodTraits specialization for a static server streaming method.
111 template <typename Req, typename Resp>
112 struct MethodTraits<NanopbClientStreaming<Req, Resp>*> {
113   using Implementation = NanopbMethod;
114   using Request = Req;
115   using Response = Resp;
116 
117   static constexpr MethodType kType = MethodType::kClientStreaming;
118   static constexpr bool kServerStreaming = false;
119   static constexpr bool kClientStreaming = true;
120 };
121 
122 // MethodTraits specialization for a server streaming method.
123 template <typename T, typename Req, typename Resp>
124 struct MethodTraits<NanopbClientStreaming<Req, Resp>(T::*)>
125     : MethodTraits<NanopbClientStreaming<Req, Resp>*> {
126   using Service = T;
127 };
128 
129 // MethodTraits specialization for a static server streaming method.
130 template <typename Req, typename Resp>
131 struct MethodTraits<NanopbBidirectionalStreaming<Req, Resp>*> {
132   using Implementation = NanopbMethod;
133   using Request = Req;
134   using Response = Resp;
135 
136   static constexpr MethodType kType = MethodType::kBidirectionalStreaming;
137   static constexpr bool kServerStreaming = true;
138   static constexpr bool kClientStreaming = true;
139 };
140 
141 // MethodTraits specialization for a server streaming method.
142 template <typename T, typename Req, typename Resp>
143 struct MethodTraits<NanopbBidirectionalStreaming<Req, Resp>(T::*)>
144     : MethodTraits<NanopbBidirectionalStreaming<Req, Resp>*> {
145   using Service = T;
146 };
147 
148 // The NanopbMethod class invokes user-defined service methods. When a
149 // pw::rpc::Server receives an RPC request packet, it looks up the matching
150 // NanopbMethod instance and calls its Invoke method, which eventually calls
151 // into the user-defined RPC function.
152 //
153 // A NanopbMethod instance is created for each user-defined RPC in the pw_rpc
154 // generated code. The NanopbMethod stores a pointer to the RPC function, a
155 // pointer to an "invoker" function that calls that function, and pointers to
156 // the Nanopb descriptors used to encode and decode request and response
157 // structs.
158 class NanopbMethod : public Method {
159  public:
160   template <auto kMethod, typename RequestType, typename ResponseType>
161   static constexpr bool matches() {
162     return std::is_same_v<MethodImplementation<kMethod>, NanopbMethod> &&
163            std::is_same_v<RequestType, Request<kMethod>> &&
164            std::is_same_v<ResponseType, Response<kMethod>>;
165   }
166 
167   // Creates a NanopbMethod for a synchronous unary RPC.
168   template <auto kMethod>
169   static constexpr NanopbMethod SynchronousUnary(
170       uint32_t id, const NanopbMethodSerde& serde) {
171     // Define a wrapper around the user-defined function that takes the
172     // request and response protobuf structs as void*. This wrapper is stored
173     // generically in the Function union, defined below.
174     //
175     // In optimized builds, the compiler inlines the user-defined function into
176     // this wrapper, eliminating any overhead.
177     constexpr SynchronousUnaryFunction wrapper =
178         [](Service& service, const void* req, void* resp) {
179           return CallMethodImplFunction<kMethod>(
180               service,
181               *static_cast<const Request<kMethod>*>(req),
182               *static_cast<Response<kMethod>*>(resp));
183         };
184     return NanopbMethod(
185         id,
186         SynchronousUnaryInvoker<AllocateSpaceFor<Request<kMethod>>(),
187                                 AllocateSpaceFor<Response<kMethod>>()>,
188         MethodType::kUnary,
189         Function{.synchronous_unary = wrapper},
190         serde);
191   }
192 
193   // Creates a NanopbMethod for an asynchronous unary RPC.
194   template <auto kMethod>
195   static constexpr NanopbMethod AsynchronousUnary(
196       uint32_t id, const NanopbMethodSerde& serde) {
197     // Define a wrapper around the user-defined function that takes the
198     // request and response protobuf structs as void*. This wrapper is stored
199     // generically in the Function union, defined below.
200     //
201     // In optimized builds, the compiler inlines the user-defined function into
202     // this wrapper, eliminating any overhead.
203     constexpr UnaryRequestFunction wrapper =
204         [](Service& service, const void* req, NanopbServerCall& resp) {
205           return CallMethodImplFunction<kMethod>(
206               service,
207               *static_cast<const Request<kMethod>*>(req),
208               static_cast<NanopbUnaryResponder<Response<kMethod>>&>(resp));
209         };
210     return NanopbMethod(
211         id,
212         AsynchronousUnaryInvoker<AllocateSpaceFor<Request<kMethod>>()>,
213         MethodType::kUnary,
214         Function{.unary_request = wrapper},
215         serde);
216   }
217 
218   // Creates a NanopbMethod for a server-streaming RPC.
219   template <auto kMethod>
220   static constexpr NanopbMethod ServerStreaming(
221       uint32_t id, const NanopbMethodSerde& serde) {
222     // Define a wrapper around the user-defined function that takes the request
223     // struct as void* and a NanopbServerCall instead of the
224     // templated NanopbServerWriter class. This wrapper is stored generically in
225     // the Function union, defined below.
226     constexpr UnaryRequestFunction wrapper =
227         [](Service& service, const void* req, NanopbServerCall& writer) {
228           return CallMethodImplFunction<kMethod>(
229               service,
230               *static_cast<const Request<kMethod>*>(req),
231               static_cast<NanopbServerWriter<Response<kMethod>>&>(writer));
232         };
233     return NanopbMethod(
234         id,
235         ServerStreamingInvoker<AllocateSpaceFor<Request<kMethod>>()>,
236         MethodType::kServerStreaming,
237         Function{.unary_request = wrapper},
238         serde);
239   }
240 
241   // Creates a NanopbMethod for a client-streaming RPC.
242   template <auto kMethod>
243   static constexpr NanopbMethod ClientStreaming(
244       uint32_t id, const NanopbMethodSerde& serde) {
245     constexpr StreamRequestFunction wrapper = [](Service& service,
246                                                  NanopbServerCall& reader) {
247       return CallMethodImplFunction<kMethod>(
248           service,
249           static_cast<NanopbServerReader<Request<kMethod>, Response<kMethod>>&>(
250               reader));
251     };
252     return NanopbMethod(id,
253                         ClientStreamingInvoker<Request<kMethod>>,
254                         MethodType::kClientStreaming,
255                         Function{.stream_request = wrapper},
256                         serde);
257   }
258 
259   // Creates a NanopbMethod for a bidirectional-streaming RPC.
260   template <auto kMethod>
261   static constexpr NanopbMethod BidirectionalStreaming(
262       uint32_t id, const NanopbMethodSerde& serde) {
263     constexpr StreamRequestFunction wrapper =
264         [](Service& service, NanopbServerCall& reader_writer) {
265           return CallMethodImplFunction<kMethod>(
266               service,
267               static_cast<NanopbServerReaderWriter<Request<kMethod>,
268                                                    Response<kMethod>>&>(
269                   reader_writer));
270         };
271     return NanopbMethod(id,
272                         BidirectionalStreamingInvoker<Request<kMethod>>,
273                         MethodType::kBidirectionalStreaming,
274                         Function{.stream_request = wrapper},
275                         serde);
276   }
277 
278   // Represents an invalid method. Used to reduce error message verbosity.
279   static constexpr NanopbMethod Invalid() {
280     return {0,
281             InvalidInvoker,
282             MethodType::kUnary,
283             {},
284             NanopbMethodSerde(nullptr, nullptr)};
285   }
286 
287   // Give access to the serializer/deserializer object for converting requests
288   // and responses between the wire format and Nanopb structs.
289   const NanopbMethodSerde& serde() const { return serde_; }
290 
291  private:
292   // Generic function signature for synchronous unary RPCs.
293   using SynchronousUnaryFunction = Status (*)(Service&,
294                                               const void* request,
295                                               void* response);
296 
297   // Generic function signature for asynchronous unary and server streaming
298   // RPCs.
299   using UnaryRequestFunction = void (*)(Service&,
300                                         const void* request,
301                                         NanopbServerCall& writer);
302 
303   // Generic function signature for client and bidirectional streaming RPCs.
304   using StreamRequestFunction = void (*)(Service&,
305                                          NanopbServerCall& reader_writer);
306 
307   // The Function union stores a pointer to a generic version of the
308   // user-defined RPC function. Using a union instead of void* avoids
309   // reinterpret_cast, which keeps this class fully constexpr.
310   union Function {
311     SynchronousUnaryFunction synchronous_unary;
312     UnaryRequestFunction unary_request;
313     StreamRequestFunction stream_request;
314   };
315 
316   // Allocates space for a struct. Rounds up to a reasonable minimum size to
317   // avoid generating unnecessary copies of the invoker functions.
318   template <typename T>
319   static constexpr size_t AllocateSpaceFor() {
320     return std::max(sizeof(T), cfg::kNanopbStructMinBufferSize);
321   }
322 
323   constexpr NanopbMethod(uint32_t id,
324                          Invoker invoker,
325                          MethodType type,
326                          Function function,
327                          const NanopbMethodSerde& serde)
328       : Method(id, invoker, type), function_(function), serde_(serde) {}
329 
330   void CallSynchronousUnary(const CallContext& context,
331                             const Packet& request,
332                             void* request_struct,
333                             void* response_struct) const
334       PW_UNLOCK_FUNCTION(rpc_lock());
335 
336   void CallUnaryRequest(const CallContext& context,
337                         MethodType type,
338                         const Packet& request,
339                         void* request_struct) const
340       PW_UNLOCK_FUNCTION(rpc_lock());
341 
342   // Invoker function for synchronous unary RPCs. Allocates request and response
343   // structs by size, with maximum alignment, to avoid generating unnecessary
344   // copies of this function for each request/response type.
345   template <size_t kRequestSize, size_t kResponseSize>
346   static void SynchronousUnaryInvoker(const CallContext& context,
347                                       const Packet& request)
348       PW_UNLOCK_FUNCTION(rpc_lock()) {
349     _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
350     std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)>
351         request_struct{};
352     _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
353     std::aligned_storage_t<kResponseSize, alignof(std::max_align_t)>
354         response_struct{};
355 
356     static_cast<const NanopbMethod&>(context.method())
357         .CallSynchronousUnary(
358             context, request, &request_struct, &response_struct);
359   }
360 
361   // Invoker function for asynchronous unary RPCs. Allocates space for a request
362   // struct. Ignores the payload buffer since resposnes are sent through the
363   // NanopbUnaryResponder.
364   template <size_t kRequestSize>
365   static void AsynchronousUnaryInvoker(const CallContext& context,
366                                        const Packet& request)
367       PW_UNLOCK_FUNCTION(rpc_lock()) {
368     _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
369     std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)>
370         request_struct{};
371 
372     static_cast<const NanopbMethod&>(context.method())
373         .CallUnaryRequest(
374             context, MethodType::kUnary, request, &request_struct);
375   }
376 
377   // Invoker function for server streaming RPCs. Allocates space for a request
378   // struct. Ignores the payload buffer since resposnes are sent through the
379   // NanopbServerWriter.
380   template <size_t kRequestSize>
381   static void ServerStreamingInvoker(const CallContext& context,
382                                      const Packet& request)
383       PW_UNLOCK_FUNCTION(rpc_lock()) {
384     _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
385     std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)>
386         request_struct{};
387 
388     static_cast<const NanopbMethod&>(context.method())
389         .CallUnaryRequest(
390             context, MethodType::kServerStreaming, request, &request_struct);
391   }
392 
393   // Invoker function for client streaming RPCs.
394   template <typename Request>
395   static void ClientStreamingInvoker(const CallContext& context, const Packet&)
396       PW_UNLOCK_FUNCTION(rpc_lock()) {
397     BaseNanopbServerReader<Request> reader(context.ClaimLocked(),
398                                            MethodType::kClientStreaming);
399     rpc_lock().unlock();
400     static_cast<const NanopbMethod&>(context.method())
401         .function_.stream_request(context.service(), reader);
402   }
403 
404   // Invoker function for bidirectional streaming RPCs.
405   template <typename Request>
406   static void BidirectionalStreamingInvoker(const CallContext& context,
407                                             const Packet&)
408       PW_UNLOCK_FUNCTION(rpc_lock()) {
409     BaseNanopbServerReader<Request> reader_writer(
410         context.ClaimLocked(), MethodType::kBidirectionalStreaming);
411     rpc_lock().unlock();
412     static_cast<const NanopbMethod&>(context.method())
413         .function_.stream_request(context.service(), reader_writer);
414   }
415 
416   // Decodes a request protobuf with Nanopb to the provided buffer. Sends an
417   // error packet if the request failed to decode.
418   bool DecodeRequest(const CallContext& context,
419                      const Packet& request,
420                      void* proto_struct) const
421       PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
422 
423   // Stores the user-defined RPC in a generic wrapper.
424   Function function_;
425 
426   // Serde used to encode and decode Nanopb structs.
427   const NanopbMethodSerde& serde_;
428 };
429 
430 }  // namespace pw::rpc::internal
431