1 // Copyright 2021 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 <type_traits> 20 21 #include "pw_function/function.h" 22 #include "pw_rpc/internal/config.h" 23 #include "pw_rpc/internal/lock.h" 24 #include "pw_rpc/internal/method.h" 25 #include "pw_rpc/method_type.h" 26 #include "pw_rpc/nanopb/internal/common.h" 27 #include "pw_rpc/nanopb/server_reader_writer.h" 28 #include "pw_span/span.h" 29 #include "pw_status/status.h" 30 #include "pw_status/status_with_size.h" 31 32 namespace pw::rpc::internal { 33 34 class NanopbMethod; 35 class Packet; 36 37 // Expected function signatures for user-implemented RPC functions. 38 template <typename Request, typename Response> 39 using NanopbSynchronousUnary = Status(const Request&, Response&); 40 41 template <typename Request, typename Response> 42 using NanopbAsynchronousUnary = void(const Request&, 43 NanopbUnaryResponder<Response>&); 44 45 template <typename Request, typename Response> 46 using NanopbServerStreaming = void(const Request&, 47 NanopbServerWriter<Response>&); 48 49 template <typename Request, typename Response> 50 using NanopbClientStreaming = void(NanopbServerReader<Request, Response>&); 51 52 template <typename Request, typename Response> 53 using NanopbBidirectionalStreaming = 54 void(NanopbServerReaderWriter<Request, Response>&); 55 56 // MethodTraits specialization for a static synchronous unary method. 57 template <typename Req, typename Resp> 58 struct MethodTraits<NanopbSynchronousUnary<Req, Resp>*> { 59 using Implementation = NanopbMethod; 60 using Request = Req; 61 using Response = Resp; 62 63 static constexpr MethodType kType = MethodType::kUnary; 64 static constexpr bool kSynchronous = true; 65 66 static constexpr bool kServerStreaming = false; 67 static constexpr bool kClientStreaming = false; 68 }; 69 70 // MethodTraits specialization for a synchronous unary method. 71 template <typename T, typename Req, typename Resp> 72 struct MethodTraits<NanopbSynchronousUnary<Req, Resp>(T::*)> 73 : MethodTraits<NanopbSynchronousUnary<Req, Resp>*> { 74 using Service = T; 75 }; 76 77 // MethodTraits specialization for a static asynchronous unary method. 78 template <typename Req, typename Resp> 79 struct MethodTraits<NanopbAsynchronousUnary<Req, Resp>*> 80 : MethodTraits<NanopbSynchronousUnary<Req, Resp>*> { 81 static constexpr bool kSynchronous = false; 82 }; 83 84 // MethodTraits specialization for an asynchronous unary method. 85 template <typename T, typename Req, typename Resp> 86 struct MethodTraits<NanopbAsynchronousUnary<Req, Resp>(T::*)> 87 : MethodTraits<NanopbSynchronousUnary<Req, Resp>(T::*)> { 88 static constexpr bool kSynchronous = false; 89 }; 90 91 // MethodTraits specialization for a static server streaming method. 92 template <typename Req, typename Resp> 93 struct MethodTraits<NanopbServerStreaming<Req, Resp>*> { 94 using Implementation = NanopbMethod; 95 using Request = Req; 96 using Response = Resp; 97 98 static constexpr MethodType kType = MethodType::kServerStreaming; 99 static constexpr bool kServerStreaming = true; 100 static constexpr bool kClientStreaming = false; 101 }; 102 103 // MethodTraits specialization for a server streaming method. 104 template <typename T, typename Req, typename Resp> 105 struct MethodTraits<NanopbServerStreaming<Req, Resp>(T::*)> 106 : MethodTraits<NanopbServerStreaming<Req, Resp>*> { 107 using Service = T; 108 }; 109 110 // MethodTraits specialization for a static server streaming method. 111 template <typename Req, typename Resp> 112 struct MethodTraits<NanopbClientStreaming<Req, Resp>*> { 113 using Implementation = NanopbMethod; 114 using Request = Req; 115 using Response = Resp; 116 117 static constexpr MethodType kType = MethodType::kClientStreaming; 118 static constexpr bool kServerStreaming = false; 119 static constexpr bool kClientStreaming = true; 120 }; 121 122 // MethodTraits specialization for a server streaming method. 123 template <typename T, typename Req, typename Resp> 124 struct MethodTraits<NanopbClientStreaming<Req, Resp>(T::*)> 125 : MethodTraits<NanopbClientStreaming<Req, Resp>*> { 126 using Service = T; 127 }; 128 129 // MethodTraits specialization for a static server streaming method. 130 template <typename Req, typename Resp> 131 struct MethodTraits<NanopbBidirectionalStreaming<Req, Resp>*> { 132 using Implementation = NanopbMethod; 133 using Request = Req; 134 using Response = Resp; 135 136 static constexpr MethodType kType = MethodType::kBidirectionalStreaming; 137 static constexpr bool kServerStreaming = true; 138 static constexpr bool kClientStreaming = true; 139 }; 140 141 // MethodTraits specialization for a server streaming method. 142 template <typename T, typename Req, typename Resp> 143 struct MethodTraits<NanopbBidirectionalStreaming<Req, Resp>(T::*)> 144 : MethodTraits<NanopbBidirectionalStreaming<Req, Resp>*> { 145 using Service = T; 146 }; 147 148 // The NanopbMethod class invokes user-defined service methods. When a 149 // pw::rpc::Server receives an RPC request packet, it looks up the matching 150 // NanopbMethod instance and calls its Invoke method, which eventually calls 151 // into the user-defined RPC function. 152 // 153 // A NanopbMethod instance is created for each user-defined RPC in the pw_rpc 154 // generated code. The NanopbMethod stores a pointer to the RPC function, a 155 // pointer to an "invoker" function that calls that function, and pointers to 156 // the Nanopb descriptors used to encode and decode request and response 157 // structs. 158 class NanopbMethod : public Method { 159 public: 160 template <auto kMethod, typename RequestType, typename ResponseType> 161 static constexpr bool matches() { 162 return std::is_same_v<MethodImplementation<kMethod>, NanopbMethod> && 163 std::is_same_v<RequestType, Request<kMethod>> && 164 std::is_same_v<ResponseType, Response<kMethod>>; 165 } 166 167 // Creates a NanopbMethod for a synchronous unary RPC. 168 template <auto kMethod> 169 static constexpr NanopbMethod SynchronousUnary( 170 uint32_t id, const NanopbMethodSerde& serde) { 171 // Define a wrapper around the user-defined function that takes the 172 // request and response protobuf structs as void*. This wrapper is stored 173 // generically in the Function union, defined below. 174 // 175 // In optimized builds, the compiler inlines the user-defined function into 176 // this wrapper, eliminating any overhead. 177 constexpr SynchronousUnaryFunction wrapper = 178 [](Service& service, const void* req, void* resp) { 179 return CallMethodImplFunction<kMethod>( 180 service, 181 *static_cast<const Request<kMethod>*>(req), 182 *static_cast<Response<kMethod>*>(resp)); 183 }; 184 return NanopbMethod( 185 id, 186 SynchronousUnaryInvoker<AllocateSpaceFor<Request<kMethod>>(), 187 AllocateSpaceFor<Response<kMethod>>()>, 188 MethodType::kUnary, 189 Function{.synchronous_unary = wrapper}, 190 serde); 191 } 192 193 // Creates a NanopbMethod for an asynchronous unary RPC. 194 template <auto kMethod> 195 static constexpr NanopbMethod AsynchronousUnary( 196 uint32_t id, const NanopbMethodSerde& serde) { 197 // Define a wrapper around the user-defined function that takes the 198 // request and response protobuf structs as void*. This wrapper is stored 199 // generically in the Function union, defined below. 200 // 201 // In optimized builds, the compiler inlines the user-defined function into 202 // this wrapper, eliminating any overhead. 203 constexpr UnaryRequestFunction wrapper = 204 [](Service& service, const void* req, NanopbServerCall& resp) { 205 return CallMethodImplFunction<kMethod>( 206 service, 207 *static_cast<const Request<kMethod>*>(req), 208 static_cast<NanopbUnaryResponder<Response<kMethod>>&>(resp)); 209 }; 210 return NanopbMethod( 211 id, 212 AsynchronousUnaryInvoker<AllocateSpaceFor<Request<kMethod>>()>, 213 MethodType::kUnary, 214 Function{.unary_request = wrapper}, 215 serde); 216 } 217 218 // Creates a NanopbMethod for a server-streaming RPC. 219 template <auto kMethod> 220 static constexpr NanopbMethod ServerStreaming( 221 uint32_t id, const NanopbMethodSerde& serde) { 222 // Define a wrapper around the user-defined function that takes the request 223 // struct as void* and a NanopbServerCall instead of the 224 // templated NanopbServerWriter class. This wrapper is stored generically in 225 // the Function union, defined below. 226 constexpr UnaryRequestFunction wrapper = 227 [](Service& service, const void* req, NanopbServerCall& writer) { 228 return CallMethodImplFunction<kMethod>( 229 service, 230 *static_cast<const Request<kMethod>*>(req), 231 static_cast<NanopbServerWriter<Response<kMethod>>&>(writer)); 232 }; 233 return NanopbMethod( 234 id, 235 ServerStreamingInvoker<AllocateSpaceFor<Request<kMethod>>()>, 236 MethodType::kServerStreaming, 237 Function{.unary_request = wrapper}, 238 serde); 239 } 240 241 // Creates a NanopbMethod for a client-streaming RPC. 242 template <auto kMethod> 243 static constexpr NanopbMethod ClientStreaming( 244 uint32_t id, const NanopbMethodSerde& serde) { 245 constexpr StreamRequestFunction wrapper = [](Service& service, 246 NanopbServerCall& reader) { 247 return CallMethodImplFunction<kMethod>( 248 service, 249 static_cast<NanopbServerReader<Request<kMethod>, Response<kMethod>>&>( 250 reader)); 251 }; 252 return NanopbMethod(id, 253 ClientStreamingInvoker<Request<kMethod>>, 254 MethodType::kClientStreaming, 255 Function{.stream_request = wrapper}, 256 serde); 257 } 258 259 // Creates a NanopbMethod for a bidirectional-streaming RPC. 260 template <auto kMethod> 261 static constexpr NanopbMethod BidirectionalStreaming( 262 uint32_t id, const NanopbMethodSerde& serde) { 263 constexpr StreamRequestFunction wrapper = 264 [](Service& service, NanopbServerCall& reader_writer) { 265 return CallMethodImplFunction<kMethod>( 266 service, 267 static_cast<NanopbServerReaderWriter<Request<kMethod>, 268 Response<kMethod>>&>( 269 reader_writer)); 270 }; 271 return NanopbMethod(id, 272 BidirectionalStreamingInvoker<Request<kMethod>>, 273 MethodType::kBidirectionalStreaming, 274 Function{.stream_request = wrapper}, 275 serde); 276 } 277 278 // Represents an invalid method. Used to reduce error message verbosity. 279 static constexpr NanopbMethod Invalid() { 280 return {0, 281 InvalidInvoker, 282 MethodType::kUnary, 283 {}, 284 NanopbMethodSerde(nullptr, nullptr)}; 285 } 286 287 // Give access to the serializer/deserializer object for converting requests 288 // and responses between the wire format and Nanopb structs. 289 const NanopbMethodSerde& serde() const { return serde_; } 290 291 private: 292 // Generic function signature for synchronous unary RPCs. 293 using SynchronousUnaryFunction = Status (*)(Service&, 294 const void* request, 295 void* response); 296 297 // Generic function signature for asynchronous unary and server streaming 298 // RPCs. 299 using UnaryRequestFunction = void (*)(Service&, 300 const void* request, 301 NanopbServerCall& writer); 302 303 // Generic function signature for client and bidirectional streaming RPCs. 304 using StreamRequestFunction = void (*)(Service&, 305 NanopbServerCall& reader_writer); 306 307 // The Function union stores a pointer to a generic version of the 308 // user-defined RPC function. Using a union instead of void* avoids 309 // reinterpret_cast, which keeps this class fully constexpr. 310 union Function { 311 SynchronousUnaryFunction synchronous_unary; 312 UnaryRequestFunction unary_request; 313 StreamRequestFunction stream_request; 314 }; 315 316 // Allocates space for a struct. Rounds up to a reasonable minimum size to 317 // avoid generating unnecessary copies of the invoker functions. 318 template <typename T> 319 static constexpr size_t AllocateSpaceFor() { 320 return std::max(sizeof(T), cfg::kNanopbStructMinBufferSize); 321 } 322 323 constexpr NanopbMethod(uint32_t id, 324 Invoker invoker, 325 MethodType type, 326 Function function, 327 const NanopbMethodSerde& serde) 328 : Method(id, invoker, type), function_(function), serde_(serde) {} 329 330 void CallSynchronousUnary(const CallContext& context, 331 const Packet& request, 332 void* request_struct, 333 void* response_struct) const 334 PW_UNLOCK_FUNCTION(rpc_lock()); 335 336 void CallUnaryRequest(const CallContext& context, 337 MethodType type, 338 const Packet& request, 339 void* request_struct) const 340 PW_UNLOCK_FUNCTION(rpc_lock()); 341 342 // Invoker function for synchronous unary RPCs. Allocates request and response 343 // structs by size, with maximum alignment, to avoid generating unnecessary 344 // copies of this function for each request/response type. 345 template <size_t kRequestSize, size_t kResponseSize> 346 static void SynchronousUnaryInvoker(const CallContext& context, 347 const Packet& request) 348 PW_UNLOCK_FUNCTION(rpc_lock()) { 349 _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS 350 std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)> 351 request_struct{}; 352 _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS 353 std::aligned_storage_t<kResponseSize, alignof(std::max_align_t)> 354 response_struct{}; 355 356 static_cast<const NanopbMethod&>(context.method()) 357 .CallSynchronousUnary( 358 context, request, &request_struct, &response_struct); 359 } 360 361 // Invoker function for asynchronous unary RPCs. Allocates space for a request 362 // struct. Ignores the payload buffer since resposnes are sent through the 363 // NanopbUnaryResponder. 364 template <size_t kRequestSize> 365 static void AsynchronousUnaryInvoker(const CallContext& context, 366 const Packet& request) 367 PW_UNLOCK_FUNCTION(rpc_lock()) { 368 _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS 369 std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)> 370 request_struct{}; 371 372 static_cast<const NanopbMethod&>(context.method()) 373 .CallUnaryRequest( 374 context, MethodType::kUnary, request, &request_struct); 375 } 376 377 // Invoker function for server streaming RPCs. Allocates space for a request 378 // struct. Ignores the payload buffer since resposnes are sent through the 379 // NanopbServerWriter. 380 template <size_t kRequestSize> 381 static void ServerStreamingInvoker(const CallContext& context, 382 const Packet& request) 383 PW_UNLOCK_FUNCTION(rpc_lock()) { 384 _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS 385 std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)> 386 request_struct{}; 387 388 static_cast<const NanopbMethod&>(context.method()) 389 .CallUnaryRequest( 390 context, MethodType::kServerStreaming, request, &request_struct); 391 } 392 393 // Invoker function for client streaming RPCs. 394 template <typename Request> 395 static void ClientStreamingInvoker(const CallContext& context, const Packet&) 396 PW_UNLOCK_FUNCTION(rpc_lock()) { 397 BaseNanopbServerReader<Request> reader(context.ClaimLocked(), 398 MethodType::kClientStreaming); 399 rpc_lock().unlock(); 400 static_cast<const NanopbMethod&>(context.method()) 401 .function_.stream_request(context.service(), reader); 402 } 403 404 // Invoker function for bidirectional streaming RPCs. 405 template <typename Request> 406 static void BidirectionalStreamingInvoker(const CallContext& context, 407 const Packet&) 408 PW_UNLOCK_FUNCTION(rpc_lock()) { 409 BaseNanopbServerReader<Request> reader_writer( 410 context.ClaimLocked(), MethodType::kBidirectionalStreaming); 411 rpc_lock().unlock(); 412 static_cast<const NanopbMethod&>(context.method()) 413 .function_.stream_request(context.service(), reader_writer); 414 } 415 416 // Decodes a request protobuf with Nanopb to the provided buffer. Sends an 417 // error packet if the request failed to decode. 418 bool DecodeRequest(const CallContext& context, 419 const Packet& request, 420 void* proto_struct) const 421 PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()); 422 423 // Stores the user-defined RPC in a generic wrapper. 424 Function function_; 425 426 // Serde used to encode and decode Nanopb structs. 427 const NanopbMethodSerde& serde_; 428 }; 429 430 } // namespace pw::rpc::internal 431