1 // Copyright 2022 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 <utility>
17
18 #include "pw_chrono/system_clock.h"
19 #include "pw_rpc/client.h"
20 #include "pw_rpc/internal/method_info.h"
21 #include "pw_rpc/synchronous_call_result.h"
22 #include "pw_sync/timed_thread_notification.h"
23
24 // Synchronous Call wrappers
25 //
26 // Wraps an asynchronous RPC client call, converting it to a synchronous
27 // interface.
28 //
29 // WARNING! This should not be called from any context that cannot be blocked!
30 // This method will block the calling thread until the RPC completes, and
31 // translate the response into a pw::rpc::SynchronousCallResult that contains
32 // the error type and status or the proto response.
33 //
34 // Example:
35 //
36 // pw_rpc_EchoMessage request{.msg = "hello" };
37 // pw::rpc::SynchronousCallResult<pw_rpc_EchoMessage> result =
38 // pw::rpc::SynchronousCall<EchoService::Echo>(rpc_client,
39 // channel_id,
40 // request);
41 // if (result.ok()) {
42 // printf("%s", result.response().msg);
43 // }
44 //
45 // Note: The above example will block indefinitely. If you'd like to include a
46 // timeout for how long the call should block for, use the
47 // `SynchronousCallFor()` or `SynchronousCallUntil()` variants.
48 //
49 // Additionally, the use of a generated Client object is supported:
50 //
51 // pw_rpc::nanopb::EchoService::client client;
52 // pw_rpc_EchoMessage request{.msg = "hello" };
53 // pw::rpc::SynchronousCallResult<pw_rpc_EchoMessage> result =
54 // pw::rpc::SynchronousCall<EchoService::Echo>(client, request);
55 //
56 // if (result.ok()) {
57 // printf("%s", result.response().msg);
58 // }
59
60 namespace pw::rpc {
61 namespace internal {
62
63 template <typename Response>
64 struct SynchronousCallState {
OnCompletedCallbackSynchronousCallState65 auto OnCompletedCallback() {
66 return [this](const Response& response, Status status) {
67 result = SynchronousCallResult<Response>(status, response);
68 notify.release();
69 };
70 }
71
OnRpcErrorCallbackSynchronousCallState72 auto OnRpcErrorCallback() {
73 return [this](Status status) {
74 result = SynchronousCallResult<Response>::RpcError(status);
75 notify.release();
76 };
77 }
78
79 SynchronousCallResult<Response> result;
80 sync::TimedThreadNotification notify;
81 };
82
83 } // namespace internal
84
85 // SynchronousCall
86 //
87 // Template arguments:
88 // kRpcMethod: The RPC Method to invoke
89 //
90 // Arguments:
91 // client: The pw::rpc::Client to use for the call
92 // channel_id: The ID of the RPC channel to make the call on
93 // request: The proto struct to send as the request
94 template <auto kRpcMethod>
95 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCall(Client & client,uint32_t channel_id,const typename internal::MethodInfo<kRpcMethod>::Request & request)96 SynchronousCall(
97 Client& client,
98 uint32_t channel_id,
99 const typename internal::MethodInfo<kRpcMethod>::Request& request) {
100 using Info = internal::MethodInfo<kRpcMethod>;
101 using Response = typename Info::Response;
102 static_assert(Info::kType == MethodType::kUnary,
103 "Only unary methods can be used with synchronous calls");
104
105 internal::SynchronousCallState<Response> call_state;
106
107 auto call = kRpcMethod(client,
108 channel_id,
109 request,
110 call_state.OnCompletedCallback(),
111 call_state.OnRpcErrorCallback());
112
113 call_state.notify.acquire();
114
115 return std::move(call_state.result);
116 }
117
118 // SynchronousCall
119 //
120 // Template arguments:
121 // kRpcMethod: The RPC Method to invoke
122 //
123 // Arguments:
124 // client: The service Client to use for the call
125 // request: The proto struct to send as the request
126 template <auto kRpcMethod>
127 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCall(const typename internal::MethodInfo<kRpcMethod>::GeneratedClient & client,const typename internal::MethodInfo<kRpcMethod>::Request & request)128 SynchronousCall(
129 const typename internal::MethodInfo<kRpcMethod>::GeneratedClient& client,
130 const typename internal::MethodInfo<kRpcMethod>::Request& request) {
131 using Info = internal::MethodInfo<kRpcMethod>;
132 using Response = typename Info::Response;
133 static_assert(Info::kType == MethodType::kUnary,
134 "Only unary methods can be used with synchronous calls");
135
136 constexpr auto Function =
137 Info::template Function<typename Info::GeneratedClient>();
138
139 internal::SynchronousCallState<Response> call_state;
140
141 auto call = (client.*Function)(request,
142 call_state.OnCompletedCallback(),
143 call_state.OnRpcErrorCallback());
144
145 call_state.notify.acquire();
146
147 return std::move(call_state.result);
148 }
149
150 // SynchronousCallFor
151 //
152 // Template arguments:
153 // kRpcMethod: The RPC Method to invoke
154 //
155 // Arguments:
156 // client: The pw::rpc::Client to use for the call
157 // channel_id: The ID of the RPC channel to make the call on
158 // request: The proto struct to send as the request
159 // timeout: Duration to block for before returning with Timeout
160 template <auto kRpcMethod>
161 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallFor(Client & client,uint32_t channel_id,const typename internal::MethodInfo<kRpcMethod>::Request & request,chrono::SystemClock::duration timeout)162 SynchronousCallFor(
163 Client& client,
164 uint32_t channel_id,
165 const typename internal::MethodInfo<kRpcMethod>::Request& request,
166 chrono::SystemClock::duration timeout) {
167 using Info = internal::MethodInfo<kRpcMethod>;
168 using Response = typename Info::Response;
169 static_assert(Info::kType == MethodType::kUnary,
170 "Only unary methods can be used with synchronous calls");
171
172 internal::SynchronousCallState<Response> call_state;
173
174 auto call = kRpcMethod(client,
175 channel_id,
176 request,
177 call_state.OnCompletedCallback(),
178 call_state.OnRpcErrorCallback());
179
180 if (!call_state.notify.try_acquire_for(timeout)) {
181 return SynchronousCallResult<Response>::Timeout();
182 }
183
184 return std::move(call_state.result);
185 }
186
187 // SynchronousCallFor
188 //
189 // Template arguments:
190 // kRpcMethod: The RPC Method to invoke
191 //
192 // Arguments:
193 // client: The service Client to use for the call
194 // request: The proto struct to send as the request
195 // timeout: Duration to block for before returning with Timeout
196 template <auto kRpcMethod>
197 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallFor(const typename internal::MethodInfo<kRpcMethod>::GeneratedClient & client,const typename internal::MethodInfo<kRpcMethod>::Request & request,chrono::SystemClock::duration timeout)198 SynchronousCallFor(
199 const typename internal::MethodInfo<kRpcMethod>::GeneratedClient& client,
200 const typename internal::MethodInfo<kRpcMethod>::Request& request,
201 chrono::SystemClock::duration timeout) {
202 using Info = internal::MethodInfo<kRpcMethod>;
203 using Response = typename Info::Response;
204 static_assert(Info::kType == MethodType::kUnary,
205 "Only unary methods can be used with synchronous calls");
206
207 constexpr auto Function =
208 Info::template Function<typename Info::GeneratedClient>();
209
210 internal::SynchronousCallState<Response> call_state;
211
212 auto call = (client.*Function)(request,
213 call_state.OnCompletedCallback(),
214 call_state.OnRpcErrorCallback());
215
216 if (!call_state.notify.try_acquire_for(timeout)) {
217 return SynchronousCallResult<Response>::Timeout();
218 }
219
220 return std::move(call_state.result);
221 }
222
223 // SynchronousCallUntil
224 //
225 // Template arguments:
226 // kRpcMethod: The RPC Method to invoke
227 //
228 // Arguments:
229 // client: The pw::rpc::Client to use for the call
230 // channel_id: The ID of the RPC channel to make the call on
231 // request: The proto struct to send as the request
232 // deadline: Timepoint to block until before returning with Timeout
233 template <auto kRpcMethod>
234 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallUntil(Client & client,uint32_t channel_id,const typename internal::MethodInfo<kRpcMethod>::Request & request,chrono::SystemClock::time_point deadline)235 SynchronousCallUntil(
236 Client& client,
237 uint32_t channel_id,
238 const typename internal::MethodInfo<kRpcMethod>::Request& request,
239 chrono::SystemClock::time_point deadline) {
240 using Info = internal::MethodInfo<kRpcMethod>;
241 using Response = typename Info::Response;
242 static_assert(Info::kType == MethodType::kUnary,
243 "Only unary methods can be used with synchronous calls");
244
245 internal::SynchronousCallState<Response> call_state;
246
247 auto call = kRpcMethod(client,
248 channel_id,
249 request,
250 call_state.OnCompletedCallback(),
251 call_state.OnRpcErrorCallback());
252
253 if (!call_state.notify.try_acquire_until(deadline)) {
254 return SynchronousCallResult<Response>::Timeout();
255 }
256
257 return std::move(call_state.result);
258 }
259
260 // SynchronousCallUntil
261 //
262 // Template arguments:
263 // kRpcMethod: The RPC Method to invoke
264 //
265 // Arguments:
266 // client: The service Client to use for the call
267 // request: The proto struct to send as the request
268 // deadline: Timepoint to block until before returning with Timeout
269 template <auto kRpcMethod>
270 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallUntil(const typename internal::MethodInfo<kRpcMethod>::GeneratedClient & client,const typename internal::MethodInfo<kRpcMethod>::Request & request,chrono::SystemClock::time_point deadline)271 SynchronousCallUntil(
272 const typename internal::MethodInfo<kRpcMethod>::GeneratedClient& client,
273 const typename internal::MethodInfo<kRpcMethod>::Request& request,
274 chrono::SystemClock::time_point deadline) {
275 using Info = internal::MethodInfo<kRpcMethod>;
276 using Response = typename Info::Response;
277 static_assert(Info::kType == MethodType::kUnary,
278 "Only unary methods can be used with synchronous calls");
279
280 constexpr auto Function =
281 Info::template Function<typename Info::GeneratedClient>();
282
283 internal::SynchronousCallState<Response> call_state;
284
285 auto call = (client.*Function)(request,
286 call_state.OnCompletedCallback(),
287 call_state.OnRpcErrorCallback());
288
289 if (!call_state.notify.try_acquire_until(deadline)) {
290 return SynchronousCallResult<Response>::Timeout();
291 }
292
293 return std::move(call_state.result);
294 }
295 } // namespace pw::rpc
296