blob: 2cc36bf8860fb8b1c305f565509972f26990eea4 [file] [log] [blame]
// Copyright 2021 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.
// This file defines the ServerReaderWriter, ServerReader, and ServerWriter
// classes for the Nanopb RPC interface. These classes are used for
// bidirectional, client, and server streaming RPCs.
#pragma once
#include "pw_bytes/span.h"
#include "pw_rpc/channel.h"
#include "pw_rpc/internal/call.h"
#include "pw_rpc/internal/method_lookup.h"
#include "pw_rpc/internal/open_call.h"
#include "pw_rpc/server.h"
namespace pw::rpc {
namespace internal {
// Forward declarations for internal classes needed in friend statements.
class NanopbMethod;
namespace test {
template <typename, typename, uint32_t>
class InvocationContext;
} // namespace test
// Non-templated base so the methods are instantiated only once.
class GenericNanopbResponder : public internal::Call {
public:
constexpr GenericNanopbResponder(MethodType type) : internal::Call(type) {}
GenericNanopbResponder(const CallContext& call, MethodType type)
: internal::Call(call, type) {}
Status SendResponse(const void* response, Status status) {
return SendClientStreamOrResponse(response, &status);
}
protected:
Status SendClientStream(const void* response) {
return SendClientStreamOrResponse(response, nullptr);
}
void DecodeRequest(ConstByteSpan payload, void* request_struct) const;
private:
Status SendClientStreamOrResponse(const void* response, const Status* status);
};
// The BaseNanopbServerReader serves as the base for the ServerReader and
// ServerReaderWriter classes. It adds a callback templated on the request
// struct type. It is templated on the Request type only.
template <typename Request>
class BaseNanopbServerReader : public GenericNanopbResponder {
public:
BaseNanopbServerReader(const internal::CallContext& call, MethodType type)
: GenericNanopbResponder(call, type) {}
protected:
constexpr BaseNanopbServerReader(MethodType type)
: GenericNanopbResponder(type) {}
void set_on_next(Function<void(const Request& request)> on_next) {
nanopb_on_next_ = std::move(on_next);
internal::Call::set_on_next([this](ConstByteSpan payload) {
Request request_struct;
DecodeRequest(payload, &request_struct);
nanopb_on_next_(request_struct);
});
}
private:
Function<void(const Request&)> nanopb_on_next_;
};
} // namespace internal
// The NanopbServerReaderWriter is used to send and receive messages in a Nanopb
// bidirectional streaming RPC.
template <typename Request, typename Response>
class NanopbServerReaderWriter
: private internal::BaseNanopbServerReader<Request> {
public:
// Creates a NanopbServerReaderWriter that is ready to send responses for a
// particular RPC. This can be used for testing or to send responses to an RPC
// that has not been started by a client.
template <auto kMethod, uint32_t kMethodId, typename ServiceImpl>
[[nodiscard]] static NanopbServerReaderWriter Open(Server& server,
uint32_t channel_id,
ServiceImpl& service) {
static_assert(std::is_same_v<Request, internal::Request<kMethod>>,
"The request type of a NanopbServerReaderWriter must match "
"the method.");
static_assert(std::is_same_v<Response, internal::Response<kMethod>>,
"The response type of a NanopbServerReaderWriter must match "
"the method.");
return {internal::OpenCall<kMethod, MethodType::kBidirectionalStreaming>(
server,
channel_id,
service,
internal::MethodLookup::GetNanopbMethod<ServiceImpl, kMethodId>())};
}
constexpr NanopbServerReaderWriter()
: internal::BaseNanopbServerReader<Request>(
MethodType::kBidirectionalStreaming) {}
NanopbServerReaderWriter(NanopbServerReaderWriter&&) = default;
NanopbServerReaderWriter& operator=(NanopbServerReaderWriter&&) = default;
using internal::GenericNanopbResponder::active;
using internal::GenericNanopbResponder::channel_id;
// Writes a response struct. Returns the following Status codes:
//
// OK - the response was successfully sent
// FAILED_PRECONDITION - the writer is closed
// INTERNAL - pw_rpc was unable to encode the Nanopb protobuf
// other errors - the ChannelOutput failed to send the packet; the error
// codes are determined by the ChannelOutput implementation
//
Status Write(const Response& response) {
return internal::GenericNanopbResponder::SendClientStream(&response);
}
Status Finish(Status status = OkStatus()) {
return internal::Call::CloseAndSendResponse(status);
}
// Functions for setting RPC event callbacks.
using internal::BaseNanopbServerReader<Request>::set_on_client_stream_end;
using internal::BaseNanopbServerReader<Request>::set_on_error;
using internal::BaseNanopbServerReader<Request>::set_on_next;
private:
friend class internal::NanopbMethod;
template <typename, typename, uint32_t>
friend class internal::test::InvocationContext;
NanopbServerReaderWriter(const internal::CallContext& call)
: internal::BaseNanopbServerReader<Request>(
call, MethodType::kBidirectionalStreaming) {}
};
// The NanopbServerReader is used to receive messages and send a response in a
// Nanopb client streaming RPC.
template <typename Request, typename Response>
class NanopbServerReader : private internal::BaseNanopbServerReader<Request> {
public:
// Creates a NanopbServerReader that is ready to send a response to a
// particular RPC. This can be used for testing or to finish an RPC that has
// not been started by the client.
template <auto kMethod, uint32_t kMethodId, typename ServiceImpl>
[[nodiscard]] static NanopbServerReader Open(Server& server,
uint32_t channel_id,
ServiceImpl& service) {
static_assert(
std::is_same_v<Request, internal::Request<kMethod>>,
"The request type of a NanopbServerReader must match the method.");
static_assert(
std::is_same_v<Response, internal::Response<kMethod>>,
"The response type of a NanopbServerReader must match the method.");
return {internal::OpenCall<kMethod, MethodType::kClientStreaming>(
server,
channel_id,
service,
internal::MethodLookup::GetNanopbMethod<ServiceImpl, kMethodId>())};
}
// Allow default construction so that users can declare a variable into which
// to move NanopbServerReaders from RPC calls.
constexpr NanopbServerReader()
: internal::BaseNanopbServerReader<Request>(
MethodType::kClientStreaming) {}
NanopbServerReader(NanopbServerReader&&) = default;
NanopbServerReader& operator=(NanopbServerReader&&) = default;
using internal::GenericNanopbResponder::active;
using internal::GenericNanopbResponder::channel_id;
// Functions for setting RPC event callbacks.
using internal::BaseNanopbServerReader<Request>::set_on_client_stream_end;
using internal::BaseNanopbServerReader<Request>::set_on_error;
using internal::BaseNanopbServerReader<Request>::set_on_next;
Status Finish(const Response& response, Status status = OkStatus()) {
return internal::BaseNanopbServerReader<Request>::SendResponse(&response,
status);
}
private:
friend class internal::NanopbMethod;
template <typename, typename, uint32_t>
friend class internal::test::InvocationContext;
NanopbServerReader(const internal::CallContext& call)
: internal::BaseNanopbServerReader<Request>(
call, MethodType::kClientStreaming) {}
};
// The NanopbServerWriter is used to send responses in a Nanopb server streaming
// RPC.
template <typename Response>
class NanopbServerWriter : private internal::GenericNanopbResponder {
public:
// Creates a NanopbServerWriter that is ready to send responses for a
// particular RPC. This can be used for testing or to send responses to an RPC
// that has not been started by a client.
template <auto kMethod, uint32_t kMethodId, typename ServiceImpl>
[[nodiscard]] static NanopbServerWriter Open(Server& server,
uint32_t channel_id,
ServiceImpl& service) {
static_assert(
std::is_same_v<Response, internal::Response<kMethod>>,
"The response type of a NanopbServerWriter must match the method.");
return {internal::OpenCall<kMethod, MethodType::kServerStreaming>(
server,
channel_id,
service,
internal::MethodLookup::GetNanopbMethod<ServiceImpl, kMethodId>())};
}
// Allow default construction so that users can declare a variable into which
// to move ServerWriters from RPC calls.
constexpr NanopbServerWriter()
: internal::GenericNanopbResponder(MethodType::kServerStreaming) {}
NanopbServerWriter(NanopbServerWriter&&) = default;
NanopbServerWriter& operator=(NanopbServerWriter&&) = default;
using internal::GenericNanopbResponder::active;
using internal::GenericNanopbResponder::channel_id;
using internal::GenericNanopbResponder::open;
// Writes a response struct. Returns the following Status codes:
//
// OK - the response was successfully sent
// FAILED_PRECONDITION - the writer is closed
// INTERNAL - pw_rpc was unable to encode the Nanopb protobuf
// other errors - the ChannelOutput failed to send the packet; the error
// codes are determined by the ChannelOutput implementation
//
Status Write(const Response& response) {
return internal::GenericNanopbResponder::SendClientStream(&response);
}
Status Finish(Status status = OkStatus()) {
return internal::Call::CloseAndSendResponse(status);
}
private:
friend class internal::NanopbMethod;
template <typename, typename, uint32_t>
friend class internal::test::InvocationContext;
NanopbServerWriter(const internal::CallContext& call)
: internal::GenericNanopbResponder(call, MethodType::kServerStreaming) {}
};
template <typename Response>
class NanopbServerResponder : private internal::GenericNanopbResponder {
public:
// Creates a NanopbServerResponder that is ready to send a response for a
// particular RPC. This can be used for testing or to send responses to an RPC
// that has not been started by a client.
template <auto kMethod, uint32_t kMethodId, typename ServiceImpl>
[[nodiscard]] static NanopbServerResponder Open(Server& server,
uint32_t channel_id,
ServiceImpl& service) {
static_assert(
std::is_same_v<Response, internal::Response<kMethod>>,
"The response type of a NanopbServerResponder must match the method.");
return {internal::OpenCall<kMethod, MethodType::kUnary>(
server,
channel_id,
service,
internal::MethodLookup::GetNanopbMethod<ServiceImpl, kMethodId>())};
}
// Allow default construction so that users can declare a variable into which
// to move ServerWriters from RPC calls.
constexpr NanopbServerResponder()
: internal::GenericNanopbResponder(MethodType::kUnary) {}
NanopbServerResponder(NanopbServerResponder&&) = default;
NanopbServerResponder& operator=(NanopbServerResponder&&) = default;
using internal::GenericNanopbResponder::active;
using internal::GenericNanopbResponder::channel_id;
// Sends the response. Returns the following Status codes:
//
// OK - the response was successfully sent
// FAILED_PRECONDITION - the writer is closed
// INTERNAL - pw_rpc was unable to encode the Nanopb protobuf
// other errors - the ChannelOutput failed to send the packet; the error
// codes are determined by the ChannelOutput implementation
//
Status Finish(const Response& response, Status status = OkStatus()) {
return internal::GenericNanopbResponder::SendResponse(&response, status);
}
private:
friend class internal::NanopbMethod;
template <typename, typename, uint32_t>
friend class internal::test::InvocationContext;
NanopbServerResponder(const internal::CallContext& call)
: internal::GenericNanopbResponder(call, MethodType::kUnary) {}
};
// TODO(hepler): "pw::rpc::ServerWriter" should not be specific to Nanopb.
template <typename T>
using ServerWriter = NanopbServerWriter<T>;
} // namespace pw::rpc