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