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 <algorithm> 17 #include <cstddef> 18 #include <cstdint> 19 #include <span> 20 #include <type_traits> 21 22 #include "pw_rpc/internal/base_server_writer.h" 23 #include "pw_rpc/internal/config.h" 24 #include "pw_rpc/internal/method.h" 25 #include "pw_rpc/internal/method_type.h" 26 #include "pw_rpc/internal/nanopb_common.h" 27 #include "pw_rpc/server_context.h" 28 #include "pw_status/status.h" 29 #include "pw_status/status_with_size.h" 30 31 namespace pw::rpc { 32 33 // Define the Nanopb version of the the ServerWriter class. 34 template <typename T> 35 class ServerWriter : public internal::BaseServerWriter { 36 public: 37 // Allow default construction so that users can declare a variable into which 38 // to move ServerWriters from RPC calls. 39 constexpr ServerWriter() = default; 40 41 ServerWriter(ServerWriter&&) = default; 42 ServerWriter& operator=(ServerWriter&&) = default; 43 44 // Writes a response struct. Returns the following Status codes: 45 // 46 // OK - the response was successfully sent 47 // FAILED_PRECONDITION - the writer is closed 48 // INTERNAL - pw_rpc was unable to encode the Nanopb protobuf 49 // other errors - the ChannelOutput failed to send the packet; the error 50 // codes are determined by the ChannelOutput implementation 51 // 52 Status Write(const T& response); 53 }; 54 55 namespace internal { 56 57 class NanopbMethod; 58 class Packet; 59 60 // MethodTraits specialization for a static unary method. 61 template <typename RequestType, typename ResponseType> 62 struct MethodTraits<Status (*)( 63 ServerContext&, const RequestType&, ResponseType&)> { 64 using Implementation = NanopbMethod; 65 using Request = RequestType; 66 using Response = ResponseType; 67 68 static constexpr MethodType kType = MethodType::kUnary; 69 static constexpr bool kServerStreaming = false; 70 static constexpr bool kClientStreaming = false; 71 }; 72 73 // MethodTraits specialization for a unary method. 74 template <typename T, typename RequestType, typename ResponseType> 75 struct MethodTraits<Status (T::*)( 76 ServerContext&, const RequestType&, ResponseType&)> 77 : public MethodTraits<Status (*)( 78 ServerContext&, const RequestType&, ResponseType&)> { 79 using Service = T; 80 }; 81 82 // MethodTraits specialization for a static server streaming method. 83 template <typename RequestType, typename ResponseType> 84 struct MethodTraits<void (*)( 85 ServerContext&, const RequestType&, ServerWriter<ResponseType>&)> { 86 using Implementation = NanopbMethod; 87 using Request = RequestType; 88 using Response = ResponseType; 89 90 static constexpr MethodType kType = MethodType::kServerStreaming; 91 static constexpr bool kServerStreaming = true; 92 static constexpr bool kClientStreaming = false; 93 }; 94 95 // MethodTraits specialization for a server streaming method. 96 template <typename T, typename RequestType, typename ResponseType> 97 struct MethodTraits<void (T::*)( 98 ServerContext&, const RequestType&, ServerWriter<ResponseType>&)> 99 : public MethodTraits<void (*)( 100 ServerContext&, const RequestType&, ServerWriter<ResponseType>&)> { 101 using Service = T; 102 }; 103 104 template <auto method> 105 using Request = typename MethodTraits<decltype(method)>::Request; 106 107 template <auto method> 108 using Response = typename MethodTraits<decltype(method)>::Response; 109 110 // The NanopbMethod class invokes user-defined service methods. When a 111 // pw::rpc::Server receives an RPC request packet, it looks up the matching 112 // NanopbMethod instance and calls its Invoke method, which eventually calls 113 // into the user-defined RPC function. 114 // 115 // A NanopbMethod instance is created for each user-defined RPC in the pw_rpc 116 // generated code. The NanopbMethod stores a pointer to the RPC function, a 117 // pointer to an "invoker" function that calls that function, and pointers to 118 // the Nanopb descriptors used to encode and decode request and response 119 // structs. 120 class NanopbMethod : public Method { 121 public: 122 template <auto method, typename RequestType, typename ResponseType> 123 static constexpr bool matches() { 124 return std::is_same_v<MethodImplementation<method>, NanopbMethod> && 125 std::is_same_v<RequestType, Request<method>> && 126 std::is_same_v<ResponseType, Response<method>>; 127 } 128 129 // Creates a NanopbMethod for a unary RPC. 130 template <auto method> 131 static constexpr NanopbMethod Unary(uint32_t id, 132 NanopbMessageDescriptor request, 133 NanopbMessageDescriptor response) { 134 // Define a wrapper around the user-defined function that takes the 135 // request and response protobuf structs as void*. This wrapper is stored 136 // generically in the Function union, defined below. 137 // 138 // In optimized builds, the compiler inlines the user-defined function into 139 // this wrapper, elminating any overhead. 140 constexpr UnaryFunction wrapper = 141 [](ServerCall& call, const void* req, void* resp) { 142 return CallMethodImplFunction<method>( 143 call, 144 *static_cast<const Request<method>*>(req), 145 *static_cast<Response<method>*>(resp)); 146 }; 147 return NanopbMethod(id, 148 UnaryInvoker<AllocateSpaceFor<Request<method>>(), 149 AllocateSpaceFor<Response<method>>()>, 150 Function{.unary = wrapper}, 151 request, 152 response); 153 } 154 155 // Creates a NanopbMethod for a server-streaming RPC. 156 template <auto method> 157 static constexpr NanopbMethod ServerStreaming( 158 uint32_t id, 159 NanopbMessageDescriptor request, 160 NanopbMessageDescriptor response) { 161 // Define a wrapper around the user-defined function that takes the request 162 // struct as void* and a BaseServerWriter instead of the templated 163 // ServerWriter class. This wrapper is stored generically in the Function 164 // union, defined below. 165 constexpr ServerStreamingFunction wrapper = 166 [](ServerCall& call, const void* req, BaseServerWriter& writer) { 167 return CallMethodImplFunction<method>( 168 call, 169 *static_cast<const Request<method>*>(req), 170 static_cast<ServerWriter<Response<method>>&>(writer)); 171 }; 172 return NanopbMethod( 173 id, 174 ServerStreamingInvoker<AllocateSpaceFor<Request<method>>()>, 175 Function{.server_streaming = wrapper}, 176 request, 177 response); 178 } 179 180 // Represents an invalid method. Used to reduce error message verbosity. 181 static constexpr NanopbMethod Invalid() { 182 return {0, InvalidInvoker, {}, nullptr, nullptr}; 183 } 184 185 // Encodes a response protobuf with Nanopb to the provided buffer. 186 StatusWithSize EncodeResponse(const void* proto_struct, 187 std::span<std::byte> buffer) const { 188 return serde_.EncodeResponse(buffer, proto_struct); 189 } 190 191 // Decodes a response protobuf with Nanopb to the provided buffer. For testing 192 // use. 193 bool DecodeResponse(std::span<const std::byte> response, 194 void* proto_struct) const { 195 return serde_.DecodeResponse(proto_struct, response); 196 } 197 198 private: 199 // Generic version of the unary RPC function signature: 200 // 201 // Status(ServerCall&, const Request&, Response&) 202 // 203 using UnaryFunction = Status (*)(ServerCall&, 204 const void* request, 205 void* response); 206 207 // Generic version of the server streaming RPC function signature: 208 // 209 // Status(ServerCall&, const Request&, ServerWriter<Response>&) 210 // 211 using ServerStreamingFunction = void (*)(ServerCall&, 212 const void* request, 213 BaseServerWriter& writer); 214 215 // The Function union stores a pointer to a generic version of the 216 // user-defined RPC function. Using a union instead of void* avoids 217 // reinterpret_cast, which keeps this class fully constexpr. 218 union Function { 219 UnaryFunction unary; 220 ServerStreamingFunction server_streaming; 221 // TODO(hepler): Add client_streaming and bidi_streaming 222 }; 223 224 // Allocates space for a struct. Rounds up to a reasonable minimum size to 225 // avoid generating unnecessary copies of the invoker functions. 226 template <typename T> 227 static constexpr size_t AllocateSpaceFor() { 228 return std::max(sizeof(T), cfg::kNanopbStructMinBufferSize); 229 } 230 231 constexpr NanopbMethod(uint32_t id, 232 Invoker invoker, 233 Function function, 234 NanopbMessageDescriptor request, 235 NanopbMessageDescriptor response) 236 : Method(id, invoker), function_(function), serde_(request, response) {} 237 238 void CallUnary(ServerCall& call, 239 const Packet& request, 240 void* request_struct, 241 void* response_struct) const; 242 243 void CallServerStreaming(ServerCall& call, 244 const Packet& request, 245 void* request_struct) const; 246 247 // TODO(hepler): Add CallClientStreaming and CallBidiStreaming 248 249 // Invoker function for unary RPCs. Allocates request and response structs by 250 // size, with maximum alignment, to avoid generating unnecessary copies of 251 // this function for each request/response type. 252 template <size_t kRequestSize, size_t kResponseSize> 253 static void UnaryInvoker(const Method& method, 254 ServerCall& call, 255 const Packet& request) { 256 _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS 257 std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)> 258 request_struct{}; 259 _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS 260 std::aligned_storage_t<kResponseSize, alignof(std::max_align_t)> 261 response_struct{}; 262 263 static_cast<const NanopbMethod&>(method).CallUnary( 264 call, request, &request_struct, &response_struct); 265 } 266 267 // Invoker function for server streaming RPCs. Allocates space for a request 268 // struct. Ignores the payload buffer since resposnes are sent through the 269 // ServerWriter. 270 template <size_t kRequestSize> 271 static void ServerStreamingInvoker(const Method& method, 272 ServerCall& call, 273 const Packet& request) { 274 _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS 275 std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)> 276 request_struct{}; 277 278 static_cast<const NanopbMethod&>(method).CallServerStreaming( 279 call, request, &request_struct); 280 } 281 282 // Decodes a request protobuf with Nanopb to the provided buffer. Sends an 283 // error packet if the request failed to decode. 284 bool DecodeRequest(Channel& channel, 285 const Packet& request, 286 void* proto_struct) const; 287 288 // Encodes a response and sends it over the provided channel. 289 void SendResponse(Channel& channel, 290 const Packet& request, 291 const void* response_struct, 292 Status status) const; 293 294 // Stores the user-defined RPC in a generic wrapper. 295 Function function_; 296 297 // Serde used to encode and decode Nanopb structs. 298 NanopbMethodSerde serde_; 299 }; 300 301 } // namespace internal 302 303 template <typename T> 304 Status ServerWriter<T>::Write(const T& response) { 305 if (!open()) { 306 return Status::FailedPrecondition(); 307 } 308 309 std::span<std::byte> buffer = AcquirePayloadBuffer(); 310 311 if (auto result = 312 static_cast<const internal::NanopbMethod&>(method()).EncodeResponse( 313 &response, buffer); 314 result.ok()) { 315 return ReleasePayloadBuffer(buffer.first(result.size())); 316 } 317 318 ReleasePayloadBuffer(); 319 return Status::Internal(); 320 } 321 322 } // namespace pw::rpc 323