blob: e7711d803567800815037b3dab4966936331e66e [file] [log] [blame]
// 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 <type_traits>
#include "pw_assert/assert.h"
#include "pw_containers/vector.h"
#include "pw_rpc/channel.h"
#include "pw_rpc/internal/hash.h"
#include "pw_rpc/internal/method_lookup.h"
#include "pw_rpc/internal/packet.h"
#include "pw_rpc/internal/server.h"
#include "pw_rpc/internal/test_method_context.h"
#include "pw_rpc/raw/internal/method.h"
#include "pw_rpc_private/fake_channel_output.h"
namespace pw::rpc {
// Declares a context object that may be used to invoke an RPC. The context is
// declared with the name of the implemented service and the method to invoke.
// The RPC can then be invoked with the call method.
//
// For a unary RPC, context.call(request) returns the status, and the response
// struct can be accessed via context.response().
//
// PW_RAW_TEST_METHOD_CONTEXT(my::CoolService, TheMethod) context;
// EXPECT_EQ(OkStatus(), context.call(encoded_request).status());
// EXPECT_EQ(0,
// std::memcmp(encoded_response,
// context.response().data(),
// sizeof(encoded_response)));
//
// For a server streaming RPC, context.call(request) invokes the method. As in a
// normal RPC, the method completes when the ServerWriter's Finish method is
// called (or it goes out of scope).
//
// PW_RAW_TEST_METHOD_CONTEXT(my::CoolService, TheStreamingMethod) context;
// context.call(encoded_response);
//
// EXPECT_TRUE(context.done()); // Check that the RPC completed
// EXPECT_EQ(OkStatus(), context.status()); // Check the status
//
// EXPECT_EQ(3u, context.responses().size());
// ByteSpan& response = context.responses()[0]; // check individual responses
//
// for (ByteSpan& response : context.responses()) {
// // iterate over the responses
// }
//
// PW_RAW_TEST_METHOD_CONTEXT forwards its constructor arguments to the
// underlying service. For example:
//
// PW_RAW_TEST_METHOD_CONTEXT(MyService, Go) context(service, args);
//
// PW_RAW_TEST_METHOD_CONTEXT takes two optional arguments:
//
// size_t kMaxResponses: maximum responses to store; ignored unless streaming
// size_t kOutputSizeBytes: buffer size; must be large enough for a packet
//
// Example:
//
// PW_RAW_TEST_METHOD_CONTEXT(MyService, BestMethod, 3, 256) context;
// ASSERT_EQ(3u, context.responses().max_size());
//
#define PW_RAW_TEST_METHOD_CONTEXT(service, method, ...) \
::pw::rpc::RawTestMethodContext<service, \
&service::method, \
::pw::rpc::internal::Hash(#method), \
##__VA_ARGS__>
template <typename Service,
auto kMethod,
uint32_t kMethodId,
size_t kMaxResponses = 4,
size_t kOutputSizeBytes = 128>
class RawTestMethodContext;
// Internal classes that implement RawTestMethodContext.
namespace internal::test::raw {
// A ChannelOutput implementation that stores the outgoing payloads and status.
template <size_t kOutputSize, size_t kMaxResponses>
class MessageOutput final : public FakeChannelOutput {
public:
MessageOutput(bool server_streaming)
: FakeChannelOutput(packet_buffer_, server_streaming) {}
const Vector<ByteSpan>& responses() const { return responses_; }
// Allocates a response buffer and returns a reference to the response span
// for it.
ByteSpan& AllocateResponse() {
// If we run out of space, the back message is always the most recent.
response_buffers_.emplace_back();
response_buffers_.back() = {};
responses_.emplace_back();
responses_.back() = {response_buffers_.back().data(),
response_buffers_.back().size()};
return responses_.back();
}
private:
void AppendResponse(ConstByteSpan response) override {
ByteSpan& response_span = AllocateResponse();
PW_ASSERT(response.size() <= response_span.size());
std::memcpy(response_span.data(), response.data(), response.size());
response_span = response_span.first(response.size());
}
void ClearResponses() override {
responses_.clear();
response_buffers_.clear();
}
std::array<std::byte, kOutputSize> packet_buffer_;
Vector<ByteSpan, kMaxResponses> responses_;
Vector<std::array<std::byte, kOutputSize>, kMaxResponses> response_buffers_;
};
// Collects everything needed to invoke a particular RPC.
template <typename Service,
auto kMethod,
uint32_t kMethodId,
size_t kMaxResponses,
size_t kOutputSize>
class RawInvocationContext : public InvocationContext<Service, kMethodId> {
public:
// Returns the responses that have been recorded. The maximum number of
// responses is responses().max_size(). responses().back() is always the most
// recent response, even if total_responses() > responses().max_size().
const Vector<ByteSpan>& responses() const { return output_.responses(); }
// Gives access to the RPC's most recent response.
ConstByteSpan response() const {
PW_ASSERT(!responses().empty());
return responses().back();
}
protected:
template <typename... Args>
RawInvocationContext(Args&&... args)
: InvocationContext<Service, kMethodId>(
MethodLookup::GetRawMethod<Service, kMethodId>(),
output_,
std::forward<Args>(args)...),
output_(MethodTraits<decltype(kMethod)>::kServerStreaming) {}
MessageOutput<kOutputSize, kMaxResponses>& output() { return output_; }
private:
MessageOutput<kOutputSize, kMaxResponses> output_;
};
// Method invocation context for a unary RPC. Returns the status in call() and
// provides the response through the response() method.
template <typename Service,
auto kMethod,
uint32_t kMethodId,
size_t kOutputSize>
class UnaryContext
: public RawInvocationContext<Service, kMethod, kMethodId, 1, kOutputSize> {
using Base =
RawInvocationContext<Service, kMethod, kMethodId, 1, kOutputSize>;
public:
template <typename... Args>
UnaryContext(Args&&... args) : Base(std::forward<Args>(args)...) {}
// Invokes the RPC with the provided request. Returns RPC's StatusWithSize.
StatusWithSize call(ConstByteSpan request) {
Base::output().clear();
ByteSpan& response = Base::output().AllocateResponse();
auto sws =
CallMethodImplFunction<kMethod>(Base::server_call(), request, response);
response = response.first(sws.size());
return sws;
}
};
// Method invocation context for a server streaming RPC.
template <typename Service,
auto kMethod,
uint32_t kMethodId,
size_t kMaxResponses,
size_t kOutputSize>
class ServerStreamingContext : public RawInvocationContext<Service,
kMethod,
kMethodId,
kMaxResponses,
kOutputSize> {
using Base = RawInvocationContext<Service,
kMethod,
kMethodId,
kMaxResponses,
kOutputSize>;
public:
template <typename... Args>
ServerStreamingContext(Args&&... args) : Base(std::forward<Args>(args)...) {}
// Invokes the RPC with the provided request.
void call(ConstByteSpan request) {
Base::output().clear();
RawServerWriter writer(Base::server_call());
return CallMethodImplFunction<kMethod>(
Base::server_call(), request, writer);
}
// Returns a server writer which writes responses into the context's buffer.
// This should not be called alongside call(); use one or the other.
RawServerWriter writer() {
Base::output().clear();
return RawServerWriter(Base::server_call());
}
};
// Method invocation context for a client streaming RPC.
template <typename Service,
auto kMethod,
uint32_t kMethodId,
size_t kMaxResponses,
size_t kOutputSize>
class ClientStreamingContext : public RawInvocationContext<Service,
kMethod,
kMethodId,
kMaxResponses,
kOutputSize> {
using Base = RawInvocationContext<Service,
kMethod,
kMethodId,
kMaxResponses,
kOutputSize>;
public:
template <typename... Args>
ClientStreamingContext(Args&&... args) : Base(std::forward<Args>(args)...) {}
// Invokes the RPC.
void call() {
Base::output().clear();
RawServerReader reader_writer(Base::server_call());
return CallMethodImplFunction<kMethod>(Base::server_call(), reader_writer);
}
// Returns a reader/writer which writes responses into the context's buffer.
// This should not be called alongside call(); use one or the other.
RawServerReader reader() {
Base::output().clear();
return RawServerReader(Base::server_call());
}
// Allow sending client streaming packets.
using Base::SendClientStream;
using Base::SendClientStreamEnd;
};
// Method invocation context for a bidirectional streaming RPC.
template <typename Service,
auto kMethod,
uint32_t kMethodId,
size_t kMaxResponses,
size_t kOutputSize>
class BidirectionalStreamingContext : public RawInvocationContext<Service,
kMethod,
kMethodId,
kMaxResponses,
kOutputSize> {
using Base = RawInvocationContext<Service,
kMethod,
kMethodId,
kMaxResponses,
kOutputSize>;
public:
template <typename... Args>
BidirectionalStreamingContext(Args&&... args)
: Base(std::forward<Args>(args)...) {}
// Invokes the RPC.
void call() {
Base::output().clear();
RawServerReaderWriter reader_writer(Base::server_call());
return CallMethodImplFunction<kMethod>(Base::server_call(), reader_writer);
}
// Returns a reader/writer which writes responses into the context's buffer.
// This should not be called alongside call(); use one or the other.
RawServerReaderWriter reader_writer() {
Base::output().clear();
return RawServerReaderWriter(Base::server_call());
}
// Allow sending client streaming packets.
using Base::SendClientStream;
using Base::SendClientStreamEnd;
};
// Alias to select the type of the context object to use based on which type of
// RPC it is for.
template <typename Service,
auto kMethod,
uint32_t kMethodId,
size_t kMaxResponses,
size_t kOutputSize>
using Context = std::tuple_element_t<
static_cast<size_t>(MethodTraits<decltype(kMethod)>::kType),
std::tuple<UnaryContext<Service, kMethod, kMethodId, kOutputSize>,
ServerStreamingContext<Service,
kMethod,
kMethodId,
kMaxResponses,
kOutputSize>,
ClientStreamingContext<Service,
kMethod,
kMethodId,
kMaxResponses,
kOutputSize>,
BidirectionalStreamingContext<Service,
kMethod,
kMethodId,
kMaxResponses,
kOutputSize>>>;
} // namespace internal::test::raw
template <typename Service,
auto kMethod,
uint32_t kMethodId,
size_t kMaxResponses,
size_t kOutputSizeBytes>
class RawTestMethodContext
: public internal::test::raw::Context<Service,
kMethod,
kMethodId,
kMaxResponses,
kOutputSizeBytes> {
public:
// Forwards constructor arguments to the service class.
template <typename... ServiceArgs>
RawTestMethodContext(ServiceArgs&&... service_args)
: internal::test::raw::Context<Service,
kMethod,
kMethodId,
kMaxResponses,
kOutputSizeBytes>(
std::forward<ServiceArgs>(service_args)...) {}
};
} // namespace pw::rpc