blob: 07fa7f3fbff3b7d56c36f13e1a377a0f3535270b [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.
// The classes in this file facilitate testing RPC services. The main class is
// PayloadsView, which iterates over the payloads sent by an RPC service or
// client. This allows verifying that code that invokes RPCs or an RPC service
// implementation sends the expected requests or responses.
//
// This code is inteded for testing, not for deployment.
#pragma once
#include <tuple>
#include "pw_containers/filtered_view.h"
#include "pw_containers/vector.h"
#include "pw_containers/wrapped_iterator.h"
#include "pw_rpc/channel.h"
#include "pw_rpc/internal/method_info.h"
#include "pw_rpc/internal/packet.h"
#include "pw_rpc/method_type.h"
namespace pw::rpc {
namespace internal::test {
class FakeChannelOutput;
// Finds packets of a specified type for a particular method.
class PacketFilter {
public:
// Use Channel::kUnassignedChannelId to ignore the channel.
constexpr PacketFilter(PacketType packet_type_1,
PacketType packet_type_2,
uint32_t channel_id,
uint32_t service_id,
uint32_t method_id)
: packet_type_1_(packet_type_1),
packet_type_2_(packet_type_2),
channel_id_(channel_id),
service_id_(service_id),
method_id_(method_id) {}
constexpr bool operator()(const Packet& packet) const {
return (packet.type() == packet_type_1_ ||
packet.type() == packet_type_2_) &&
(channel_id_ == Channel::kUnassignedChannelId ||
packet.channel_id() == channel_id_) &&
packet.service_id() == service_id_ &&
packet.method_id() == method_id_;
}
private:
// Support filtering on two packet types to handle reading both client and
// server streams for bidirectional streams.
PacketType packet_type_1_;
PacketType packet_type_2_;
uint32_t channel_id_;
uint32_t service_id_;
uint32_t method_id_;
};
using PacketsView = containers::FilteredView<Vector<Packet>, PacketFilter>;
} // namespace internal::test
// Returns the payloads for a particular RPC in a Vector of RPC packets.
//
// Adapts a FilteredView of packets to return payloads instead of packets.
class PayloadsView {
public:
class iterator : public containers::WrappedIterator<
iterator,
internal::test::PacketsView::iterator,
ConstByteSpan> {
public:
constexpr iterator() = default;
// Access the payload (rather than packet) with operator* and operator->.
const ConstByteSpan& operator*() const { return value().payload(); }
const ConstByteSpan* operator->() const { return &value().payload(); }
private:
friend class PayloadsView;
constexpr iterator(const internal::test::PacketsView::iterator& it)
: containers::WrappedIterator<iterator,
internal::test::PacketsView::iterator,
ConstByteSpan>(it) {}
};
using const_iterator = iterator;
const ConstByteSpan& operator[](size_t index) const {
auto it = begin();
std::advance(it, index);
return *it;
}
// Number of payloads for the specified RPC.
size_t size() const { return view_.size(); }
bool empty() const { return begin() == end(); }
// Returns the first/last payload for the RPC. size() must be > 0.
const ConstByteSpan& front() const { return *begin(); }
const ConstByteSpan& back() const { return *std::prev(end()); }
iterator begin() const { return iterator(view_.begin()); }
iterator end() const { return iterator(view_.end()); }
private:
friend class internal::test::FakeChannelOutput;
template <typename>
friend class NanopbPayloadsView;
template <typename>
friend class PwpbPayloadsView;
template <auto kMethod>
using MethodInfo = internal::MethodInfo<kMethod>;
using PacketType = internal::PacketType;
template <auto kMethod>
static constexpr PayloadsView For(const Vector<internal::Packet>& packets,
uint32_t channel_id) {
constexpr auto kTypes = PacketTypesWithPayload(MethodInfo<kMethod>::kType);
return PayloadsView(packets,
std::get<0>(kTypes),
std::get<1>(kTypes),
channel_id,
MethodInfo<kMethod>::kServiceId,
MethodInfo<kMethod>::kMethodId);
}
constexpr PayloadsView(const Vector<internal::Packet>& packets,
MethodType method_type,
uint32_t channel_id,
uint32_t service_id,
uint32_t method_id)
: PayloadsView(packets,
std::get<0>(PacketTypesWithPayload(method_type)),
std::get<1>(PacketTypesWithPayload(method_type)),
channel_id,
service_id,
method_id) {}
constexpr PayloadsView(const Vector<internal::Packet>& packets,
PacketType packet_type_1,
PacketType packet_type_2,
uint32_t channel_id,
uint32_t service_id,
uint32_t method_id)
: view_(packets,
internal::test::PacketFilter(packet_type_1,
packet_type_2,
channel_id,
service_id,
method_id)) {}
static constexpr std::tuple<PacketType, PacketType> PacketTypesWithPayload(
MethodType method_type) {
switch (method_type) {
case MethodType::kUnary:
return {PacketType::REQUEST, PacketType::RESPONSE};
case MethodType::kServerStreaming:
return {PacketType::REQUEST, PacketType::SERVER_STREAM};
case MethodType::kClientStreaming:
return {PacketType::CLIENT_STREAM, PacketType::RESPONSE};
case MethodType::kBidirectionalStreaming:
return {PacketType::CLIENT_STREAM, PacketType::SERVER_STREAM};
}
// Workaround for GCC 8 bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86678
#if defined(__GNUC__) && __GNUC__ < 9
return {};
#else
PW_ASSERT(false);
#endif // defined(__GNUC__) && __GNUC__ < 9
}
internal::test::PacketsView view_;
};
// Class for iterating over RPC statuses associated witha particular RPC. This
// is used to iterate over the user RPC statuses and or protocol errors for a
// particular RPC.
class StatusView {
public:
class iterator : public containers::WrappedIterator<
iterator,
internal::test::PacketsView::iterator,
Status> {
public:
constexpr iterator() = default;
// Access the status (rather than packet) with operator* and operator->.
const Status& operator*() const { return value().status(); }
const Status* operator->() const { return &value().status(); }
private:
friend class StatusView;
constexpr iterator(const internal::test::PacketsView::iterator& it)
: containers::WrappedIterator<iterator,
internal::test::PacketsView::iterator,
Status>(it) {}
};
using const_iterator = iterator;
const Status& operator[](size_t index) const {
auto it = begin();
std::advance(it, index);
return *it;
}
// Number of statuses in this view.
size_t size() const { return view_.size(); }
bool empty() const { return begin() == end(); }
// Returns the first/last payload for the RPC. size() must be > 0.
const Status& front() const { return *begin(); }
const Status& back() const { return *std::prev(end()); }
iterator begin() const { return iterator(view_.begin()); }
iterator end() const { return iterator(view_.end()); }
private:
friend class internal::test::FakeChannelOutput;
template <auto kMethod>
using MethodInfo = internal::MethodInfo<kMethod>;
using PacketType = internal::PacketType;
constexpr StatusView(const Vector<internal::Packet>& packets,
PacketType packet_type_1,
PacketType packet_type_2,
uint32_t channel_id,
uint32_t service_id,
uint32_t method_id)
: view_(packets,
internal::test::PacketFilter(packet_type_1,
packet_type_2,
channel_id,
service_id,
method_id)) {}
internal::test::PacketsView view_;
};
} // namespace pw::rpc