blob: b01402244384f7645eeb591ae9b970b146433fda [file] [log] [blame]
#ifndef FUZZTEST_RPC_FUZZING_RPC_SEQUENCE_H_
#define FUZZTEST_RPC_FUZZING_RPC_SEQUENCE_H_
#include <memory>
#include <optional>
#include <vector>
#include "google/protobuf/descriptor.h"
#include "google/protobuf/message.h"
#include "absl/container/flat_hash_map.h"
#include "./rpc_fuzzing/proto_field_path.h"
namespace fuzztest::internal {
// An RPC call is identified by an id because we might have multiple calls to
// the same rpc method. In the sequence, the id is also the index of the node.
using RpcNodeID = std::int32_t;
// Represents a data-flow edge between two RPC calls.
struct RpcDataFlowEdge {
RpcNodeID from_node_id = -1;
// The message field in the response.
FieldPath from_field;
// The dynamic field in the request.
FieldPath to_field;
};
bool operator==(const RpcDataFlowEdge& lhs, const RpcDataFlowEdge& rhs);
bool operator!=(const RpcDataFlowEdge& lhs, const RpcDataFlowEdge& rhs);
// Represents a remote procedure call (a request). Parts of the request that are
// coming from a previous response are represented with a data flow edge
// (`RpcDataFlowEdge`).
class RpcNode {
public:
RpcNode(const google::protobuf::MethodDescriptor& method,
std::unique_ptr<google::protobuf::Message> request)
: method_(&method), request_(std::move(request)) {}
RpcNode(const RpcNode&);
RpcNode(RpcNode&&) noexcept;
RpcNode& operator=(const RpcNode&);
RpcNode& operator=(RpcNode&&) = default;
const google::protobuf::MethodDescriptor& method() const { return *method_; }
google::protobuf::Message& request() { return *request_; }
const google::protobuf::Message& request() const { return *request_; }
void AddDependency(RpcDataFlowEdge dep) { dependencies_.push_back(dep); }
const std::vector<RpcDataFlowEdge>& dependencies() const {
return dependencies_;
}
std::vector<RpcDataFlowEdge>& dependencies() { return dependencies_; }
friend bool operator==(const RpcNode& lhs, const RpcNode& rhs);
private:
// The `method_` is ensured to be non-null.
const google::protobuf::MethodDescriptor* method_;
std::unique_ptr<google::protobuf::Message> request_;
std::vector<RpcDataFlowEdge> dependencies_;
};
bool operator==(const RpcNode& lhs, const RpcNode& rhs);
bool operator!=(const RpcNode& lhs, const RpcNode& rhs);
inline RpcNode::RpcNode(RpcNode&&) noexcept = default;
// The RpcSequence represents a topologically sorted RpcDataFlowGraph.
using RpcSequence = std::vector<RpcNode>;
// The RpcDataFlowGraph represents a "remote procedure call session", i.e., a
// set of RPC method calls and the data-flow dependencies between them.
class RpcDataFlowGraph {
public:
void AddNode(RpcNodeID id, RpcNode rpc_node);
void RemoveNode(RpcNodeID id);
const RpcNode& GetNode(RpcNodeID id) const;
size_t NodeNum() const;
absl::flat_hash_map<RpcNodeID, RpcNode>& GetAllNodes() { return rpc_nodes_; }
const absl::flat_hash_map<RpcNodeID, RpcNode>& GetAllNodes() const {
return rpc_nodes_;
}
// Returns a topologically sorted sequence representation of the graph. Note
// that there are multiple possible topological orderings, from which it
// returns a random one. Multiple calls to the method return the same ordering
// (unless RandomizeTopologicalOrdering() is called).
RpcSequence GetSequence() const;
// Converts a sequence to a graph with the sequence node ordering.
static RpcDataFlowGraph FromSequence(const RpcSequence& sequence);
// Randomizes the topological ordering of the nodes.
void RandomizeTopologicalOrdering();
// Returns the ordering of the nodes corresponding to that returned by
// `GetSequence`.
std::vector<RpcNodeID>& GetOrderedNodeIds() const;
private:
// Invalidates the node order so that if the order is needed again it will be
// re-computed on demand. This optimization avoids unnecessary recomputation
// every time the graph is changed because the recomputation can be costly.
void InvalidateNodeOrder() const { node_order_.reset(); }
absl::flat_hash_map<RpcNodeID, RpcNode> rpc_nodes_;
// `GetSequence` might change the order so we need `mutable` for `GetSequence`
// to be used in `FromValue` in the RpcSession domain.
mutable std::optional<std::vector<RpcNodeID>> node_order_;
};
// Returns true if the graphs have the same nodes and ordering.
bool operator==(const RpcDataFlowGraph& lhs, const RpcDataFlowGraph& rhs);
bool operator!=(const RpcDataFlowGraph& lhs, const RpcDataFlowGraph& rhs);
} // namespace fuzztest::internal
#endif // FUZZTEST_RPC_FUZZING_RPC_SEQUENCE_H_