aboutsummaryrefslogtreecommitdiff
path: root/pw_rpc/public/pw_rpc/server.h
blob: 2369c46a1a57b8aa8cd19b1fd29974b66e744fab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// 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 <cstddef>
#include <tuple>

#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<Channel> 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 <typename... OtherServices>
  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 <typename... OtherServices>
  void UnregisterService(Service& service, OtherServices&... services)
      PW_LOCKS_EXCLUDED(internal::rpc_lock()) {
    internal::rpc_lock().lock();
    UnregisterServiceLocked(service, static_cast<Service&>(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 <typename, typename>
  friend class NanopbServerReaderWriter;
  template <typename>
  friend class NanopbServerWriter;
  template <typename, typename>
  friend class NanopbServerReader;
  template <typename>
  friend class NanopbUnaryResponder;

  template <typename, typename>
  friend class PwpbServerReaderWriter;
  template <typename>
  friend class PwpbServerWriter;
  template <typename, typename>
  friend class PwpbServerReader;
  template <typename>
  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 <typename CallType,
            auto kMethod,
            MethodType kExpected,
            typename ServiceImpl,
            typename MethodImpl>
  [[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<kMethod>;
    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<Service*, const internal::Method*> FindMethod(
      const internal::Packet& packet)
      PW_EXCLUSIVE_LOCKS_REQUIRED(internal::rpc_lock());

  void HandleCompletionRequest(const internal::Packet& packet,
                               internal::Channel& channel,
                               IntrusiveList<internal::Call>::iterator call)
      const PW_UNLOCK_FUNCTION(internal::rpc_lock());

  void HandleClientStreamPacket(const internal::Packet& packet,
                                internal::Channel& channel,
                                IntrusiveList<internal::Call>::iterator call)
      const PW_UNLOCK_FUNCTION(internal::rpc_lock());

  template <typename... OtherServices>
  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<Service> services_ PW_GUARDED_BY(internal::rpc_lock());
};

}  // namespace pw::rpc