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, std::span(response)); 141 PW_ASSERT( 142 responder.Finish(std::span(response).first(sws.size()), sws.status()) 143 .ok()); 144 return sws; 145 } else { 146 Base::template call<kMethod, RawUnaryResponder>(request); 147 } 148 } 149 }; 150 151 // Method invocation context for a server streaming RPC. 152 template <typename Service, 153 auto kMethod, 154 uint32_t kMethodId, 155 size_t kMaxPackets> 156 class ServerStreamingContext 157 : public RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets> { 158 using Base = RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets>; 159 160 public: 161 template <typename... Args> ServerStreamingContext(Args &&...args)162 ServerStreamingContext(Args&&... args) : Base(std::forward<Args>(args)...) {} 163 164 // Invokes the RPC with the provided request. call(ConstByteSpan request)165 void call(ConstByteSpan request) { 166 Base::template call<kMethod, RawServerWriter>(request); 167 } 168 169 // Returns a server writer which writes responses into the context's buffer. 170 // This should not be called alongside call(); use one or the other. writer()171 RawServerWriter writer() { 172 return Base::template GetResponder<RawServerWriter>(); 173 } 174 }; 175 176 // Method invocation context for a client streaming RPC. 177 template <typename Service, 178 auto kMethod, 179 uint32_t kMethodId, 180 size_t kMaxPackets> 181 class ClientStreamingContext 182 : public RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets> { 183 using Base = RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets>; 184 185 public: 186 template <typename... Args> ClientStreamingContext(Args &&...args)187 ClientStreamingContext(Args&&... args) : Base(std::forward<Args>(args)...) {} 188 189 // Invokes the RPC. call()190 void call() { Base::template call<kMethod, RawServerReader>(); } 191 192 // Returns a reader/writer which writes responses into the context's buffer. 193 // This should not be called alongside call(); use one or the other. reader()194 RawServerReader reader() { 195 return Base::template GetResponder<RawServerReader>(); 196 } 197 198 // Allow sending client streaming packets. 199 using Base::SendClientStream; 200 using Base::SendClientStreamEnd; 201 }; 202 203 // Method invocation context for a bidirectional streaming RPC. 204 template <typename Service, 205 auto kMethod, 206 uint32_t kMethodId, 207 size_t kMaxPackets> 208 class BidirectionalStreamingContext 209 : public RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets> { 210 using Base = RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets>; 211 212 public: 213 template <typename... Args> BidirectionalStreamingContext(Args &&...args)214 BidirectionalStreamingContext(Args&&... args) 215 : Base(std::forward<Args>(args)...) {} 216 217 // Invokes the RPC. call()218 void call() { Base::template call<kMethod, RawServerReaderWriter>(); } 219 220 // Returns a reader/writer which writes responses into the context's buffer. 221 // This should not be called alongside call(); use one or the other. reader_writer()222 RawServerReaderWriter reader_writer() { 223 return Base::template GetResponder<RawServerReaderWriter>(); 224 } 225 226 // Allow sending client streaming packets. 227 using Base::SendClientStream; 228 using Base::SendClientStreamEnd; 229 }; 230 231 // Alias to select the type of the context object to use based on which type of 232 // RPC it is for. 233 template <typename Service, 234 auto kMethod, 235 uint32_t kMethodId, 236 size_t kMaxPackets> 237 using Context = std::tuple_element_t< 238 static_cast<size_t>(MethodTraits<decltype(kMethod)>::kType), 239 std::tuple<UnaryContext<Service, kMethod, kMethodId>, 240 ServerStreamingContext<Service, kMethod, kMethodId, kMaxPackets>, 241 ClientStreamingContext<Service, kMethod, kMethodId, kMaxPackets>, 242 BidirectionalStreamingContext<Service, 243 kMethod, 244 kMethodId, 245 kMaxPackets>>>; 246 247 } // namespace internal::test::raw 248 249 template <typename Service, 250 auto kMethod, 251 uint32_t kMethodId, 252 size_t kMaxPackets> 253 class RawTestMethodContext 254 : public internal::test::raw:: 255 Context<Service, kMethod, kMethodId, kMaxPackets> { 256 public: 257 // Forwards constructor arguments to the service class. 258 template <typename... ServiceArgs> RawTestMethodContext(ServiceArgs &&...service_args)259 RawTestMethodContext(ServiceArgs&&... service_args) 260 : internal::test::raw::Context<Service, kMethod, kMethodId, kMaxPackets>( 261 std::forward<ServiceArgs>(service_args)...) {} 262 }; 263 264 } // namespace pw::rpc 265