// Copyright 2020 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #pragma once #include #include #include "pw_containers/intrusive_list.h" #include "pw_rpc/channel.h" #include "pw_rpc/internal/call.h" #include "pw_rpc/internal/channel.h" #include "pw_rpc/internal/endpoint.h" #include "pw_rpc/internal/lock.h" #include "pw_rpc/internal/method.h" #include "pw_rpc/internal/method_info.h" #include "pw_rpc/internal/server_call.h" #include "pw_rpc/service.h" #include "pw_span/span.h" #include "pw_status/status.h" namespace pw::rpc { class Server : public internal::Endpoint { public: // If dynamic allocation is supported, it is not necessary to preallocate a // channels list. #if PW_RPC_DYNAMIC_ALLOCATION _PW_RPC_CONSTEXPR Server() = default; #endif // PW_RPC_DYNAMIC_ALLOCATION // Creates a client that uses a set of RPC channels. Channels can be shared // between multiple clients and servers. _PW_RPC_CONSTEXPR Server(span channels) : Endpoint(channels) {} // Registers one or more services with the server. This should not be called // directly with a Service; instead, use a generated class which inherits // from it. // // This function may be called with any number of services. Combining // registration into fewer calls is preferred so the RPC mutex is only // locked/unlocked once. template void RegisterService(Service& service, OtherServices&... services) PW_LOCKS_EXCLUDED(internal::rpc_lock()) { internal::RpcLockGuard lock; services_.push_front(service); // Register the first service // Register any additional services by expanding the parameter pack. This // is a fold expression of the comma operator. (services_.push_front(services), ...); } // Returns whether a service is registered. // // Calling RegisterService with a registered service will assert. So depending // on your logic you might want to check if a service is currently registered. bool IsServiceRegistered(const Service& service) const PW_LOCKS_EXCLUDED(internal::rpc_lock()) { internal::RpcLockGuard lock; for (const Service& svc : services_) { if (&svc == &service) { return true; } } return false; } template void UnregisterService(Service& service, OtherServices&... services) PW_LOCKS_EXCLUDED(internal::rpc_lock()) { internal::rpc_lock().lock(); UnregisterServiceLocked(service, static_cast(services)...); CleanUpCalls(); } // Processes an RPC packet. The packet may contain an RPC request or a control // packet, the result of which is processed in this function. Returns whether // the packet was able to be processed: // // OK - The packet was processed by the server. // DATA_LOSS - Failed to decode the packet. // INVALID_ARGUMENT - The packet is intended for a client, not a server. // UNAVAILABLE - No RPC channel with the requested ID was found. Status ProcessPacket(ConstByteSpan packet_data) PW_LOCKS_EXCLUDED(internal::rpc_lock()); private: friend class internal::Call; // Give call classes access to OpenCall. friend class RawServerReaderWriter; friend class RawServerWriter; friend class RawServerReader; friend class RawUnaryResponder; template friend class NanopbServerReaderWriter; template friend class NanopbServerWriter; template friend class NanopbServerReader; template friend class NanopbUnaryResponder; template friend class PwpbServerReaderWriter; template friend class PwpbServerWriter; template friend class PwpbServerReader; template friend class PwpbUnaryResponder; // Opens a call object for an unrequested RPC. Calls created with OpenCall // use a special call ID and will adopt the call ID from the first packet for // their channel, service, and method. Only one call object may be opened in // this fashion at a time. // // This function checks the type of RPC at compile time. template [[nodiscard]] CallType OpenCall(uint32_t channel_id, ServiceImpl& service, const MethodImpl& method) PW_LOCKS_EXCLUDED(internal::rpc_lock()) { internal::rpc_lock().lock(); using Info = internal::MethodInfo; if constexpr (kExpected == MethodType::kUnary) { static_assert( Info::kType == kExpected, "UnaryResponder objects may only be opened for unary RPCs."); } else if constexpr (kExpected == MethodType::kServerStreaming) { static_assert( Info::kType == kExpected, "ServerWriters may only be opened for server streaming RPCs."); } else if constexpr (kExpected == MethodType::kClientStreaming) { static_assert( Info::kType == kExpected, "ServerReaders may only be opened for client streaming RPCs."); } else if constexpr (kExpected == MethodType::kBidirectionalStreaming) { static_assert(Info::kType == kExpected, "ServerReaderWriters may only be opened for bidirectional " "streaming RPCs."); } CallType call(internal::CallContext( *this, channel_id, service, method, internal::kOpenCallId) .ClaimLocked()); CleanUpCalls(); return call; } std::tuple FindMethod( const internal::Packet& packet) PW_EXCLUSIVE_LOCKS_REQUIRED(internal::rpc_lock()); void HandleCompletionRequest(const internal::Packet& packet, internal::Channel& channel, IntrusiveList::iterator call) const PW_UNLOCK_FUNCTION(internal::rpc_lock()); void HandleClientStreamPacket(const internal::Packet& packet, internal::Channel& channel, IntrusiveList::iterator call) const PW_UNLOCK_FUNCTION(internal::rpc_lock()); template void UnregisterServiceLocked(Service& service, OtherServices&... services) PW_EXCLUSIVE_LOCKS_REQUIRED(internal::rpc_lock()) { services_.remove(service); UnregisterServiceLocked(services...); AbortCallsForService(service); } void UnregisterServiceLocked() {} // Base case; nothing left to do. // Remove these internal::Endpoint functions from the public interface. using Endpoint::active_call_count; using Endpoint::ClaimLocked; using Endpoint::CleanUpCalls; using Endpoint::GetInternalChannel; IntrusiveList services_ PW_GUARDED_BY(internal::rpc_lock()); }; } // namespace pw::rpc