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