xref: /aosp_15_r20/external/pigweed/pw_rpc/pwpb/public/pw_rpc/pwpb/internal/method.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2022 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 <cstddef>
17 #include <cstdint>
18 #include <type_traits>
19 
20 #include "pw_bytes/span.h"
21 #include "pw_rpc/internal/call_context.h"
22 #include "pw_rpc/internal/lock.h"
23 #include "pw_rpc/internal/method.h"
24 #include "pw_rpc/internal/packet.h"
25 #include "pw_rpc/method_type.h"
26 #include "pw_rpc/pwpb/internal/common.h"
27 #include "pw_rpc/pwpb/server_reader_writer.h"
28 #include "pw_rpc/service.h"
29 #include "pw_span/span.h"
30 #include "pw_status/status_with_size.h"
31 
32 namespace pw::rpc::internal {
33 
34 // Expected function signatures for user-implemented RPC functions.
35 template <typename Request, typename Response>
36 using PwpbSynchronousUnary = Status(const Request&, Response&);
37 
38 template <typename Request, typename Response>
39 using PwpbAsynchronousUnary = void(const Request&,
40                                    PwpbUnaryResponder<Response>&);
41 
42 template <typename Request, typename Response>
43 using PwpbServerStreaming = void(const Request&, PwpbServerWriter<Response>&);
44 
45 template <typename Request, typename Response>
46 using PwpbClientStreaming = void(PwpbServerReader<Request, Response>&);
47 
48 template <typename Request, typename Response>
49 using PwpbBidirectionalStreaming =
50     void(PwpbServerReaderWriter<Request, Response>&);
51 
52 // The PwpbMethod class invokes user-defined service methods. When a
53 // pw::rpc::Server receives an RPC request packet, it looks up the matching
54 // PwpbMethod instance and calls its Invoke method, which eventually calls into
55 // the user-defined RPC function.
56 //
57 // A PwpbMethod instance is created for each user-defined RPC in the pw_rpc
58 // generated code. The PwpbMethod stores a pointer to the RPC function,
59 // a pointer to an "invoker" function that calls that function, and a
60 // reference to a serializer/deserializer initiiated with the message struct
61 // tables used to encode and decode request and response message structs.
62 class PwpbMethod : public Method {
63  public:
64   template <auto kMethod, typename RequestType, typename ResponseType>
matches()65   static constexpr bool matches() {
66     return std::conjunction_v<
67         std::is_same<MethodImplementation<kMethod>, PwpbMethod>,
68         std::is_same<RequestType, Request<kMethod>>,
69         std::is_same<ResponseType, Response<kMethod>>>;
70   }
71 
72   // Creates a PwpbMethod for a synchronous unary RPC.
73   // TODO: b/234874001 - Find a way to reduce the number of monomorphized copies
74   // of this method.
75   template <auto kMethod>
SynchronousUnary(uint32_t id,const PwpbMethodSerde & serde)76   static constexpr PwpbMethod SynchronousUnary(uint32_t id,
77                                                const PwpbMethodSerde& serde) {
78     // Define a wrapper around the user-defined function that takes the
79     // request and response protobuf structs as byte spans, and calls the
80     // implementation with the correct type.
81     //
82     // This wrapper is stored generically in the Function union, defined below.
83     // In optimized builds, the compiler inlines the user-defined function into
84     // this wrapper, eliminating any overhead.
85     constexpr SynchronousUnaryFunction wrapper =
86         [](Service& service, const void* request, void* response) {
87           return CallMethodImplFunction<kMethod>(
88               service,
89               *reinterpret_cast<const Request<kMethod>*>(request),
90               *reinterpret_cast<Response<kMethod>*>(response));
91         };
92     return PwpbMethod(
93         id,
94         SynchronousUnaryInvoker<Request<kMethod>, Response<kMethod>>,
95         MethodType::kUnary,
96         Function{.synchronous_unary = wrapper},
97         serde);
98   }
99 
100   // Creates a PwpbMethod for an asynchronous unary RPC.
101   // TODO: b/234874001 - Find a way to reduce the number of monomorphized copies
102   // of this method.
103   template <auto kMethod>
AsynchronousUnary(uint32_t id,const PwpbMethodSerde & serde)104   static constexpr PwpbMethod AsynchronousUnary(uint32_t id,
105                                                 const PwpbMethodSerde& serde) {
106     // Define a wrapper around the user-defined function that takes the
107     // request struct as a byte span, the response as a server call, and calls
108     // the implementation with the correct types.
109     //
110     // This wrapper is stored generically in the Function union, defined below.
111     // In optimized builds, the compiler inlines the user-defined function into
112     // this wrapper, eliminating any overhead.
113     constexpr UnaryRequestFunction wrapper =
114         [](Service& service,
115            const void* request,
116            internal::PwpbServerCall& writer) {
117           return CallMethodImplFunction<kMethod>(
118               service,
119               *reinterpret_cast<const Request<kMethod>*>(request),
120               static_cast<PwpbUnaryResponder<Response<kMethod>>&>(writer));
121         };
122     return PwpbMethod(id,
123                       AsynchronousUnaryInvoker<Request<kMethod>>,
124                       MethodType::kUnary,
125                       Function{.unary_request = wrapper},
126                       serde);
127   }
128 
129   // Creates a PwpbMethod for a server-streaming RPC.
130   template <auto kMethod>
ServerStreaming(uint32_t id,const PwpbMethodSerde & serde)131   static constexpr PwpbMethod ServerStreaming(uint32_t id,
132                                               const PwpbMethodSerde& serde) {
133     // Define a wrapper around the user-defined function that takes the
134     // request struct as a byte span, the response as a server call, and calls
135     // the implementation with the correct types.
136     //
137     // This wrapper is stored generically in the Function union, defined below.
138     // In optimized builds, the compiler inlines the user-defined function into
139     // this wrapper, eliminating any overhead.
140     constexpr UnaryRequestFunction wrapper =
141         [](Service& service,
142            const void* request,
143            internal::PwpbServerCall& writer) {
144           return CallMethodImplFunction<kMethod>(
145               service,
146               *reinterpret_cast<const Request<kMethod>*>(request),
147               static_cast<PwpbServerWriter<Response<kMethod>>&>(writer));
148         };
149     return PwpbMethod(id,
150                       ServerStreamingInvoker<Request<kMethod>>,
151                       MethodType::kServerStreaming,
152                       Function{.unary_request = wrapper},
153                       serde);
154   }
155 
156   // Creates a PwpbMethod for a client-streaming RPC.
157   template <auto kMethod>
ClientStreaming(uint32_t id,const PwpbMethodSerde & serde)158   static constexpr PwpbMethod ClientStreaming(uint32_t id,
159                                               const PwpbMethodSerde& serde) {
160     // Define a wrapper around the user-defined function that takes the
161     // request as a server call, and calls the implementation with the correct
162     // types.
163     //
164     // This wrapper is stored generically in the Function union, defined below.
165     // In optimized builds, the compiler inlines the user-defined function into
166     // this wrapper, eliminating any overhead.
167     constexpr StreamRequestFunction wrapper = [](Service& service,
168                                                  internal::PwpbServerCall&
169                                                      reader) {
170       return CallMethodImplFunction<kMethod>(
171           service,
172           static_cast<PwpbServerReader<Request<kMethod>, Response<kMethod>>&>(
173               reader));
174     };
175     return PwpbMethod(id,
176                       ClientStreamingInvoker<Request<kMethod>>,
177                       MethodType::kClientStreaming,
178                       Function{.stream_request = wrapper},
179                       serde);
180   }
181 
182   // Creates a PwpbMethod for a bidirectional-streaming RPC.
183   template <auto kMethod>
BidirectionalStreaming(uint32_t id,const PwpbMethodSerde & serde)184   static constexpr PwpbMethod BidirectionalStreaming(
185       uint32_t id, const PwpbMethodSerde& serde) {
186     // Define a wrapper around the user-defined function that takes the
187     // request and response as a server call, and calls the implementation with
188     // the correct types.
189     //
190     // This wrapper is stored generically in the Function union, defined below.
191     // In optimized builds, the compiler inlines the user-defined function into
192     // this wrapper, eliminating any overhead.
193     constexpr StreamRequestFunction wrapper =
194         [](Service& service, internal::PwpbServerCall& reader_writer) {
195           return CallMethodImplFunction<kMethod>(
196               service,
197               static_cast<
198                   PwpbServerReaderWriter<Request<kMethod>, Response<kMethod>>&>(
199                   reader_writer));
200         };
201     return PwpbMethod(id,
202                       BidirectionalStreamingInvoker<Request<kMethod>>,
203                       MethodType::kBidirectionalStreaming,
204                       Function{.stream_request = wrapper},
205                       serde);
206   }
207 
208   // Represents an invalid method. Used to reduce error message verbosity.
Invalid()209   static constexpr PwpbMethod Invalid() {
210     return {0,
211             InvalidInvoker,
212             MethodType::kUnary,
213             {},
214             PwpbMethodSerde(nullptr, nullptr)};
215   }
216 
217   // Give access to the serializer/deserializer object for converting requests
218   // and responses between the wire format and pw_protobuf structs.
serde()219   const PwpbMethodSerde& serde() const { return serde_; }
220 
221  private:
222   // Generic function signature for synchronous unary RPCs.
223   using SynchronousUnaryFunction = Status (*)(Service&,
224                                               const void* request,
225                                               void* response);
226 
227   // Generic function signature for asynchronous unary and server streaming
228   // RPCs.
229   using UnaryRequestFunction = void (*)(Service&,
230                                         const void* request,
231                                         internal::PwpbServerCall& writer);
232 
233   // Generic function signature for client and bidirectional streaming RPCs.
234   using StreamRequestFunction =
235       void (*)(Service&, internal::PwpbServerCall& reader_writer);
236 
237   // The Function union stores a pointer to a generic version of the
238   // user-defined RPC function. Using a union instead of void* avoids
239   // reinterpret_cast, which keeps this class fully constexpr.
240   union Function {
241     SynchronousUnaryFunction synchronous_unary;
242     UnaryRequestFunction unary_request;
243     StreamRequestFunction stream_request;
244   };
245 
PwpbMethod(uint32_t id,Invoker invoker,MethodType type,Function function,const PwpbMethodSerde & serde)246   constexpr PwpbMethod(uint32_t id,
247                        Invoker invoker,
248                        MethodType type,
249                        Function function,
250                        const PwpbMethodSerde& serde)
251       : Method(id, invoker, type), function_(function), serde_(serde) {}
252 
253   template <typename Request, typename Response>
CallSynchronousUnary(const CallContext & context,const Packet & request,Request & request_struct,Response & response_struct)254   void CallSynchronousUnary(const CallContext& context,
255                             const Packet& request,
256                             Request& request_struct,
257                             Response& response_struct) const
258       PW_UNLOCK_FUNCTION(rpc_lock()) {
259     if (!DecodeRequest(context, request, request_struct).ok()) {
260       context.server().CleanUpCalls();
261       return;
262     }
263 
264     internal::PwpbServerCall responder(context.ClaimLocked(),
265                                        MethodType::kUnary);
266     context.server().CleanUpCalls();
267     const Status status = function_.synchronous_unary(
268         context.service(), &request_struct, &response_struct);
269     responder.SendUnaryResponse(response_struct, status).IgnoreError();
270   }
271 
272   template <typename Request>
CallUnaryRequest(const CallContext & context,MethodType method_type,const Packet & request,Request & request_struct)273   void CallUnaryRequest(const CallContext& context,
274                         MethodType method_type,
275                         const Packet& request,
276                         Request& request_struct) const
277       PW_UNLOCK_FUNCTION(rpc_lock()) {
278     if (!DecodeRequest(context, request, request_struct).ok()) {
279       context.server().CleanUpCalls();
280       return;
281     }
282 
283     internal::PwpbServerCall server_writer(context.ClaimLocked(), method_type);
284     context.server().CleanUpCalls();
285     function_.unary_request(context.service(), &request_struct, server_writer);
286   }
287 
288   // Decodes a request protobuf into the provided buffer. Sends an error packet
289   // if the request failed to decode.
290   template <typename Request>
DecodeRequest(const CallContext & context,const Packet & request,Request & request_struct)291   Status DecodeRequest(const CallContext& context,
292                        const Packet& request,
293                        Request& request_struct) const
294       PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
295     const auto status =
296         serde_.request().Decode(request.payload(), request_struct);
297     if (status.ok()) {
298       return status;
299     }
300 
301     // The channel is known to exist. It was found when the request was
302     // processed and the lock has been held since, so GetInternalChannel cannot
303     // fail.
304     context.server()
305         .GetInternalChannel(context.channel_id())
306         ->Send(Packet::ServerError(request, Status::DataLoss()))
307         .IgnoreError();
308     return status;
309   }
310 
311   // Invoker function for synchronous unary RPCs.
312   template <typename Request, typename Response>
SynchronousUnaryInvoker(const CallContext & context,const Packet & request)313   static void SynchronousUnaryInvoker(const CallContext& context,
314                                       const Packet& request)
315       PW_UNLOCK_FUNCTION(rpc_lock()) {
316     Request request_struct{};
317     Response response_struct{};
318 
319     static_cast<const PwpbMethod&>(context.method())
320         .CallSynchronousUnary(
321             context, request, request_struct, response_struct);
322   }
323 
324   // Invoker function for asynchronous unary RPCs.
325   template <typename Request>
AsynchronousUnaryInvoker(const CallContext & context,const Packet & request)326   static void AsynchronousUnaryInvoker(const CallContext& context,
327                                        const Packet& request)
328       PW_UNLOCK_FUNCTION(rpc_lock()) {
329     Request request_struct{};
330 
331     static_cast<const PwpbMethod&>(context.method())
332         .CallUnaryRequest(context, MethodType::kUnary, request, request_struct);
333   }
334 
335   // Invoker function for server streaming RPCs.
336   template <typename Request>
ServerStreamingInvoker(const CallContext & context,const Packet & request)337   static void ServerStreamingInvoker(const CallContext& context,
338                                      const Packet& request)
339       PW_UNLOCK_FUNCTION(rpc_lock()) {
340     Request request_struct{};
341 
342     static_cast<const PwpbMethod&>(context.method())
343         .CallUnaryRequest(
344             context, MethodType::kServerStreaming, request, request_struct);
345   }
346 
347   // Invoker function for client streaming RPCs.
348   template <typename Request>
ClientStreamingInvoker(const CallContext & context,const Packet &)349   static void ClientStreamingInvoker(const CallContext& context, const Packet&)
350       PW_UNLOCK_FUNCTION(rpc_lock()) {
351     internal::BasePwpbServerReader<Request> reader(
352         context.ClaimLocked(), MethodType::kClientStreaming);
353     context.server().CleanUpCalls();
354     static_cast<const PwpbMethod&>(context.method())
355         .function_.stream_request(context.service(), reader);
356   }
357 
358   // Invoker function for bidirectional streaming RPCs.
359   template <typename Request>
BidirectionalStreamingInvoker(const CallContext & context,const Packet &)360   static void BidirectionalStreamingInvoker(const CallContext& context,
361                                             const Packet&)
362       PW_UNLOCK_FUNCTION(rpc_lock()) {
363     internal::BasePwpbServerReader<Request> reader_writer(
364         context.ClaimLocked(), MethodType::kBidirectionalStreaming);
365     context.server().CleanUpCalls();
366     static_cast<const PwpbMethod&>(context.method())
367         .function_.stream_request(context.service(), reader_writer);
368   }
369 
370   // Stores the user-defined RPC in a generic wrapper.
371   Function function_;
372 
373   // Serde used to encode and decode pw_protobuf structs.
374   const PwpbMethodSerde& serde_;
375 };
376 
377 // MethodTraits specialization for a static synchronous unary method.
378 // TODO: b/234874320 - Further qualify this (and nanopb) definition so that they
379 // can co-exist in the same project.
380 template <typename Req, typename Res>
381 struct MethodTraits<PwpbSynchronousUnary<Req, Res>*> {
382   using Implementation = PwpbMethod;
383   using Request = Req;
384   using Response = Res;
385 
386   static constexpr MethodType kType = MethodType::kUnary;
387   static constexpr bool kSynchronous = true;
388 
389   static constexpr bool kServerStreaming = false;
390   static constexpr bool kClientStreaming = false;
391 };
392 
393 // MethodTraits specialization for a synchronous raw unary method.
394 template <typename T, typename Req, typename Res>
395 struct MethodTraits<PwpbSynchronousUnary<Req, Res>(T::*)>
396     : MethodTraits<PwpbSynchronousUnary<Req, Res>*> {
397   using Service = T;
398 };
399 
400 // MethodTraits specialization for a static asynchronous unary method.
401 template <typename Req, typename Resp>
402 struct MethodTraits<PwpbAsynchronousUnary<Req, Resp>*>
403     : MethodTraits<PwpbSynchronousUnary<Req, Resp>*> {
404   static constexpr bool kSynchronous = false;
405 };
406 
407 // MethodTraits specialization for an asynchronous unary method.
408 template <typename T, typename Req, typename Resp>
409 struct MethodTraits<PwpbAsynchronousUnary<Req, Resp>(T::*)>
410     : MethodTraits<PwpbSynchronousUnary<Req, Resp>(T::*)> {
411   static constexpr bool kSynchronous = false;
412 };
413 
414 // MethodTraits specialization for a static server streaming method.
415 template <typename Req, typename Resp>
416 struct MethodTraits<PwpbServerStreaming<Req, Resp>*> {
417   using Implementation = PwpbMethod;
418   using Request = Req;
419   using Response = Resp;
420 
421   static constexpr MethodType kType = MethodType::kServerStreaming;
422   static constexpr bool kServerStreaming = true;
423   static constexpr bool kClientStreaming = false;
424 };
425 
426 // MethodTraits specialization for a server streaming method.
427 template <typename T, typename Req, typename Resp>
428 struct MethodTraits<PwpbServerStreaming<Req, Resp>(T::*)>
429     : MethodTraits<PwpbServerStreaming<Req, Resp>*> {
430   using Service = T;
431 };
432 
433 // MethodTraits specialization for a static server streaming method.
434 template <typename Req, typename Resp>
435 struct MethodTraits<PwpbClientStreaming<Req, Resp>*> {
436   using Implementation = PwpbMethod;
437   using Request = Req;
438   using Response = Resp;
439 
440   static constexpr MethodType kType = MethodType::kClientStreaming;
441   static constexpr bool kServerStreaming = false;
442   static constexpr bool kClientStreaming = true;
443 };
444 
445 // MethodTraits specialization for a server streaming method.
446 template <typename T, typename Req, typename Resp>
447 struct MethodTraits<PwpbClientStreaming<Req, Resp>(T::*)>
448     : MethodTraits<PwpbClientStreaming<Req, Resp>*> {
449   using Service = T;
450 };
451 
452 // MethodTraits specialization for a static server streaming method.
453 template <typename Req, typename Resp>
454 struct MethodTraits<PwpbBidirectionalStreaming<Req, Resp>*> {
455   using Implementation = PwpbMethod;
456   using Request = Req;
457   using Response = Resp;
458 
459   static constexpr MethodType kType = MethodType::kBidirectionalStreaming;
460   static constexpr bool kServerStreaming = true;
461   static constexpr bool kClientStreaming = true;
462 };
463 
464 // MethodTraits specialization for a server streaming method.
465 template <typename T, typename Req, typename Resp>
466 struct MethodTraits<PwpbBidirectionalStreaming<Req, Resp>(T::*)>
467     : MethodTraits<PwpbBidirectionalStreaming<Req, Resp>*> {
468   using Service = T;
469 };
470 
471 }  // namespace pw::rpc::internal
472