1 // Copyright 2023 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_rpc/internal/config.h"
19 #include "pw_rpc/internal/method_info.h"
20 #include "pw_rpc/synchronous_call_result.h"
21 #include "pw_sync/timed_thread_notification.h"
22
23 #if PW_RPC_DYNAMIC_ALLOCATION
24 #include PW_RPC_MAKE_UNIQUE_PTR_INCLUDE
25 #endif // PW_RPC_DYNAMIC_ALLOCATION
26
27 namespace pw::rpc::internal {
28
29 template <typename Response>
30 struct SynchronousCallState {
OnCompletedCallbackSynchronousCallState31 auto OnCompletedCallback() {
32 return [this](const Response& response, Status status) {
33 result = SynchronousCallResult<Response>(status, response);
34 notify.release();
35 };
36 }
37
OnRpcErrorCallbackSynchronousCallState38 auto OnRpcErrorCallback() {
39 return [this](Status status) {
40 result = SynchronousCallResult<Response>::RpcError(status);
41 notify.release();
42 };
43 }
44
45 SynchronousCallResult<Response> result;
46 sync::TimedThreadNotification notify;
47 };
48
49 class RawSynchronousCallState {
50 public:
RawSynchronousCallState(Function<void (ConstByteSpan,Status)> on_completed)51 RawSynchronousCallState(Function<void(ConstByteSpan, Status)> on_completed)
52 : on_completed_(std::move(on_completed)) {}
53
OnCompletedCallback()54 auto OnCompletedCallback() {
55 return [this](ConstByteSpan response, Status status) {
56 if (on_completed_) {
57 on_completed_(response, status);
58 }
59 notify.release();
60 };
61 }
62
OnRpcErrorCallback()63 auto OnRpcErrorCallback() {
64 return [this](Status status) {
65 error = status;
66 notify.release();
67 };
68 }
69
70 Status error;
71 sync::TimedThreadNotification notify;
72
73 private:
74 Function<void(ConstByteSpan, Status)> on_completed_;
75 };
76
77 // Overloaded function to choose detween timeout and deadline APIs.
AcquireNotification(sync::TimedThreadNotification & notification,chrono::SystemClock::duration timeout)78 inline bool AcquireNotification(sync::TimedThreadNotification& notification,
79 chrono::SystemClock::duration timeout) {
80 return notification.try_acquire_for(timeout);
81 }
82
AcquireNotification(sync::TimedThreadNotification & notification,chrono::SystemClock::time_point timeout)83 inline bool AcquireNotification(sync::TimedThreadNotification& notification,
84 chrono::SystemClock::time_point timeout) {
85 return notification.try_acquire_until(timeout);
86 }
87
88 template <auto kRpcMethod,
89 typename Response = typename MethodInfo<kRpcMethod>::Response,
90 typename DoCall,
91 typename... TimeoutArg>
StructSynchronousCall(DoCall && do_call,TimeoutArg...timeout_arg)92 SynchronousCallResult<Response> StructSynchronousCall(
93 DoCall&& do_call, TimeoutArg... timeout_arg) {
94 static_assert(MethodInfo<kRpcMethod>::kType == MethodType::kUnary,
95 "Only unary methods can be used with synchronous calls");
96
97 // If dynamic allocation is enabled, heap-allocate the call_state.
98 #if PW_RPC_DYNAMIC_ALLOCATION
99 auto call_state_ptr = PW_RPC_MAKE_UNIQUE_PTR(SynchronousCallState<Response>);
100 SynchronousCallState<Response>& call_state(*call_state_ptr);
101 #else
102 SynchronousCallState<Response> call_state;
103 #endif // PW_RPC_DYNAMIC_ALLOCATION
104
105 auto call = std::forward<DoCall>(do_call)(call_state);
106
107 // Wait for the notification based on the type of the timeout argument.
108 if constexpr (sizeof...(TimeoutArg) == 0) {
109 call_state.notify.acquire(); // Wait forever, since no timeout was given.
110 } else if (!AcquireNotification(call_state.notify, timeout_arg...)) {
111 return SynchronousCallResult<Response>::Timeout();
112 }
113
114 return std::move(call_state.result);
115 }
116
117 // Template for a raw synchronous call. Used for SynchronousCall,
118 // SynchronousCallFor, and SynchronousCallUntil. The type of the timeout
119 // argument is used to determine the behavior.
120 template <auto kRpcMethod, typename DoCall, typename... TimeoutArg>
RawSynchronousCall(Function<void (ConstByteSpan,Status)> && on_completed,DoCall && do_call,TimeoutArg...timeout_arg)121 Status RawSynchronousCall(Function<void(ConstByteSpan, Status)>&& on_completed,
122 DoCall&& do_call,
123 TimeoutArg... timeout_arg) {
124 static_assert(MethodInfo<kRpcMethod>::kType == MethodType::kUnary,
125 "Only unary methods can be used with synchronous calls");
126
127 RawSynchronousCallState call_state{std::move(on_completed)};
128
129 auto call = std::forward<DoCall>(do_call)(call_state);
130
131 // Wait for the notification based on the type of the timeout argument.
132 if constexpr (sizeof...(TimeoutArg) == 0) {
133 call_state.notify.acquire(); // Wait forever, since no timeout was given.
134 } else if (!AcquireNotification(call_state.notify, timeout_arg...)) {
135 return Status::DeadlineExceeded();
136 }
137
138 return call_state.error;
139 }
140
141 // Choose which call state object to use (raw or struct).
142 template <auto kRpcMethod,
143 typename Response =
144 typename internal::MethodInfo<kRpcMethod>::Response>
145 using CallState = std::conditional_t<
146 std::is_same_v<typename MethodInfo<kRpcMethod>::Request, void>,
147 RawSynchronousCallState,
148 SynchronousCallState<Response>>;
149
150 // Invokes the RPC method free function using a call_state.
151 template <auto kRpcMethod, typename Request>
CallFreeFunction(Client & client,uint32_t channel_id,const Request & request)152 constexpr auto CallFreeFunction(Client& client,
153 uint32_t channel_id,
154 const Request& request) {
155 return [&client, channel_id, &request](CallState<kRpcMethod>& call_state) {
156 return kRpcMethod(client,
157 channel_id,
158 request,
159 call_state.OnCompletedCallback(),
160 call_state.OnRpcErrorCallback());
161 };
162 }
163
164 // Invokes the RPC method free function using a call_state and a custom
165 // response.
166 template <
167 auto kRpcMethod,
168 typename Response = typename internal::MethodInfo<kRpcMethod>::Response,
169 typename Request>
CallFreeFunctionWithCustomResponse(Client & client,uint32_t channel_id,const Request & request)170 constexpr auto CallFreeFunctionWithCustomResponse(Client& client,
171 uint32_t channel_id,
172 const Request& request) {
173 return [&client, channel_id, &request](
174 CallState<kRpcMethod, Response>& call_state) {
175 constexpr auto kMemberFunction =
176 MethodInfo<kRpcMethod>::template FunctionTemplate<
177 typename MethodInfo<kRpcMethod>::ServiceClass,
178 Response>();
179 return (*kMemberFunction)(client,
180 channel_id,
181 request,
182 call_state.OnCompletedCallback(),
183 call_state.OnRpcErrorCallback());
184 };
185 }
186
187 // Invokes the RPC function on the generated service client using a call_state.
188 template <auto kRpcMethod, typename GeneratedClient, typename Request>
CallGeneratedClient(const GeneratedClient & client,const Request & request)189 constexpr auto CallGeneratedClient(const GeneratedClient& client,
190 const Request& request) {
191 return [&client, &request](CallState<kRpcMethod>& call_state) {
192 constexpr auto kMemberFunction =
193 MethodInfo<kRpcMethod>::template Function<GeneratedClient>();
194 return (client.*kMemberFunction)(request,
195 call_state.OnCompletedCallback(),
196 call_state.OnRpcErrorCallback());
197 };
198 }
199
200 } // namespace pw::rpc::internal
201