#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <variant>
#include <vector>
#include "argument_retriever.hpp"
#include "attribute_collector.hpp"
#include "clock.hpp"
#include "condition/base.hpp"
#include "dynamic_string.hpp"
#include "exception.hpp"
#include "expression.hpp"
#include "log.hpp"
#include "memory_resource.hpp"
#include "object.hpp"
#include "object_store.hpp"
#include "pointer.hpp"
#include "target_address.hpp"
#include "traits.hpp"
namespace ddwaf {
struct processor_target {
target_index index;
std::string name;
std::vector<std::variant<std::string, int64_t>> key_path;
};
struct processor_parameter {
std::vector<processor_target> targets;
};
struct processor_mapping {
std::vector<processor_parameter> inputs;
processor_target output;
};
struct resolved_argument_count {
std::size_t all{0};
std::size_t optional{0};
};
struct processor_cache {
expression::cache_type expr_cache;
std::unordered_set<target_index> generated;
std::vector<resolved_argument_count> evaluated;
struct {
std::vector<std::optional<dynamic_string>> fragment_fields;
} fingerprint;
};
template <typename Class, typename... Args>
function_traits<Class::param_names.size(), Class, Args...> make_eval_traits(
owned_object (Class::*)(Args...) const);
template <typename... Ts> constexpr std::size_t count_optionals()
{
return (is_optional_argument<Ts>::value + ...);
}
template <typename T> struct tuple_optionals_trait : std::false_type {};
template <typename... Ts>
struct tuple_optionals_trait<std::tuple<Ts...>>
: std::bool_constant<(count_optionals<Ts...>() > 0)> {
static constexpr std::size_t count = count_optionals<Ts...>();
};
template <typename T>
concept is_tuple_with_optional = tuple_optionals_trait<T>::value;
class base_processor {
public:
base_processor() = default;
base_processor(const base_processor &) = delete;
base_processor &operator=(const base_processor &) = delete;
base_processor(base_processor &&rhs) noexcept = default;
base_processor &operator=(base_processor &&rhs) noexcept = default;
virtual ~base_processor() = default;
virtual void eval(object_store &store, attribute_collector &collector, processor_cache &cache,
nonnull_ptr<memory::memory_resource> alloc, ddwaf::timer &deadline) const = 0;
virtual void get_addresses(std::unordered_map<target_index, std::string> &addresses) const = 0;
[[nodiscard]] virtual const std::string &get_id() const = 0;
};
template <typename Self> class structured_processor : public base_processor {
public:
structured_processor(std::string id, std::shared_ptr<expression> expr,
std::vector<processor_mapping> mappings, bool evaluate, bool output)
: id_(std::move(id)), expr_(std::move(expr)), mappings_(std::move(mappings)),
evaluate_(evaluate), output_(output)
{}
structured_processor(const structured_processor &) = delete;
structured_processor &operator=(const structured_processor &) = delete;
structured_processor(structured_processor &&rhs) noexcept = default;
structured_processor &operator=(structured_processor &&rhs) noexcept = default;
~structured_processor() override = default;
void eval(object_store &store, attribute_collector &collector, processor_cache &cache,
nonnull_ptr<memory::memory_resource> alloc, ddwaf::timer &deadline) const override
{
DDWAF_DEBUG("Evaluating processor '{}'", id_);
if (!expr_->eval(cache.expr_cache, store, {}, {}, deadline)) {
return;
}
using func_traits = decltype(make_eval_traits(&Self::eval_impl));
using tuple_type = typename func_traits::tuple_type;
static_assert(func_traits::nargs == Self::param_names.size());
if constexpr (is_tuple_with_optional<tuple_type>) {
if (cache.evaluated.size() < mappings_.size()) {
cache.evaluated.resize(mappings_.size());
}
}
for (std::size_t i = 0; i < mappings_.size(); ++i) {
const auto &mapping = mappings_[i];
if (deadline.expired()) {
throw ddwaf::timeout_exception();
}
if (store.has_target(mapping.output.index) ||
cache.generated.contains(mapping.output.index)) {
if constexpr (is_tuple_with_optional<tuple_type>) {
if (cache.evaluated[i].optional == tuple_optionals_trait<tuple_type>::count) {
continue;
}
} else {
continue;
}
}
tuple_type args;
auto arg_count = resolve_arguments(
mapping, store, args, std::make_index_sequence<func_traits::nargs>{});
if constexpr (is_tuple_with_optional<tuple_type>) {
if (arg_count.all == 0 || (arg_count.all == cache.evaluated[i].all &&
arg_count.optional == cache.evaluated[i].optional)) {
continue;
}
} else {
if (arg_count.all == 0) {
continue;
}
}
auto suitable_alloc = evaluate_ ? memory::get_default_resource() : alloc;
auto object = std::apply(
[&](auto &&...args) {
return static_cast<const Self *>(this)->eval_impl(
std::forward<decltype(args)>(args)..., cache, suitable_alloc, deadline);
},
std::move(args));
cache.generated.emplace(mapping.output.index);
if constexpr (is_tuple_with_optional<tuple_type>) {
cache.evaluated[i] = arg_count;
}
if (object.is_invalid()) {
continue;
}
if (output_) {
if (evaluate_) {
collector.insert_or_assign(mapping.output.name, object.clone(alloc));
store.insert_and_apply(
mapping.output.index, mapping.output.name, std::move(object));
} else {
collector.insert_or_assign(mapping.output.name, std::move(object));
}
} else {
store.insert_and_apply(
mapping.output.index, mapping.output.name, std::move(object));
}
}
}
[[nodiscard]] const std::string &get_id() const override { return id_; }
void get_addresses(std::unordered_map<target_index, std::string> &addresses) const override
{
expr_->get_addresses(addresses);
for (const auto &mapping : mappings_) {
for (const auto &input : mapping.inputs) {
for (const auto &target : input.targets) {
addresses.emplace(target.index, target.name);
}
}
}
}
static constexpr auto arguments()
{
return generate_argument_spec(std::make_index_sequence<Self::param_names.size()>());
}
template <size_t... Is>
static constexpr auto generate_argument_spec(std::index_sequence<Is...>) {
constexpr auto param_names = Self::param_names;
using func_traits = decltype(make_eval_traits(&Self::eval_impl));
static_assert(param_names.size() <= func_traits::nargs);
return std::array<parameter_specification, sizeof...(Is)>{{
{
param_names[Is],
argument_retriever<typename func_traits::template arg_type<Is>>::is_variadic,
argument_retriever<typename func_traits::template arg_type<Is>>::is_optional,
}...,
}};
}
protected:
template <size_t I, size_t... Is, typename Args>
resolved_argument_count resolve_arguments(const processor_mapping &mapping,
const object_store &store, Args &args, std::index_sequence<I, Is...> ,
resolved_argument_count count = {}) const
{
using TupleElement = std::tuple_element_t<I, Args>;
auto arg = resolve_argument<I>(mapping, store);
if constexpr (is_unary_argument<TupleElement>::value) {
if (!arg.has_value()) {
return {};
}
++count.all;
std::get<I>(args) = std::move(arg.value());
} else if constexpr (is_variadic_argument<TupleElement>::value) {
if (arg.empty()) {
return {};
}
++count.all;
std::get<I>(args) = std::move(arg);
} else {
count.all += static_cast<std::size_t>(arg.has_value());
count.optional += static_cast<std::size_t>(arg.has_value());
std::get<I>(args) = std::move(arg);
}
if constexpr (sizeof...(Is) > 0) {
return resolve_arguments(mapping, store, args, std::index_sequence<Is...>{}, count);
} else {
return count;
}
}
template <size_t I>
[[nodiscard]] auto resolve_argument(
const processor_mapping &mapping, const object_store &store) const
{
using func_traits = decltype(make_eval_traits(&Self::eval_impl));
using target_type = typename func_traits::template arg_type<I>;
using retriever = argument_retriever<target_type>;
if constexpr (retriever::is_variadic) {
if (mapping.inputs.size() <= I) {
return target_type{};
}
return retriever::retrieve(store, {}, mapping.inputs[I].targets);
} else if constexpr (retriever::is_optional) {
if (mapping.inputs.size() <= I) {
return target_type{};
}
const auto &arg = mapping.inputs[I];
if (arg.targets.empty()) {
return target_type{};
}
return retriever::retrieve(store, {}, arg.targets.at(0));
} else {
if (mapping.inputs.size() <= I) {
return std::optional<target_type>{};
}
const auto &arg = mapping.inputs[I];
if (arg.targets.empty()) {
return std::optional<target_type>{};
}
return retriever::retrieve(store, {}, arg.targets.at(0));
}
}
std::string id_;
std::shared_ptr<expression> expr_;
std::vector<processor_mapping> mappings_;
bool evaluate_{false};
bool output_{true};
};
}