#pragma once
#include <cstdint>
#include <functional>
#include <map>
#include <optional>
#include <ostream>
#include <set>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
#include "clock.hpp"
#include "context_allocator.hpp"
#include "exclusion/common.hpp"
#include "object.hpp"
#include "object_store.hpp"
#include "target_address.hpp"
namespace ddwaf {
class path_trie {
class trie_node {
public:
trie_node() {} ~trie_node() = default;
trie_node(const trie_node &) = delete;
trie_node(trie_node &&) = default;
trie_node &operator=(const trie_node &) = delete;
trie_node &operator=(trie_node &&) = default;
[[nodiscard]] const trie_node *get_child(std::string_view key) const
{
auto it = children_.find(key);
if (it == children_.end()) {
return nullptr;
}
return &it->second;
}
template <typename InternString>
std::pair<std::reference_wrapper<trie_node>, bool > get_or_create_child(
std::string_view key, InternString &&intern_str_fun)
{
{
auto it = children_.find(key);
if (it != children_.end()) {
return {it->second, false};
}
}
auto interned_str = std::forward<InternString>(intern_str_fun)(key);
auto [it, is_new] = children_.emplace(std::piecewise_construct,
std::forward_as_tuple(interned_str), std::forward_as_tuple());
return {std::reference_wrapper{it->second}, true};
}
[[nodiscard]] bool is_terminal() const { return children_.empty(); }
void clear() { children_.clear(); }
protected:
#ifdef HAS_NONRECURSIVE_UNORDERED_MAP
template <typename K, typename V> using MapType = std::map<K, V>;
#else
template <typename K, typename V> using MapType = std::unordered_map<K, V>;
#endif
MapType<std::string_view, trie_node> children_{};
};
static_assert(std::is_move_assignable_v<trie_node>);
static_assert(std::is_move_constructible_v<trie_node>);
static_assert(std::is_default_constructible_v<trie_node>);
static_assert(std::is_constructible_v<trie_node>);
public:
class traverser {
public:
enum class state : uint8_t { not_found, found, intermediate_node };
explicit traverser(const trie_node *root)
{
if (root != nullptr) {
cur_nodes_.emplace_back(root);
}
}
[[nodiscard]] traverser descend(std::string_view next_key) const
{
if (get_state() != state::intermediate_node) {
return *this;
}
std::vector<const trie_node *> next_nodes;
next_nodes.reserve(cur_nodes_.size());
for (const auto *cur_node : cur_nodes_) {
const auto *next_node = cur_node->get_child(next_key);
if (next_node != nullptr) {
if (next_node->is_terminal()) {
return traverser{next_node};
}
next_nodes.emplace_back(next_node);
}
const auto *glob_node = cur_node->get_child("*");
if (glob_node != nullptr) {
if (glob_node->is_terminal()) {
return traverser{glob_node};
}
next_nodes.emplace_back(glob_node);
}
}
return traverser{std::move(next_nodes)};
}
[[nodiscard]] traverser descend_wildcard() const
{
if (get_state() != state::intermediate_node) {
return *this;
}
std::vector<const trie_node *> next_nodes;
next_nodes.reserve(cur_nodes_.size());
for (const auto *cur_node : cur_nodes_) {
const auto *glob_node = cur_node->get_child("*");
if (glob_node != nullptr) {
if (glob_node->is_terminal()) {
return traverser{glob_node};
}
next_nodes.emplace_back(glob_node);
}
}
return traverser{std::move(next_nodes)};
}
[[nodiscard]] state get_state() const
{
if (cur_nodes_.empty()) {
return state::not_found;
}
if (cur_nodes_.size() == 1 && cur_nodes_.back()->is_terminal()) {
return state::found;
}
return state::intermediate_node;
}
private:
explicit traverser(std::vector<const trie_node *> &&nodes) : cur_nodes_(std::move(nodes)) {}
std::vector<const trie_node *> cur_nodes_;
};
template <typename StringType>
void insert(const std::vector<StringType> &path)
requires std::is_constructible_v<std::string_view, StringType>
{
if (!root) {
root.emplace();
}
trie_node *cur = &root.value();
bool last_is_new = true;
for (auto &&component : path) {
auto &&[node, is_new] = cur->get_or_create_child(
component, [this](std::string_view sv) { return intern_string(sv); });
if (!is_new && node.get().is_terminal()) {
return;
}
cur = &node.get();
last_is_new = is_new;
}
if (!last_is_new) {
cur->clear();
}
}
[[nodiscard]] traverser get_traverser() const
{
if (!root) {
return traverser{nullptr};
}
return traverser{&root.value()};
}
private:
std::string_view intern_string(std::string_view orig_sv)
{
auto it = strings.find(orig_sv);
if (it != strings.end()) {
return {*it};
}
auto [new_it, is_new] = strings.emplace(orig_sv);
return {*new_it};
}
std::optional<trie_node> root = std::nullopt;
std::set<std::string, std::less<>> strings;
};
inline std::ostream &operator<<(std::ostream &os, const path_trie::traverser::state &st)
{
using state = path_trie::traverser::state;
switch (st) {
case state::not_found:
return os << std::string_view{"not_found"};
case state::found:
return os << std::string_view{"found"};
case state::intermediate_node:
return os << std::string_view{"intermediate_node"};
}
return os;
}
class object_filter {
public:
using cache_type = memory::unordered_map<target_index, object_cache_key>;
object_filter() = default;
void insert(
target_index target, std::string name, const std::vector<std::string_view> &key_path = {})
{
target_paths_[target].insert(key_path);
targets_.emplace(target, std::move(name));
}
[[nodiscard]] bool empty() const { return target_paths_.empty(); }
object_set match(const object_store &store, cache_type &cache, ddwaf::timer &deadline) const;
void get_addresses(std::unordered_map<target_index, std::string> &addresses) const
{
for (const auto &[index, str] : targets_) { addresses.emplace(index, str); }
}
protected:
std::unordered_map<target_index, path_trie> target_paths_;
std::unordered_map<target_index, std::string> targets_;
};
}