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