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