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 <type_traits> 17 18 #include "pw_assert/assert.h" 19 #include "pw_containers/vector.h" 20 #include "pw_preprocessor/arguments.h" 21 #include "pw_rpc/channel.h" 22 #include "pw_rpc/internal/hash.h" 23 #include "pw_rpc/internal/method_lookup.h" 24 #include "pw_rpc/internal/packet.h" 25 #include "pw_rpc/internal/test_method_context.h" 26 #include "pw_rpc/raw/fake_channel_output.h" 27 #include "pw_rpc/raw/internal/method.h" 28 29 namespace pw::rpc { 30 31 // Declares a context object that may be used to invoke an RPC. The context is 32 // declared with the name of the implemented service and the method to invoke. 33 // The RPC can then be invoked with the call method. 34 // 35 // For a unary RPC, context.call(request) returns the status, and the response 36 // struct can be accessed via context.response(). 37 // 38 // PW_RAW_TEST_METHOD_CONTEXT(my::CoolService, TheMethod) context; 39 // EXPECT_EQ(OkStatus(), context.call(encoded_request).status()); 40 // EXPECT_EQ(0, 41 // std::memcmp(encoded_response, 42 // context.response().data(), 43 // sizeof(encoded_response))); 44 // 45 // For a server streaming RPC, context.call(request) invokes the method. As in a 46 // normal RPC, the method completes when the ServerWriter's Finish method is 47 // called (or it goes out of scope). 48 // 49 // PW_RAW_TEST_METHOD_CONTEXT(my::CoolService, TheStreamingMethod) context; 50 // context.call(encoded_response); 51 // 52 // EXPECT_TRUE(context.done()); // Check that the RPC completed 53 // EXPECT_EQ(OkStatus(), context.status()); // Check the status 54 // 55 // EXPECT_EQ(3u, context.responses().size()); 56 // ByteSpan& response = context.responses()[0]; // check individual responses 57 // 58 // for (ByteSpan& response : context.responses()) { 59 // // iterate over the responses 60 // } 61 // 62 // PW_RAW_TEST_METHOD_CONTEXT forwards its constructor arguments to the 63 // underlying service. For example: 64 // 65 // PW_RAW_TEST_METHOD_CONTEXT(MyService, Go) context(service, args); 66 // 67 // PW_RAW_TEST_METHOD_CONTEXT takes one optional arguments: 68 // 69 // size_t kMaxPackets: maximum packets to store 70 // 71 // Example: 72 // 73 // PW_RAW_TEST_METHOD_CONTEXT(MyService, BestMethod, 3, 256) context; 74 // ASSERT_EQ(3u, context.responses().max_size()); 75 // 76 #define PW_RAW_TEST_METHOD_CONTEXT(service, method, ...) \ 77 ::pw::rpc::RawTestMethodContext<service, \ 78 &service::method, \ 79 ::pw::rpc::internal::Hash(#method) \ 80 PW_COMMA_ARGS(__VA_ARGS__)> 81 template <typename Service, 82 auto kMethod, 83 uint32_t kMethodId, 84 size_t kMaxPackets = 6> 85 class RawTestMethodContext; 86 87 // Internal classes that implement RawTestMethodContext. 88 namespace internal::test::raw { 89 90 inline constexpr size_t kPayloadsBufferSizeBytes = 256; 91 92 // Collects everything needed to invoke a particular RPC. 93 template <typename Service, 94 auto kMethod, 95 uint32_t kMethodId, 96 size_t kMaxPackets> 97 class RawInvocationContext 98 : public InvocationContext< 99 RawFakeChannelOutput<kMaxPackets, kPayloadsBufferSizeBytes>, 100 Service, 101 kMethodId> { 102 public: 103 // Gives access to the RPC's most recent response. response()104 const ConstByteSpan& response() const { return Base::responses().back(); } 105 106 protected: 107 template <typename... Args> RawInvocationContext(Args &&...args)108 RawInvocationContext(Args&&... args) 109 : Base(MethodLookup::GetRawMethod<Service, kMethodId>(), 110 MethodTraits<decltype(kMethod)>::kType, 111 std::forward<Args>(args)...) {} 112 113 private: 114 using Base = InvocationContext< 115 RawFakeChannelOutput<kMaxPackets, kPayloadsBufferSizeBytes>, 116 Service, 117 kMethodId>; 118 }; 119 120 // Method invocation context for a unary RPC. Returns the status in call() and 121 // provides the response through the response() method. 122 template <typename Service, auto kMethod, uint32_t kMethodId> 123 class UnaryContext 124 : public RawInvocationContext<Service, kMethod, kMethodId, 1> { 125 using Base = RawInvocationContext<Service, kMethod, kMethodId, 1>; 126 127 public: 128 template <typename... Args> UnaryContext(Args &&...args)129 UnaryContext(Args&&... args) : Base(std::forward<Args>(args)...) {} 130 131 // Invokes the RPC with the provided request. Returns RPC's StatusWithSize. 132 template <size_t kSynchronousResponseBufferSizeBytes = 64> call(ConstByteSpan request)133 auto call(ConstByteSpan request) { 134 if constexpr (MethodTraits<decltype(kMethod)>::kSynchronous) { 135 Base::output().clear(); 136 137 auto responder = Base::template GetResponder<RawUnaryResponder>(); 138 std::byte response[kSynchronousResponseBufferSizeBytes] = {}; 139 auto sws = CallMethodImplFunction<kMethod>( 140 Base::service(), request, span(response)); 141 PW_ASSERT(responder.Finish(span(response).first(sws.size()), sws.status()) 142 .ok()); 143 return sws; 144 } else { 145 Base::template call<kMethod, RawUnaryResponder>(request); 146 } 147 } 148 }; 149 150 // Method invocation context for a server streaming RPC. 151 template <typename Service, 152 auto kMethod, 153 uint32_t kMethodId, 154 size_t kMaxPackets> 155 class ServerStreamingContext 156 : public RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets> { 157 using Base = RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets>; 158 159 public: 160 template <typename... Args> ServerStreamingContext(Args &&...args)161 ServerStreamingContext(Args&&... args) : Base(std::forward<Args>(args)...) {} 162 163 // Invokes the RPC with the provided request. call(ConstByteSpan request)164 void call(ConstByteSpan request) { 165 Base::template call<kMethod, RawServerWriter>(request); 166 } 167 168 // Returns a server writer which writes responses into the context's buffer. 169 // This should not be called alongside call(); use one or the other. writer()170 RawServerWriter writer() { 171 return Base::template GetResponder<RawServerWriter>(); 172 } 173 }; 174 175 // Method invocation context for a client streaming RPC. 176 template <typename Service, 177 auto kMethod, 178 uint32_t kMethodId, 179 size_t kMaxPackets> 180 class ClientStreamingContext 181 : public RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets> { 182 using Base = RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets>; 183 184 public: 185 template <typename... Args> ClientStreamingContext(Args &&...args)186 ClientStreamingContext(Args&&... args) : Base(std::forward<Args>(args)...) {} 187 188 // Invokes the RPC. call()189 void call() { Base::template call<kMethod, RawServerReader>(); } 190 191 // Returns a reader/writer which writes responses into the context's buffer. 192 // This should not be called alongside call(); use one or the other. reader()193 RawServerReader reader() { 194 return Base::template GetResponder<RawServerReader>(); 195 } 196 197 // Allow sending client streaming packets. 198 using Base::SendClientStream; 199 using Base::SendClientStreamEnd; 200 }; 201 202 // Method invocation context for a bidirectional streaming RPC. 203 template <typename Service, 204 auto kMethod, 205 uint32_t kMethodId, 206 size_t kMaxPackets> 207 class BidirectionalStreamingContext 208 : public RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets> { 209 using Base = RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets>; 210 211 public: 212 template <typename... Args> BidirectionalStreamingContext(Args &&...args)213 BidirectionalStreamingContext(Args&&... args) 214 : Base(std::forward<Args>(args)...) {} 215 216 // Invokes the RPC. call()217 void call() { Base::template call<kMethod, RawServerReaderWriter>(); } 218 219 // Returns a reader/writer which writes responses into the context's buffer. 220 // This should not be called alongside call(); use one or the other. reader_writer()221 RawServerReaderWriter reader_writer() { 222 return Base::template GetResponder<RawServerReaderWriter>(); 223 } 224 225 // Allow sending client streaming packets. 226 using Base::SendClientStream; 227 using Base::SendClientStreamEnd; 228 }; 229 230 // Alias to select the type of the context object to use based on which type of 231 // RPC it is for. 232 template <typename Service, 233 auto kMethod, 234 uint32_t kMethodId, 235 size_t kMaxPackets> 236 using Context = std::tuple_element_t< 237 static_cast<size_t>(MethodTraits<decltype(kMethod)>::kType), 238 std::tuple<UnaryContext<Service, kMethod, kMethodId>, 239 ServerStreamingContext<Service, kMethod, kMethodId, kMaxPackets>, 240 ClientStreamingContext<Service, kMethod, kMethodId, kMaxPackets>, 241 BidirectionalStreamingContext<Service, 242 kMethod, 243 kMethodId, 244 kMaxPackets>>>; 245 246 } // namespace internal::test::raw 247 248 template <typename Service, 249 auto kMethod, 250 uint32_t kMethodId, 251 size_t kMaxPackets> 252 class RawTestMethodContext 253 : public internal::test::raw:: 254 Context<Service, kMethod, kMethodId, kMaxPackets> { 255 public: 256 // Forwards constructor arguments to the service class. 257 template <typename... ServiceArgs> RawTestMethodContext(ServiceArgs &&...service_args)258 RawTestMethodContext(ServiceArgs&&... service_args) 259 : internal::test::raw::Context<Service, kMethod, kMethodId, kMaxPackets>( 260 std::forward<ServiceArgs>(service_args)...) {} 261 }; 262 263 } // namespace pw::rpc 264