• 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 <tuple>
17 #include <utility>
18 
19 #include "pw_assert/assert.h"
20 #include "pw_bytes/span.h"
21 #include "pw_containers/vector.h"
22 #include "pw_preprocessor/arguments.h"
23 #include "pw_rpc/internal/hash.h"
24 #include "pw_rpc/internal/method_lookup.h"
25 #include "pw_rpc/internal/test_method_context.h"
26 #include "pw_rpc/nanopb/fake_channel_output.h"
27 #include "pw_rpc/nanopb/internal/method.h"
28 
29 namespace pw::rpc {
30 
31 // Declares a context object that may be used to invoke an RPC. The context is
32 // declared with the name of the implemented service and the method to invoke.
33 // The RPC can then be invoked with the call method.
34 //
35 // For a unary RPC, context.call(request) returns the status, and the response
36 // struct can be accessed via context.response().
37 //
38 //   PW_NANOPB_TEST_METHOD_CONTEXT(my::CoolService, TheMethod) context;
39 //   EXPECT_EQ(OkStatus(), context.call({.some_arg = 123}));
40 //   EXPECT_EQ(500, context.response().some_response_value);
41 //
42 // For a unary RPC with repeated fields in the response, nanopb uses a
43 // pb_callback_t field called when parsing the response as many times as the
44 // field is present in the protobuf. To set the pb_callback_t fields create the
45 // Response struct and pass it to the response method:
46 //
47 //   PW_NANOPB_TEST_METHOD_CONTEXT(my::CoolService, TheMethod) context;
48 //   EXPECT_EQ(OkStatus(), context.call({.some_arg = 123}));
49 //
50 //   TheMethodResponse response{};
51 //   response.repeated_field.funcs.decode = +[](pb_istream_t* stream,
52 //                                              const pb_field_iter_t* field,
53 //                                              void** arg) -> bool {
54 //     ... decode the field from stream with pb_decode* functions ...
55 //     EXPECT_EQ(submsg.some_field, 123);
56 //     return true;
57 //   };
58 //   response.repeated_field.arg = &some_context_passed_decode_if_needed;
59 //   context.response(response);  // Callbacks called from here.
60 //
61 // For a server streaming RPC, context.call(request) invokes the method. As in a
62 // normal RPC, the method completes when the ServerWriter's Finish method is
63 // called (or it goes out of scope).
64 //
65 //   PW_NANOPB_TEST_METHOD_CONTEXT(my::CoolService, TheStreamingMethod) context;
66 //   context.call({.some_arg = 123});
67 //
68 //   EXPECT_TRUE(context.done());  // Check that the RPC completed
69 //   EXPECT_EQ(OkStatus(), context.status());  // Check the status
70 //
71 //   EXPECT_EQ(3u, context.responses().size());
72 //   EXPECT_EQ(123, context.responses()[0].value); // check individual responses
73 //
74 //   for (const MyResponse& response : context.responses()) {
75 //     // iterate over the responses
76 //   }
77 //
78 // PW_NANOPB_TEST_METHOD_CONTEXT forwards its constructor arguments to the
79 // underlying serivce. For example:
80 //
81 //   PW_NANOPB_TEST_METHOD_CONTEXT(MyService, Go) context(service, args);
82 //
83 // PW_NANOPB_TEST_METHOD_CONTEXT takes one optional argument:
84 //
85 //   size_t kMaxPackets: maximum packets to store
86 //
87 // Example:
88 //
89 //   PW_NANOPB_TEST_METHOD_CONTEXT(MyService, BestMethod, 3, 256) context;
90 //   ASSERT_EQ(3u, context.responses().max_size());
91 //
92 #define PW_NANOPB_TEST_METHOD_CONTEXT(service, method, ...)             \
93   ::pw::rpc::NanopbTestMethodContext<service,                           \
94                                      &service::method,                  \
95                                      ::pw::rpc::internal::Hash(#method) \
96                                          PW_COMMA_ARGS(__VA_ARGS__)>
97 template <typename Service,
98           auto kMethod,
99           uint32_t kMethodId,
100           size_t kMaxPackets = 6,
101           size_t kPayloadsBufferSizeBytes = 256>
102 class NanopbTestMethodContext;
103 
104 // Internal classes that implement NanopbTestMethodContext.
105 namespace internal::test::nanopb {
106 
107 // Collects everything needed to invoke a particular RPC.
108 template <typename Service,
109           auto kMethod,
110           uint32_t kMethodId,
111           size_t kMaxPackets,
112           size_t kPayloadsBufferSizeBytes>
113 class NanopbInvocationContext
114     : public InvocationContext<
115           NanopbFakeChannelOutput<kMaxPackets, kPayloadsBufferSizeBytes>,
116           Service,
117           kMethodId> {
118  public:
119   using Request = internal::Request<kMethod>;
120   using Response = internal::Response<kMethod>;
121 
122   // Gives access to the RPC's most recent response.
response()123   Response response() const {
124     Response response{};
125     PW_ASSERT_OK(kMethodInfo.serde().response().Decode(Base::responses().back(),
126                                                        response));
127     return response;
128   }
129 
130   // Gives access to the RPC's most recent response using pased Response object
131   // to parse the nanopb. Use this version when you need to set pb_callback_t
132   // fields in the Response object before parsing.
response(Response & response)133   void response(Response& response) const {
134     PW_ASSERT_OK(kMethodInfo.serde().response().Decode(Base::responses().back(),
135                                                        response));
136   }
137 
responses()138   NanopbPayloadsView<Response> responses() const {
139     return Base::output().template payload_structs<Response>(
140         kMethodInfo.serde().response(),
141         MethodTraits<decltype(kMethod)>::kType,
142         Base::channel_id(),
143         internal::UnwrapServiceId(Base::service().service_id()),
144         kMethodId);
145   }
146 
147  protected:
148   template <typename... Args>
NanopbInvocationContext(Args &&...args)149   NanopbInvocationContext(Args&&... args)
150       : Base(kMethodInfo,
151              MethodTraits<decltype(kMethod)>::kType,
152              std::forward<Args>(args)...) {}
153 
154   template <size_t kEncodingBufferSizeBytes = 128>
SendClientStream(const Request & request)155   void SendClientStream(const Request& request) PW_LOCKS_EXCLUDED(rpc_lock()) {
156     std::array<std::byte, kEncodingBufferSizeBytes> buffer;
157     Base::SendClientStream(span(buffer).first(
158         kMethodInfo.serde().request().Encode(&request, buffer).size()));
159   }
160 
161  private:
162   using Base = InvocationContext<
163       NanopbFakeChannelOutput<kMaxPackets, kPayloadsBufferSizeBytes>,
164       Service,
165       kMethodId>;
166 
167   static constexpr NanopbMethod kMethodInfo =
168       MethodLookup::GetNanopbMethod<Service, kMethodId>();
169 };
170 
171 // Method invocation context for a unary RPC. Returns the status in
172 // call_context() and provides the response through the response() method.
173 template <typename Service,
174           auto kMethod,
175           uint32_t kMethodId,
176           size_t kPayloadsBufferSizeBytes>
177 class UnaryContext : public NanopbInvocationContext<Service,
178                                                     kMethod,
179                                                     kMethodId,
180                                                     1,
181                                                     kPayloadsBufferSizeBytes> {
182  private:
183   using Base = NanopbInvocationContext<Service,
184                                        kMethod,
185                                        kMethodId,
186                                        1,
187                                        kPayloadsBufferSizeBytes>;
188 
189  public:
190   using Request = typename Base::Request;
191   using Response = typename Base::Response;
192 
193   template <typename... Args>
UnaryContext(Args &&...args)194   UnaryContext(Args&&... args) : Base(std::forward<Args>(args)...) {}
195 
196   // Invokes the RPC with the provided request. Returns the status.
call(const Request & request)197   auto call(const Request& request) {
198     if constexpr (MethodTraits<decltype(kMethod)>::kSynchronous) {
199       Base::output().clear();
200 
201       NanopbUnaryResponder<Response> responder =
202           Base::template GetResponder<NanopbUnaryResponder<Response>>();
203       Response response = {};
204       Status status =
205           CallMethodImplFunction<kMethod>(Base::service(), request, response);
206       PW_ASSERT(responder.Finish(response, status).ok());
207       return status;
208 
209     } else {
210       Base::template call<kMethod, NanopbUnaryResponder<Response>>(request);
211     }
212   }
213 };
214 
215 // Method invocation context for a server streaming RPC.
216 template <typename Service,
217           auto kMethod,
218           uint32_t kMethodId,
219           size_t kMaxPackets,
220           size_t kPayloadsBufferSizeBytes>
221 class ServerStreamingContext
222     : public NanopbInvocationContext<Service,
223                                      kMethod,
224                                      kMethodId,
225                                      kMaxPackets,
226                                      kPayloadsBufferSizeBytes> {
227  private:
228   using Base = NanopbInvocationContext<Service,
229                                        kMethod,
230                                        kMethodId,
231                                        kMaxPackets,
232                                        kPayloadsBufferSizeBytes>;
233 
234  public:
235   using Request = typename Base::Request;
236   using Response = typename Base::Response;
237 
238   template <typename... Args>
ServerStreamingContext(Args &&...args)239   ServerStreamingContext(Args&&... args) : Base(std::forward<Args>(args)...) {}
240 
241   // Invokes the RPC with the provided request.
call(const Request & request)242   void call(const Request& request) {
243     Base::template call<kMethod, NanopbServerWriter<Response>>(request);
244   }
245 
246   // Returns a server writer which writes responses into the context's buffer.
247   // This should not be called alongside call(); use one or the other.
writer()248   NanopbServerWriter<Response> writer() {
249     return Base::template GetResponder<NanopbServerWriter<Response>>();
250   }
251 };
252 
253 // Method invocation context for a client streaming RPC.
254 template <typename Service,
255           auto kMethod,
256           uint32_t kMethodId,
257           size_t kMaxPackets,
258           size_t kPayloadsBufferSizeBytes>
259 class ClientStreamingContext
260     : public NanopbInvocationContext<Service,
261                                      kMethod,
262                                      kMethodId,
263                                      kMaxPackets,
264                                      kPayloadsBufferSizeBytes> {
265  private:
266   using Base = NanopbInvocationContext<Service,
267                                        kMethod,
268                                        kMethodId,
269                                        kMaxPackets,
270                                        kPayloadsBufferSizeBytes>;
271 
272  public:
273   using Request = typename Base::Request;
274   using Response = typename Base::Response;
275 
276   template <typename... Args>
ClientStreamingContext(Args &&...args)277   ClientStreamingContext(Args&&... args) : Base(std::forward<Args>(args)...) {}
278 
279   // Invokes the RPC.
call()280   void call() {
281     Base::template call<kMethod, NanopbServerReader<Request, Response>>();
282   }
283 
284   // Returns a server reader which writes responses into the context's buffer.
285   // This should not be called alongside call(); use one or the other.
reader()286   NanopbServerReader<Request, Response> reader() {
287     return Base::template GetResponder<NanopbServerReader<Request, Response>>();
288   }
289 
290   // Allow sending client streaming packets.
291   using Base::SendClientStream;
292   using Base::SendClientStreamEnd;
293 };
294 
295 // Method invocation context for a bidirectional streaming RPC.
296 template <typename Service,
297           auto kMethod,
298           uint32_t kMethodId,
299           size_t kMaxPackets,
300           size_t kPayloadsBufferSizeBytes>
301 class BidirectionalStreamingContext
302     : public NanopbInvocationContext<Service,
303                                      kMethod,
304                                      kMethodId,
305                                      kMaxPackets,
306                                      kPayloadsBufferSizeBytes> {
307  private:
308   using Base = NanopbInvocationContext<Service,
309                                        kMethod,
310                                        kMethodId,
311                                        kMaxPackets,
312                                        kPayloadsBufferSizeBytes>;
313 
314  public:
315   using Request = typename Base::Request;
316   using Response = typename Base::Response;
317 
318   template <typename... Args>
BidirectionalStreamingContext(Args &&...args)319   BidirectionalStreamingContext(Args&&... args)
320       : Base(std::forward<Args>(args)...) {}
321 
322   // Invokes the RPC.
call()323   void call() {
324     Base::template call<kMethod, NanopbServerReaderWriter<Request, Response>>();
325   }
326 
327   // Returns a server reader which writes responses into the context's buffer.
328   // This should not be called alongside call(); use one or the other.
reader_writer()329   NanopbServerReaderWriter<Request, Response> reader_writer() {
330     return Base::template GetResponder<
331         NanopbServerReaderWriter<Request, Response>>();
332   }
333 
334   // Allow sending client streaming packets.
335   using Base::SendClientStream;
336   using Base::SendClientStreamEnd;
337 };
338 
339 // Alias to select the type of the context object to use based on which type of
340 // RPC it is for.
341 template <typename Service,
342           auto kMethod,
343           uint32_t kMethodId,
344           size_t kMaxPackets,
345           size_t kPayloadsBufferSizeBytes>
346 using Context = std::tuple_element_t<
347     static_cast<size_t>(internal::MethodTraits<decltype(kMethod)>::kType),
348     std::tuple<
349         UnaryContext<Service, kMethod, kMethodId, kPayloadsBufferSizeBytes>,
350         ServerStreamingContext<Service,
351                                kMethod,
352                                kMethodId,
353                                kMaxPackets,
354                                kPayloadsBufferSizeBytes>,
355         ClientStreamingContext<Service,
356                                kMethod,
357                                kMethodId,
358                                kMaxPackets,
359                                kPayloadsBufferSizeBytes>,
360         BidirectionalStreamingContext<Service,
361                                       kMethod,
362                                       kMethodId,
363                                       kMaxPackets,
364                                       kPayloadsBufferSizeBytes>>>;
365 
366 }  // namespace internal::test::nanopb
367 
368 template <typename Service,
369           auto kMethod,
370           uint32_t kMethodId,
371           size_t kMaxPackets,
372           size_t kPayloadsBufferSizeBytes>
373 class NanopbTestMethodContext
374     : public internal::test::nanopb::Context<Service,
375                                              kMethod,
376                                              kMethodId,
377                                              kMaxPackets,
378                                              kPayloadsBufferSizeBytes> {
379  public:
380   // Forwards constructor arguments to the service class.
381   template <typename... ServiceArgs>
NanopbTestMethodContext(ServiceArgs &&...service_args)382   NanopbTestMethodContext(ServiceArgs&&... service_args)
383       : internal::test::nanopb::Context<Service,
384                                         kMethod,
385                                         kMethodId,
386                                         kMaxPackets,
387                                         kPayloadsBufferSizeBytes>(
388             std::forward<ServiceArgs>(service_args)...) {}
389 };
390 
391 }  // namespace pw::rpc
392