Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / third_party / pigweed / repo / pw_rpc / nanopb / public / pw_rpc / internal / nanopb_method.h
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 <algorithm>
17 #include <cstddef>
18 #include <cstdint>
19 #include <span>
20 #include <type_traits>
21
22 #include "pw_rpc/internal/base_server_writer.h"
23 #include "pw_rpc/internal/config.h"
24 #include "pw_rpc/internal/method.h"
25 #include "pw_rpc/internal/method_type.h"
26 #include "pw_rpc/internal/nanopb_common.h"
27 #include "pw_rpc/server_context.h"
28 #include "pw_status/status.h"
29 #include "pw_status/status_with_size.h"
30
31 namespace pw::rpc {
32
33 // Define the Nanopb version of the the ServerWriter class.
34 template <typename T>
35 class ServerWriter : public internal::BaseServerWriter {
36  public:
37   // Allow default construction so that users can declare a variable into which
38   // to move ServerWriters from RPC calls.
39   constexpr ServerWriter() = default;
40
41   ServerWriter(ServerWriter&&) = default;
42   ServerWriter& operator=(ServerWriter&&) = default;
43
44   // Writes a response struct. Returns the following Status codes:
45   //
46   //   OK - the response was successfully sent
47   //   FAILED_PRECONDITION - the writer is closed
48   //   INTERNAL - pw_rpc was unable to encode the Nanopb protobuf
49   //   other errors - the ChannelOutput failed to send the packet; the error
50   //       codes are determined by the ChannelOutput implementation
51   //
52   Status Write(const T& response);
53 };
54
55 namespace internal {
56
57 class NanopbMethod;
58 class Packet;
59
60 // MethodTraits specialization for a static unary method.
61 template <typename RequestType, typename ResponseType>
62 struct MethodTraits<Status (*)(
63     ServerContext&, const RequestType&, ResponseType&)> {
64   using Implementation = NanopbMethod;
65   using Request = RequestType;
66   using Response = ResponseType;
67
68   static constexpr MethodType kType = MethodType::kUnary;
69   static constexpr bool kServerStreaming = false;
70   static constexpr bool kClientStreaming = false;
71 };
72
73 // MethodTraits specialization for a unary method.
74 template <typename T, typename RequestType, typename ResponseType>
75 struct MethodTraits<Status (T::*)(
76     ServerContext&, const RequestType&, ResponseType&)>
77     : public MethodTraits<Status (*)(
78           ServerContext&, const RequestType&, ResponseType&)> {
79   using Service = T;
80 };
81
82 // MethodTraits specialization for a static server streaming method.
83 template <typename RequestType, typename ResponseType>
84 struct MethodTraits<void (*)(
85     ServerContext&, const RequestType&, ServerWriter<ResponseType>&)> {
86   using Implementation = NanopbMethod;
87   using Request = RequestType;
88   using Response = ResponseType;
89
90   static constexpr MethodType kType = MethodType::kServerStreaming;
91   static constexpr bool kServerStreaming = true;
92   static constexpr bool kClientStreaming = false;
93 };
94
95 // MethodTraits specialization for a server streaming method.
96 template <typename T, typename RequestType, typename ResponseType>
97 struct MethodTraits<void (T::*)(
98     ServerContext&, const RequestType&, ServerWriter<ResponseType>&)>
99     : public MethodTraits<void (*)(
100           ServerContext&, const RequestType&, ServerWriter<ResponseType>&)> {
101   using Service = T;
102 };
103
104 template <auto method>
105 using Request = typename MethodTraits<decltype(method)>::Request;
106
107 template <auto method>
108 using Response = typename MethodTraits<decltype(method)>::Response;
109
110 // The NanopbMethod class invokes user-defined service methods. When a
111 // pw::rpc::Server receives an RPC request packet, it looks up the matching
112 // NanopbMethod instance and calls its Invoke method, which eventually calls
113 // into the user-defined RPC function.
114 //
115 // A NanopbMethod instance is created for each user-defined RPC in the pw_rpc
116 // generated code. The NanopbMethod stores a pointer to the RPC function, a
117 // pointer to an "invoker" function that calls that function, and pointers to
118 // the Nanopb descriptors used to encode and decode request and response
119 // structs.
120 class NanopbMethod : public Method {
121  public:
122   template <auto method>
123   static constexpr bool matches() {
124     return std::is_same_v<MethodImplementation<method>, NanopbMethod>;
125   }
126
127   // Creates a NanopbMethod for a unary RPC.
128   template <auto method>
129   static constexpr NanopbMethod Unary(uint32_t id,
130                                       NanopbMessageDescriptor request,
131                                       NanopbMessageDescriptor response) {
132     // Define a wrapper around the user-defined function that takes the
133     // request and response protobuf structs as void*. This wrapper is stored
134     // generically in the Function union, defined below.
135     //
136     // In optimized builds, the compiler inlines the user-defined function into
137     // this wrapper, elminating any overhead.
138     constexpr UnaryFunction wrapper =
139         [](ServerCall& call, const void* req, void* resp) {
140           return CallMethodImplFunction<method>(
141               call,
142               *static_cast<const Request<method>*>(req),
143               *static_cast<Response<method>*>(resp));
144         };
145     return NanopbMethod(id,
146                         UnaryInvoker<AllocateSpaceFor<Request<method>>(),
147                                      AllocateSpaceFor<Response<method>>()>,
148                         Function{.unary = wrapper},
149                         request,
150                         response);
151   }
152
153   // Creates a NanopbMethod for a server-streaming RPC.
154   template <auto method>
155   static constexpr NanopbMethod ServerStreaming(
156       uint32_t id,
157       NanopbMessageDescriptor request,
158       NanopbMessageDescriptor response) {
159     // Define a wrapper around the user-defined function that takes the request
160     // struct as void* and a BaseServerWriter instead of the templated
161     // ServerWriter class. This wrapper is stored generically in the Function
162     // union, defined below.
163     constexpr ServerStreamingFunction wrapper =
164         [](ServerCall& call, const void* req, BaseServerWriter& writer) {
165           return CallMethodImplFunction<method>(
166               call,
167               *static_cast<const Request<method>*>(req),
168               static_cast<ServerWriter<Response<method>>&>(writer));
169         };
170     return NanopbMethod(
171         id,
172         ServerStreamingInvoker<AllocateSpaceFor<Request<method>>()>,
173         Function{.server_streaming = wrapper},
174         request,
175         response);
176   }
177
178   // Represents an invalid method. Used to reduce error message verbosity.
179   static constexpr NanopbMethod Invalid() {
180     return {0, InvalidInvoker, {}, nullptr, nullptr};
181   }
182
183   // Encodes a response protobuf with Nanopb to the provided buffer.
184   StatusWithSize EncodeResponse(const void* proto_struct,
185                                 std::span<std::byte> buffer) const {
186     return serde_.EncodeResponse(buffer, proto_struct);
187   }
188
189   // Decodes a response protobuf with Nanopb to the provided buffer. For testing
190   // use.
191   bool DecodeResponse(std::span<const std::byte> response,
192                       void* proto_struct) const {
193     return serde_.DecodeResponse(proto_struct, response);
194   }
195
196  private:
197   // Generic version of the unary RPC function signature:
198   //
199   //   Status(ServerCall&, const Request&, Response&)
200   //
201   using UnaryFunction = Status (*)(ServerCall&,
202                                    const void* request,
203                                    void* response);
204
205   // Generic version of the server streaming RPC function signature:
206   //
207   //   Status(ServerCall&, const Request&, ServerWriter<Response>&)
208   //
209   using ServerStreamingFunction = void (*)(ServerCall&,
210                                            const void* request,
211                                            BaseServerWriter& writer);
212
213   // The Function union stores a pointer to a generic version of the
214   // user-defined RPC function. Using a union instead of void* avoids
215   // reinterpret_cast, which keeps this class fully constexpr.
216   union Function {
217     UnaryFunction unary;
218     ServerStreamingFunction server_streaming;
219     // TODO(hepler): Add client_streaming and bidi_streaming
220   };
221
222   // Allocates space for a struct. Rounds up to a reasonable minimum size to
223   // avoid generating unnecessary copies of the invoker functions.
224   template <typename T>
225   static constexpr size_t AllocateSpaceFor() {
226     return std::max(sizeof(T), cfg::kNanopbStructMinBufferSize);
227   }
228
229   constexpr NanopbMethod(uint32_t id,
230                          Invoker invoker,
231                          Function function,
232                          NanopbMessageDescriptor request,
233                          NanopbMessageDescriptor response)
234       : Method(id, invoker), function_(function), serde_(request, response) {}
235
236   void CallUnary(ServerCall& call,
237                  const Packet& request,
238                  void* request_struct,
239                  void* response_struct) const;
240
241   void CallServerStreaming(ServerCall& call,
242                            const Packet& request,
243                            void* request_struct) const;
244
245   // TODO(hepler): Add CallClientStreaming and CallBidiStreaming
246
247   // Invoker function for unary RPCs. Allocates request and response structs by
248   // size, with maximum alignment, to avoid generating unnecessary copies of
249   // this function for each request/response type.
250   template <size_t request_size, size_t response_size>
251   static void UnaryInvoker(const Method& method,
252                            ServerCall& call,
253                            const Packet& request) {
254     _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
255     std::aligned_storage_t<request_size, alignof(std::max_align_t)>
256         request_struct{};
257     _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
258     std::aligned_storage_t<response_size, alignof(std::max_align_t)>
259         response_struct{};
260
261     static_cast<const NanopbMethod&>(method).CallUnary(
262         call, request, &request_struct, &response_struct);
263   }
264
265   // Invoker function for server streaming RPCs. Allocates space for a request
266   // struct. Ignores the payload buffer since resposnes are sent through the
267   // ServerWriter.
268   template <size_t request_size>
269   static void ServerStreamingInvoker(const Method& method,
270                                      ServerCall& call,
271                                      const Packet& request) {
272     _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
273     std::aligned_storage_t<request_size, alignof(std::max_align_t)>
274         request_struct{};
275
276     static_cast<const NanopbMethod&>(method).CallServerStreaming(
277         call, request, &request_struct);
278   }
279
280   // Decodes a request protobuf with Nanopb to the provided buffer. Sends an
281   // error packet if the request failed to decode.
282   bool DecodeRequest(Channel& channel,
283                      const Packet& request,
284                      void* proto_struct) const;
285
286   // Encodes a response and sends it over the provided channel.
287   void SendResponse(Channel& channel,
288                     const Packet& request,
289                     const void* response_struct,
290                     Status status) const;
291
292   // Stores the user-defined RPC in a generic wrapper.
293   Function function_;
294
295   // Serde used to encode and decode Nanopb structs.
296   NanopbMethodSerde serde_;
297 };
298
299 }  // namespace internal
300
301 template <typename T>
302 Status ServerWriter<T>::Write(const T& response) {
303   if (!open()) {
304     return Status::FailedPrecondition();
305   }
306
307   std::span<std::byte> buffer = AcquirePayloadBuffer();
308
309   if (auto result =
310           static_cast<const internal::NanopbMethod&>(method()).EncodeResponse(
311               &response, buffer);
312       result.ok()) {
313     return ReleasePayloadBuffer(buffer.first(result.size()));
314   }
315
316   ReleasePayloadBuffer();
317   return Status::Internal();
318 }
319
320 }  // namespace pw::rpc