blob: ec02cac951a21b88879975972891240fc1f86c00 [file] [log] [blame]
// Copyright 2023 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 <utility>
#include "pw_chrono/system_clock.h"
#include "pw_rpc/client.h"
#include "pw_rpc/internal/method_info.h"
#include "pw_rpc/internal/synchronous_call_impl.h"
#include "pw_rpc/synchronous_call_result.h"
/// @file pw_rpc/synchronous_call.h
///
/// `pw_rpc` provides wrappers that convert the asynchronous client API to a
/// synchronous API. The `SynchronousCall<RpcMethod>` functions wrap the
/// asynchronous client RPC call with a timed thread notification and returns
/// once a result is known or a timeout has occurred. Only unary methods are
/// supported.
///
/// The Nanopb and pwpb APIs return a `SynchronousCallResult<Response>` object,
/// which can be queried to determine whether any error scenarios occurred and,
/// if not, access the response. The raw API executes a function when the call
/// completes or returns a `pw::Status` if it does not.
///
/// `SynchronousCall<RpcMethod>` blocks indefinitely, whereas
/// `SynchronousCallFor<RpcMethod>` and `SynchronousCallUntil<RpcMethod>` block
/// for a given timeout or until a deadline, respectively. All wrappers work
/// with either the standalone static RPC functions or the generated service
/// client member methods.
///
/// @note Use of the SynchronousCall wrappers requires a
/// @cpp_class{pw::sync::TimedThreadNotification} backend.
///
/// The following examples use the Nanopb API to make a call that blocks
/// indefinitely. If you'd like to include a timeout for how long the call
/// should block for, use the `SynchronousCallFor()` or `SynchronousCallUntil()`
/// variants.
///
/// @code{.cpp}
/// pw_rpc_EchoMessage request{.msg = "hello" };
/// pw::rpc::SynchronousCallResult<pw_rpc_EchoMessage> result =
/// pw::rpc::SynchronousCall<EchoService::Echo>(rpc_client,
/// channel_id,
/// request);
/// if (result.ok()) {
/// PW_LOG_INFO("%s", result.response().msg);
/// }
/// @endcode
///
/// Additionally, the use of a generated `Client` object is supported:
///
/// @code{.cpp}
/// pw_rpc::nanopb::EchoService::Client client(rpc_client, channel_id);
/// pw_rpc_EchoMessage request{.msg = "hello" };
/// pw::rpc::SynchronousCallResult<pw_rpc_EchoMessage> result =
/// pw::rpc::SynchronousCall<EchoService::Echo>(client, request);
///
/// if (result.ok()) {
/// PW_LOG_INFO("%s", result.response().msg);
/// }
/// @endcode
///
/// `SynchronousCall<RpcMethod>` also supports using an optional custom response
/// message class, `SynchronousCall<RpcMethod, Response>`. This enables the use
/// of response messages with variable-length fields.
///
/// @code{.cpp}
/// pw_rpc_MyMethodRequestMessage request{};
/// class CustomResponse : public pw_rpc_MyMethodResponseMessage {
/// public:
/// CustomResponse() {
/// repeated_field.SetDecoder([this](
/// MyMethodResponse::StreamDecoder& decoder) {
/// return decoder.ReadRepeatedField(values);
/// }
/// }
/// pw::Vector<uint32_t, 4> values();
/// };
/// pw::rpc::SynchronousCallResult<CustomResponse> result =
/// pw::rpc::SynchronousCall<EchoService::Echo, CustomResponse>(rpc_client,
/// channel_id,
/// request);
/// if (result.ok()) {
/// PW_LOG_INFO("%d", result.response().values[0]);
/// }
/// };
/// @endcode
///
/// The raw API works similarly to the Nanopb API, but takes a
/// @cpp_type{pw::Function} and returns a @cpp_class{pw::Status}. If the RPC
/// completes, the @cpp_type{pw::Function} is called with the response and
/// returned status, and the `SynchronousCall` invocation returns
/// @pw_status{OK}. If the RPC fails, `SynchronousCall` returns an error.
///
/// @code{.cpp}
/// pw::Status rpc_status = pw::rpc::SynchronousCall<EchoService::Echo>(
/// rpc_client, channel_id, encoded_request,
/// [](pw::ConstByteSpan reply, pw::Status status) {
/// PW_LOG_INFO("Received %zu bytes with status %s",
/// reply.size(),
/// status.str());
/// });
/// @endcode
///
/// @warning These wrappers should not be used from any context that cannot be
/// blocked! This method will block the calling thread until the RPC completes,
/// and translate the response into a `pw::rpc::SynchronousCallResult` that
/// contains the error type and status or the proto response.
namespace pw::rpc {
/// Invokes a unary RPC synchronously using Nanopb or pwpb. Blocks indefinitely
/// until a response is received.
///
/// @param client The `pw::rpc::Client` to use for the call
/// @param channel_id The ID of the RPC channel to make the call on
/// @param request The proto struct to send as the request
template <
auto kRpcMethod,
typename Response = typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallResult<Response> SynchronousCall(
Client& client,
uint32_t channel_id,
const typename internal::MethodInfo<kRpcMethod>::Request& request) {
return internal::StructSynchronousCall<kRpcMethod, Response>(
internal::CallFreeFunctionWithCustomResponse<kRpcMethod, Response>(
client, channel_id, request));
}
/// Invokes a unary RPC synchronously using Nanopb or pwpb. Blocks indefinitely
/// until a response is received.
///
/// @param client The generated service client to use for the call
/// @param request The proto struct to send as the request
template <auto kRpcMethod, typename GeneratedClient>
SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCall(
const GeneratedClient& client,
const typename internal::MethodInfo<kRpcMethod>::Request& request) {
return internal::StructSynchronousCall<kRpcMethod>(
internal::CallGeneratedClient<kRpcMethod>(client, request));
}
/// Invokes a unary RPC synchronously using the raw API. Blocks until a
/// response is received.
template <auto kRpcMethod>
Status SynchronousCall(Client& client,
uint32_t channel_id,
ConstByteSpan request,
Function<void(ConstByteSpan, Status)>&& on_completed) {
return internal::RawSynchronousCall<kRpcMethod>(
std::move(on_completed),
internal::CallFreeFunction<kRpcMethod>(client, channel_id, request));
}
/// Invokes a unary RPC synchronously using the raw API. Blocks until a
/// response is received.
template <auto kRpcMethod>
Status SynchronousCall(
const typename internal::MethodInfo<kRpcMethod>::GeneratedClient& client,
ConstByteSpan request,
Function<void(ConstByteSpan, Status)>&& on_completed) {
return internal::RawSynchronousCall<kRpcMethod>(
std::move(on_completed),
internal::CallGeneratedClient<kRpcMethod>(client, request));
}
/// Invokes a unary RPC synchronously using Nanopb or pwpb. Blocks until a
/// response is received or the provided timeout passes.
///
/// @param client The `pw::rpc::Client` to use for the call
/// @param channel_id The ID of the RPC channel to make the call on
/// @param request The proto struct to send as the request
/// @param timeout Duration to block for before returning with Timeout
template <auto kRpcMethod>
SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallFor(
Client& client,
uint32_t channel_id,
const typename internal::MethodInfo<kRpcMethod>::Request& request,
chrono::SystemClock::duration timeout) {
return internal::StructSynchronousCall<kRpcMethod>(
internal::CallFreeFunction<kRpcMethod>(client, channel_id, request),
timeout);
}
/// Invokes a unary RPC synchronously using Nanopb or pwpb. Blocks until a
/// response is received or the provided timeout passes.
///
/// @param client The generated service client to use for the call
/// @param request The proto struct to send as the request
/// @param timeout Duration to block for before returning with Timeout
template <auto kRpcMethod, typename GeneratedClient>
SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallFor(
const GeneratedClient& client,
const typename internal::MethodInfo<kRpcMethod>::Request& request,
chrono::SystemClock::duration timeout) {
return internal::StructSynchronousCall<kRpcMethod>(
internal::CallGeneratedClient<kRpcMethod>(client, request), timeout);
}
/// Invokes a unary RPC synchronously using the raw API. Blocks until a
/// response is received or the provided timeout passes.
template <auto kRpcMethod>
Status SynchronousCallFor(
Client& client,
uint32_t channel_id,
ConstByteSpan request,
chrono::SystemClock::duration timeout,
Function<void(ConstByteSpan, Status)>&& on_completed) {
return internal::RawSynchronousCall<kRpcMethod>(
std::move(on_completed),
internal::CallFreeFunction<kRpcMethod>(client, channel_id, request),
timeout);
}
/// Invokes a unary RPC synchronously using the raw API. Blocks until a
/// response is received or the provided timeout passes.
template <auto kRpcMethod>
Status SynchronousCallFor(
const typename internal::MethodInfo<kRpcMethod>::GeneratedClient& client,
ConstByteSpan request,
chrono::SystemClock::duration timeout,
Function<void(ConstByteSpan, Status)>&& on_completed) {
return internal::RawSynchronousCall<kRpcMethod>(
std::move(on_completed),
internal::CallGeneratedClient<kRpcMethod>(client, request),
timeout);
}
/// Invokes a unary RPC synchronously using Nanopb or pwpb. Blocks until a
/// response is received or the provided deadline arrives.
///
/// @param client The `pw::rpc::Client` to use for the call
/// @param channel_id The ID of the RPC channel to make the call on
/// @param request The proto struct to send as the request
/// @param deadline Timepoint to block until before returning with Timeout
template <auto kRpcMethod>
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) {
return internal::StructSynchronousCall<kRpcMethod>(
internal::CallFreeFunction<kRpcMethod>(client, channel_id, request),
deadline);
}
/// Invokes a unary RPC synchronously using Nanopb or pwpb. Blocks until a
/// response is received or the provided deadline arrives.
///
/// @param client The generated service client to use for the call
/// @param request The proto struct to send as the request
/// @param deadline Timepoint to block until before returning with Timeout
template <auto kRpcMethod>
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) {
return internal::StructSynchronousCall<kRpcMethod>(
internal::CallGeneratedClient<kRpcMethod>(client, request), deadline);
}
/// Invokes a unary RPC synchronously using the raw API. Blocks until a
/// response is received or the provided deadline arrives.
template <auto kRpcMethod>
Status SynchronousCallUntil(
Client& client,
uint32_t channel_id,
ConstByteSpan request,
chrono::SystemClock::time_point deadline,
Function<void(ConstByteSpan, Status)>&& on_completed) {
return internal::RawSynchronousCall<kRpcMethod>(
std::move(on_completed),
internal::CallFreeFunction<kRpcMethod>(client, channel_id, request),
deadline);
}
/// Invokes a unary RPC synchronously using the raw API. Blocks until a
/// response is received or the provided deadline arrives.
template <auto kRpcMethod>
Status SynchronousCallUntil(
const typename internal::MethodInfo<kRpcMethod>::GeneratedClient& client,
ConstByteSpan request,
chrono::SystemClock::time_point deadline,
Function<void(ConstByteSpan, Status)>&& on_completed) {
return internal::RawSynchronousCall<kRpcMethod>(
std::move(on_completed),
internal::CallGeneratedClient<kRpcMethod>(client, request),
deadline);
}
} // namespace pw::rpc