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