From 62ff200a3cf8032a4bb829676251f49d95bb92d0 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Tue, 9 Jun 2026 20:01:32 +0100 Subject: [PATCH 1/3] Introduce ddwaf_(context|subcontext)_multieval to evaluate multiple batches in sequence --- fuzzer/cmdi_detector/src/main.cpp | 2 +- fuzzer/lfi_detector/src/main.cpp | 2 +- fuzzer/shi_detector_array/src/main.cpp | 2 +- fuzzer/shi_detector_string/src/main.cpp | 2 +- fuzzer/sqli_detector/src/main.cpp | 2 +- fuzzer/ssrf_detector/src/main.cpp | 2 +- include/ddwaf.h | 179 +++ libddwaf.def | 2 + smoketest/smoke.c | 29 + src/context.hpp | 70 +- src/evaluation_engine.cpp | 74 +- src/evaluation_engine.hpp | 34 +- src/interface.cpp | 106 +- src/object_store.cpp | 101 +- src/object_store.hpp | 54 +- src/processor/base.hpp | 5 +- src/serializer.cpp | 6 +- src/serializer.hpp | 1 + .../interface/context/multieval/test.cpp | 1033 +++++++++++++++++ .../context/multieval/yaml/rules.yaml | 51 + tests/unit/attribute_collector_test.cpp | 44 +- tests/unit/condition/cmdi_detector_test.cpp | 26 +- .../unit/condition/exists_condition_test.cpp | 34 +- tests/unit/condition/lfi_detector_test.cpp | 22 +- .../negated_scalar_condition_test.cpp | 34 +- .../unit/condition/scalar_condition_test.cpp | 12 +- .../condition/shi_detector_array_test.cpp | 18 +- .../condition/shi_detector_string_test.cpp | 16 +- tests/unit/condition/sqli_detector_test.cpp | 18 +- tests/unit/condition/ssrf_detector_test.cpp | 2 +- tests/unit/evaluation_engine_test.cpp | 126 +- tests/unit/exclusion/input_filter_test.cpp | 74 +- tests/unit/exclusion/object_filter_test.cpp | 62 +- tests/unit/exclusion/rule_filter_test.cpp | 30 +- tests/unit/expression_test.cpp | 62 +- tests/unit/module_test.cpp | 64 +- tests/unit/object_store_test.cpp | 107 +- tests/unit/processor/processor_test.cpp | 30 +- .../processor/structured_processor_test.cpp | 8 +- tests/unit/rule_test.cpp | 26 +- tests/unit/waf_test.cpp | 2 +- 41 files changed, 2084 insertions(+), 490 deletions(-) create mode 100644 tests/integration/interface/context/multieval/test.cpp create mode 100644 tests/integration/interface/context/multieval/yaml/rules.yaml diff --git a/fuzzer/cmdi_detector/src/main.cpp b/fuzzer/cmdi_detector/src/main.cpp index 302614ff1..e48288b40 100644 --- a/fuzzer/cmdi_detector/src/main.cpp +++ b/fuzzer/cmdi_detector/src/main.cpp @@ -199,7 +199,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/fuzzer/lfi_detector/src/main.cpp b/fuzzer/lfi_detector/src/main.cpp index 8a2d481d1..703c93358 100644 --- a/fuzzer/lfi_detector/src/main.cpp +++ b/fuzzer/lfi_detector/src/main.cpp @@ -118,7 +118,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) owned_object::make_string(resource, ddwaf::memory::get_default_resource())); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/fuzzer/shi_detector_array/src/main.cpp b/fuzzer/shi_detector_array/src/main.cpp index 753678e6c..c92b61252 100644 --- a/fuzzer/shi_detector_array/src/main.cpp +++ b/fuzzer/shi_detector_array/src/main.cpp @@ -199,7 +199,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/fuzzer/shi_detector_string/src/main.cpp b/fuzzer/shi_detector_string/src/main.cpp index 10a540ae2..de645b80e 100644 --- a/fuzzer/shi_detector_string/src/main.cpp +++ b/fuzzer/shi_detector_string/src/main.cpp @@ -117,7 +117,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) owned_object::make_string(resource, ddwaf::memory::get_default_resource())); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/fuzzer/sqli_detector/src/main.cpp b/fuzzer/sqli_detector/src/main.cpp index 5c381ead1..ea455329f 100644 --- a/fuzzer/sqli_detector/src/main.cpp +++ b/fuzzer/sqli_detector/src/main.cpp @@ -135,7 +135,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) owned_object::make_string(resource, ddwaf::memory::get_default_resource())); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/fuzzer/ssrf_detector/src/main.cpp b/fuzzer/ssrf_detector/src/main.cpp index b2a988227..c6d95cc05 100644 --- a/fuzzer/ssrf_detector/src/main.cpp +++ b/fuzzer/ssrf_detector/src/main.cpp @@ -118,7 +118,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) owned_object::make_string(resource, ddwaf::memory::get_default_resource())); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/include/ddwaf.h b/include/ddwaf.h index 043d21d29..95594bb9d 100644 --- a/include/ddwaf.h +++ b/include/ddwaf.h @@ -317,6 +317,12 @@ ddwaf_context ddwaf_context_init(const ddwaf_handle handle, ddwaf_allocator outp * format: {tag, value} * - keep: whether the data contained herein must override any * transport sampling through the relevant mechanism. + * - evaluated: an unsigned integer indicating the number of input + * batches that were fully evaluated. For this single + * evaluation it is 1 when a non-empty batch was + * evaluated and 0 otherwise (e.g. an empty input or a + * timeout before evaluation completed). See + * ddwaf_context_multieval for the multi-batch case. * This structure must be freed by the caller using the output * allocator provided through ddwaf_context_init. The object will * contain all specified keys when the value returned by @@ -349,6 +355,90 @@ ddwaf_context ddwaf_context_init(const ddwaf_handle handle, ddwaf_allocator outp DDWAF_RET_CODE ddwaf_context_eval(ddwaf_context context, ddwaf_object *data, ddwaf_allocator alloc, ddwaf_object *result, uint64_t timeout); +/** + * Perform multiple matching operations on the provided data, evaluating each + * batch in sequence and returning a single combined result. + * + * This function operates identically to ddwaf_context_eval, with the + * distinction that data must be an array of maps. Each element is treated as + * a separate input batch and is evaluated in order. Addresses provided in + * earlier batches persist in the store and remain available when evaluating + * later batches, so a rule that requires multiple addresses can be satisfied + * by data spread across different batches within a single call. The result + * reflects all events, actions, and attributes accumulated across the entire + * sequence of batches. + * + * @param context WAF context to be used in this run, this will determine the + * ruleset which will be used and it will also ensure that + * parameters are taken into account across runs. (nonnull) + * + * @param data (nonnull) Array of input batches to evaluate. Each element must + * be a map of {string, } where each key represents the relevant + * address associated to the value, which can be of an arbitrary type. The + * batches are evaluated in order. If the same address appears in multiple + * batches, the later value replaces the earlier one. Passing a non-array + * object will return DDWAF_ERR_INVALID_OBJECT. + * Ownership and lifetime semantics are identical to ddwaf_context_eval: + * the data is stored by the context and the provided allocator is used to + * free it once the context is destroyed. + * + * @param alloc (nullable) Allocator used to free the data provided. If NULL, + * the data will not be freed. + * + * @param result (nullable) Object map containing the following items: + * - events: an array of all events generated across all batches. + * - actions: a map of all actions generated across all batches + * in the format: "{action type: { }, ...}" + * - duration: an unsigned specifying the total runtime of the + * call in nanoseconds. + * - timeout: whether there has been a timeout during the call. + * - attributes: a map containing all derived objects in the + * format: {tag, value} + * - keep: whether the data contained herein must override any + * transport sampling through the relevant mechanism. + * - evaluated: an unsigned integer indicating the number of + * batches that were fully evaluated. In the normal + * case this equals the number of non-empty batches. + * On timeout or error occurring during batch I + * (0-based, counting only non-empty batches), this + * value equals I, which is also the index of the + * batch where the problem occurred. Empty batches are + * skipped and do not count towards this value. + * This structure must be freed by the caller using the output + * allocator provided through ddwaf_context_init. The object will + * contain all specified keys when the value returned by + * ddwaf_context_multieval is either DDWAF_OK or DDWAF_MATCH and + * will be empty otherwise. + * IMPORTANT: This object is not allocated with the allocator + * passed in this call. It uses the allocator given to + * ddwaf_context_init instead. + * @param timeout Maximum time budget in microseconds. + * + * @return Return code of the operation. + * @retval DDWAF_ERR_INVALID_ARGUMENT The context is invalid, the data will not + * be freed. + * @retval DDWAF_ERR_INVALID_OBJECT The data provided didn't match the desired + * structure or contained invalid objects, the + * data will be freed by this function. + * @retval DDWAF_ERR_INTERNAL There was an unexpected error and the operation did + * not succeed. The state of the WAF is undefined if + * this error is produced and the ownership of the + * data is unknown. The result structure will not be + * filled if this error occurs. + * + * Notes on addresses: + * - Within a single batch, addresses provided should be unique. + * If duplicate addresses are provided within the same batch, the latest one + * in the structure will be used for evaluation. + * - Addresses from earlier batches persist in the store and are accessible + * during evaluation of subsequent batches within the same call. If the same + * address appears in a later batch, the later value replaces the earlier one. + * - A rule that requires multiple addresses can be satisfied by data spread + * across different batches within a single call. + **/ +DDWAF_RET_CODE ddwaf_context_multieval(ddwaf_context context, ddwaf_object *data, + ddwaf_allocator alloc, ddwaf_object *result, uint64_t timeout); + /** * Performs the destruction of the context, freeing the data passed to it through * ddwaf_context_eval using the provided allocator during evaluation. @@ -395,6 +485,12 @@ ddwaf_subcontext ddwaf_subcontext_init(ddwaf_context context); * format: {tag, value} * - keep: whether the data contained herein must override any * transport sampling through the relevant mechanism. + * - evaluated: an unsigned integer indicating the number of input + * batches that were fully evaluated. For this single + * evaluation it is 1 when a non-empty batch was + * evaluated and 0 otherwise (e.g. an empty input or a + * timeout before evaluation completed). See + * ddwaf_subcontext_multieval for the multi-batch case. * This structure must be freed by the caller and will contain all * specified keys when the value returned by ddwaf_subcontext_eval * is either DDWAF_OK or DDWAF_MATCH and will be empty otherwise. @@ -425,6 +521,89 @@ ddwaf_subcontext ddwaf_subcontext_init(ddwaf_context context); DDWAF_RET_CODE ddwaf_subcontext_eval(ddwaf_subcontext subcontext, ddwaf_object *data, ddwaf_allocator alloc, ddwaf_object *result, uint64_t timeout); +/** + * Perform multiple matching operations on the provided data, evaluating each + * batch in sequence and returning a single combined result. + * + * This function operates identically to ddwaf_subcontext_eval, with the + * distinction that data must be an array of maps. Each element is treated as + * a separate input batch and is evaluated in order. Addresses provided in + * earlier batches persist in the store and remain available when evaluating + * later batches, so a rule that requires multiple addresses can be satisfied + * by data spread across different batches within a single call. The result + * reflects all events, actions, and attributes accumulated across the entire + * sequence of batches. + * + * @param subcontext WAF subcontext to be used in this run, this will determine + * the ruleset which will be used and it will also ensure that + * parameters are taken into account across runs. (nonnull) + * + * @param data (nonnull) Array of input batches to evaluate. Each element must + * be a map of {string, } where each key represents the relevant + * address associated to the value, which can be of an arbitrary type. The + * batches are evaluated in order. If the same address appears in multiple + * batches, the later value replaces the earlier one. Passing a non-array + * object will return DDWAF_ERR_INVALID_OBJECT. + * Ownership and lifetime semantics are identical to ddwaf_subcontext_eval: + * the data is stored by the subcontext and the provided allocator is used + * to free it once the subcontext is destroyed. + * + * @param alloc (nullable) Allocator used to free the data provided. If NULL, + * the data will not be freed. + * + * @param result (nullable) Object map containing the following items: + * - events: an array of all events generated across all batches. + * - actions: a map of all actions generated across all batches + * in the format: "{action type: { }, ...}" + * - duration: an unsigned specifying the total runtime of the + * call in nanoseconds. + * - timeout: whether there has been a timeout during the call. + * - attributes: a map containing all derived objects in the + * format: {tag, value} + * - keep: whether the data contained herein must override any + * transport sampling through the relevant mechanism. + * - evaluated: an unsigned integer indicating the number of + * batches that were fully evaluated. In the normal + * case this equals the number of non-empty batches. + * On timeout or error occurring during batch I + * (0-based, counting only non-empty batches), this + * value equals I, which is also the index of the + * batch where the problem occurred. Empty batches are + * skipped and do not count towards this value. + * This structure must be freed by the caller and will contain all + * specified keys when the value returned by + * ddwaf_subcontext_multieval is either DDWAF_OK or DDWAF_MATCH + * and will be empty otherwise. + * IMPORTANT: This object is not allocated with the allocator + * passed in this call. It uses the allocator given to + * ddwaf_context_init instead. + * @param timeout Maximum time budget in microseconds. + * + * @return Return code of the operation. + * @retval DDWAF_ERR_INVALID_ARGUMENT The subcontext is invalid, the data will + * not be freed. + * @retval DDWAF_ERR_INVALID_OBJECT The data provided didn't match the desired + * structure or contained invalid objects, the + * data will be freed by this function. + * @retval DDWAF_ERR_INTERNAL There was an unexpected error and the operation did + * not succeed. The state of the WAF is undefined if + * this error is produced and the ownership of the + * data is unknown. The result structure will not be + * filled if this error occurs. + * + * Notes on addresses: + * - Within a single batch, addresses provided should be unique. + * If duplicate addresses are provided within the same batch, the latest one + * in the structure will be used for evaluation. + * - Addresses from earlier batches persist in the store and are accessible + * during evaluation of subsequent batches within the same call. If the same + * address appears in a later batch, the later value replaces the earlier one. + * - A rule that requires multiple addresses can be satisfied by data spread + * across different batches within a single call. + **/ +DDWAF_RET_CODE ddwaf_subcontext_multieval(ddwaf_subcontext subcontext, ddwaf_object *data, + ddwaf_allocator alloc, ddwaf_object *result, uint64_t timeout); + /** * Performs the destruction of the subcontext, freeing the data passed to it through * ddwaf_subcontext_eval using the used-defined allocator. diff --git a/libddwaf.def b/libddwaf.def index aff52982c..0badfb6fa 100644 --- a/libddwaf.def +++ b/libddwaf.def @@ -11,9 +11,11 @@ EXPORTS ddwaf_known_addresses ddwaf_context_init ddwaf_context_eval + ddwaf_context_multieval ddwaf_context_destroy ddwaf_subcontext_init ddwaf_subcontext_eval + ddwaf_subcontext_multieval ddwaf_subcontext_destroy ddwaf_object_destroy ddwaf_get_version diff --git a/smoketest/smoke.c b/smoketest/smoke.c index a49d69221..b659a46c4 100644 --- a/smoketest/smoke.c +++ b/smoketest/smoke.c @@ -273,5 +273,34 @@ int main() { puts("result is valid"); ddwaf_object_destroy(&result, alloc); + // Exercise the multieval entrypoint. It takes an array of input batches + // (each a map of addresses); the actual data is irrelevant here, this just + // verifies the symbol is exported and the library loads correctly. + ddwaf_context multi_ctx = ddwaf_context_init(handle, alloc); + if (!multi_ctx) { + puts("multi_ctx is null"); + return 1; + } + + ddwaf_object multi_data; + ddwaf_object_set_array(&multi_data, 1, alloc); + + ddwaf_object *batch = ddwaf_object_insert(&multi_data, alloc); + ddwaf_object_set_map(batch, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(batch, STRL("key"), alloc), STRL("Arachni"), alloc); + + ddwaf_object multi_result = {0}; + ddwaf_context_multieval(multi_ctx, &multi_data, alloc, &multi_result, (uint32_t)-1); + + const ddwaf_object *multi_events = + ddwaf_object_find(&multi_result, "events", sizeof("events") - 1); + if (ddwaf_object_get_size(multi_events) == 0) { + puts("multieval result is empty"); + return 1; + } + puts("multieval result is valid"); + ddwaf_object_destroy(&multi_result, alloc); + return 0; } diff --git a/src/context.hpp b/src/context.hpp index 1c498529f..9b1b1c950 100644 --- a/src/context.hpp +++ b/src/context.hpp @@ -46,16 +46,28 @@ class subcontext { subcontext &operator=(subcontext &&) noexcept = delete; subcontext &operator=(const subcontext &) = delete; - bool insert(owned_object data) + bool insert_batch(owned_object data) { const memory::memory_resource_guard guard(mr_.get()); - return engine_->insert(std::move(data)); + return engine_->insert_batch(std::move(data)); } - bool insert(map_view data) + bool insert_batch(map_view data) { const memory::memory_resource_guard guard(mr_.get()); - return engine_->insert(data); + return engine_->insert_batch(data); + } + + bool insert_batches(owned_object data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_batches(std::move(data)); + } + + bool insert_batches(array_view data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_batches(data); } std::pair eval(timer &deadline) @@ -65,6 +77,21 @@ class subcontext { } // Internals exposed for testing + bool next_batch() + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->next_batch(); + } + bool insert_and_apply(owned_object data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_and_apply(std::move(data)); + } + bool insert_and_apply(map_view data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_and_apply(data); + } void eval_preprocessors(timer &deadline) { const memory::memory_resource_guard guard(mr_.get()); @@ -141,16 +168,28 @@ class context { context &operator=(context &&) noexcept = delete; context &operator=(const context &) = delete; - bool insert(owned_object data) + bool insert_batch(owned_object data) { const memory::memory_resource_guard guard(mr_.get()); - return engine_->insert(std::move(data)); + return engine_->insert_batch(std::move(data)); } - bool insert(map_view data) + bool insert_batch(map_view data) { const memory::memory_resource_guard guard(mr_.get()); - return engine_->insert(data); + return engine_->insert_batch(data); + } + + bool insert_batches(owned_object data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_batches(std::move(data)); + } + + bool insert_batches(array_view data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_batches(data); } std::pair eval(timer &deadline) @@ -162,6 +201,21 @@ class context { subcontext create_subcontext() { return subcontext{*engine_, store_, mr_}; } // Internals exposed for testing + bool next_batch() + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->next_batch(); + } + bool insert_and_apply(owned_object data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_and_apply(std::move(data)); + } + bool insert_and_apply(map_view data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_and_apply(data); + } void eval_preprocessors(timer &deadline) { const memory::memory_resource_guard guard(mr_.get()); diff --git a/src/evaluation_engine.cpp b/src/evaluation_engine.cpp index 73046ed54..962744d6c 100644 --- a/src/evaluation_engine.cpp +++ b/src/evaluation_engine.cpp @@ -42,57 +42,63 @@ void set_context_event_address(object_store &store) return; } - store.insert(event_addr_idx, event_addr, owned_object::make_boolean(true)); + store.insert_target(event_addr_idx, event_addr, owned_object::make_boolean(true)); } } // namespace std::pair evaluation_engine::eval(timer &deadline) { - // Clear the last batch of targets on exit so that the process can identify - // new targets in the next eval - auto on_exit = defer([this]() { store_.clear_last_batch(); }); - result_serializer serializer(ruleset_->obfuscator.get(), *ruleset_->actions, output_alloc_); // Generate result object once relevant checks have been made auto [result_object, output] = serializer.initialise_result_object(); - if (!store_.has_new_targets()) { - return {false, std::move(result_object)}; - } - - try { - // Evaluate preprocessors first in their own try-catch, if there's a - // timeout we still need to evaluate rules unaffected by it. - eval_preprocessors(deadline); - // NOLINTNEXTLINE(bugprone-empty-catch) - } catch (const ddwaf::timeout_exception &) {} + // Once evaluation finishes (on any exit path, including a timeout) flush any + // input batches left unevaluated and reset the new-target set so that the + // next eval can identify new targets. + auto on_exit = defer([this]() { store_.flush_input_queue(); }); std::vector results; - - try { - // If no rule targets are available, there is no point in evaluating them - const bool should_eval_rules = check_new_rule_targets(); - const bool should_eval_filters = should_eval_rules || check_new_filter_targets(); - - if (should_eval_filters) { - // Filters need to be evaluated even if rules don't, otherwise it'll - // break the current condition cache mechanism which requires knowing - // if an address is new to this run. - const auto &policy = eval_filters(deadline); - - if (should_eval_rules) { - eval_rules(policy, results, deadline); - if (!results.empty()) { - set_context_event_address(store_); + std::size_t batches_evaluated = 0; + + // Each queued input batch is evaluated as if it were a separate eval call, + // draining the store's queue one batch at a time. + while (store_.next_batch()) { + try { + // Evaluate preprocessors first in their own try-catch, if there's a + // timeout we still need to evaluate rules unaffected by it. + eval_preprocessors(deadline); + // NOLINTNEXTLINE(bugprone-empty-catch) + } catch (const ddwaf::timeout_exception &) {} + + try { + // If no rule targets are available, there is no point in evaluating them + const bool should_eval_rules = check_new_rule_targets(); + const bool should_eval_filters = should_eval_rules || check_new_filter_targets(); + + if (should_eval_filters) { + // Filters need to be evaluated even if rules don't, otherwise it'll + // break the current condition cache mechanism which requires knowing + // if an address is new to this run. + const auto &policy = eval_filters(deadline); + + if (should_eval_rules) { + eval_rules(policy, results, deadline); + if (!results.empty()) { + set_context_event_address(store_); + } } } + + eval_postprocessors(deadline); + ++batches_evaluated; + } catch (const ddwaf::timeout_exception &) { + break; } + } - eval_postprocessors(deadline); - // NOLINTNEXTLINE(bugprone-empty-catch) - } catch (const ddwaf::timeout_exception &) {} + output.evaluated = owned_object::make_unsigned(batches_evaluated); // Collect pending attributes, this will check if any new attributes are // available (e.g. from a postprocessor) and return a map of all attributes diff --git a/src/evaluation_engine.hpp b/src/evaluation_engine.hpp index 2a19efa1a..5414eca73 100644 --- a/src/evaluation_engine.hpp +++ b/src/evaluation_engine.hpp @@ -48,23 +48,49 @@ class evaluation_engine { evaluation_engine &operator=(evaluation_engine &&) = delete; ~evaluation_engine() = default; - bool insert(owned_object data) noexcept + bool insert_batch(owned_object data) noexcept { - if (!store_.insert(std::move(data))) { + if (!store_.insert_batch(std::move(data))) { DDWAF_WARN("Illegal WAF call: parameter structure invalid!"); return false; } return true; } - bool insert(map_view data) noexcept + bool insert_batch(map_view data) noexcept { - if (!store_.insert(data)) { + if (!store_.insert_batch(data)) { DDWAF_WARN("Illegal WAF call: parameter structure invalid!"); return false; } return true; } + + bool insert_batches(owned_object data) noexcept + { + if (!store_.insert_batches(std::move(data))) { + DDWAF_WARN("Illegal WAF call: parameter structure invalid!"); + return false; + } + return true; + } + + bool insert_batches(array_view data) noexcept + { + if (!store_.insert_batches(data)) { + DDWAF_WARN("Illegal WAF call: parameter structure invalid!"); + return false; + } + return true; + } + + // Internals exposed for testing + bool next_batch() { return store_.next_batch(); } + bool insert_and_apply(owned_object data) noexcept + { + return store_.insert_and_apply(std::move(data)); + } + bool insert_and_apply(map_view data) noexcept { return store_.insert_and_apply(data); } std::pair eval(timer &deadline); static evaluation_engine context_engine(std::shared_ptr ruleset, object_store &store, diff --git a/src/interface.cpp b/src/interface.cpp index cd773fdce..e4932229c 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -263,15 +263,19 @@ DDWAF_RET_CODE ddwaf_context_eval(ddwaf_context context, ddwaf_object *data, try { if (alloc != nullptr) { - if (!context->insert( - // safety: caller is responsible to ensure that the passed - // allocator can deallocate memory allocated for `data` - owned_object{to_ref(data), to_alloc_ptr(alloc)})) { + // safety: caller is responsible to ensure that the passed allocator + // can deallocate memory allocated for `data`. An array carries + // multiple input batches, anything else is a single (map) batch. + owned_object input{to_ref(data), to_alloc_ptr(alloc)}; + const bool inserted = object_view{input}.is_array() + ? context->insert_batches(std::move(input)) + : context->insert_batch(std::move(input)); + if (!inserted) { return DDWAF_ERR_INVALID_OBJECT; } } else { const object_view input{to_ref(data)}; - if (!input.is_map() || !context->insert(input.as())) { + if (!input.is_map() || !context->insert_batch(input.as())) { return DDWAF_ERR_INVALID_OBJECT; } } @@ -299,6 +303,45 @@ DDWAF_RET_CODE ddwaf_context_eval(ddwaf_context context, ddwaf_object *data, return DDWAF_ERR_INTERNAL; } +DDWAF_RET_CODE ddwaf_context_multieval(ddwaf_context context, ddwaf_object *data, + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + ddwaf_allocator alloc, ddwaf_object *result, uint64_t timeout) +{ + if (context == nullptr || data == nullptr) { + DDWAF_WARN("Illegal WAF call: context or data was null"); + return DDWAF_ERR_INVALID_ARGUMENT; + } + + try { + if (alloc != nullptr) { + if (!context->insert_batches(owned_object{to_ref(data), to_alloc_ptr(alloc)})) { + return DDWAF_ERR_INVALID_OBJECT; + } + } else { + const object_view input{to_ref(data)}; + if (!input.is_array() || !context->insert_batches(input.as())) { + return DDWAF_ERR_INVALID_OBJECT; + } + } + + constexpr uint64_t max_timeout_us = std::chrono::nanoseconds::max().count() / 1000; + timeout = std::min(timeout, max_timeout_us); + + timer deadline{std::chrono::microseconds(timeout)}; + auto [code, res] = context->eval(deadline); + if (result != nullptr) { + *to_ptr(result) = res.move(); + } + return code ? DDWAF_MATCH : DDWAF_OK; + } catch (const std::exception &e) { + DDWAF_ERROR("{}", e.what()); + } catch (...) { + DDWAF_ERROR("unknown exception"); + } + + return DDWAF_ERR_INTERNAL; +} + void ddwaf_context_destroy(ddwaf_context context) { try { @@ -335,15 +378,19 @@ DDWAF_RET_CODE ddwaf_subcontext_eval(ddwaf_subcontext subcontext, ddwaf_object * try { if (alloc != nullptr) { - if (!subcontext->insert( - // safety: caller is responsible to ensure that the passed - // allocator can deallocate memory allocated for `data` - owned_object{to_ref(data), to_alloc_ptr(alloc)})) { + // safety: caller is responsible to ensure that the passed allocator + // can deallocate memory allocated for `data`. An array carries + // multiple input batches, anything else is a single (map) batch. + owned_object input{to_ref(data), to_alloc_ptr(alloc)}; + const bool inserted = object_view{input}.is_array() + ? subcontext->insert_batches(std::move(input)) + : subcontext->insert_batch(std::move(input)); + if (!inserted) { return DDWAF_ERR_INVALID_OBJECT; } } else { const object_view input{to_ref(data)}; - if (!input.is_map() || !subcontext->insert(input.as())) { + if (!input.is_map() || !subcontext->insert_batch(input.as())) { return DDWAF_ERR_INVALID_OBJECT; } } @@ -367,6 +414,45 @@ DDWAF_RET_CODE ddwaf_subcontext_eval(ddwaf_subcontext subcontext, ddwaf_object * return DDWAF_ERR_INTERNAL; } +DDWAF_RET_CODE ddwaf_subcontext_multieval(ddwaf_subcontext subcontext, ddwaf_object *data, + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + ddwaf_allocator alloc, ddwaf_object *result, uint64_t timeout) +{ + if (subcontext == nullptr || data == nullptr) { + DDWAF_WARN("Illegal WAF call: subcontext or data was null"); + return DDWAF_ERR_INVALID_ARGUMENT; + } + + try { + if (alloc != nullptr) { + if (!subcontext->insert_batches(owned_object{to_ref(data), to_alloc_ptr(alloc)})) { + return DDWAF_ERR_INVALID_OBJECT; + } + } else { + const object_view input{to_ref(data)}; + if (!input.is_array() || !subcontext->insert_batches(input.as())) { + return DDWAF_ERR_INVALID_OBJECT; + } + } + + constexpr uint64_t max_timeout_us = std::chrono::nanoseconds::max().count() / 1000; + timeout = std::min(timeout, max_timeout_us); + + timer deadline{std::chrono::microseconds(timeout)}; + auto [code, res] = subcontext->eval(deadline); + if (result != nullptr) { + *to_ptr(result) = res.move(); + } + return code ? DDWAF_MATCH : DDWAF_OK; + } catch (const std::exception &e) { + DDWAF_ERROR("{}", e.what()); + } catch (...) { + DDWAF_ERROR("unknown exception"); + } + + return DDWAF_ERR_INTERNAL; +} + void ddwaf_subcontext_destroy(ddwaf_subcontext subcontext) { try { diff --git a/src/object_store.cpp b/src/object_store.cpp index 81c4e1316..b43c8df51 100644 --- a/src/object_store.cpp +++ b/src/object_store.cpp @@ -14,26 +14,90 @@ namespace ddwaf { -bool object_store::insert(owned_object &&input) +bool object_store::insert_batch(owned_object &&input) { + // The input object retains ownership of the enqueued batch, so it must + // outlive the queue. const object_view view = input_objects_.emplace_back(std::move(input)); if (!view.is_map()) { return false; } - return insert(view); + enqueue_batch(view); + return true; } -bool object_store::insert(map_view input) +bool object_store::insert_batch(map_view input) { - const auto size = input.size(); - if (size == 0) { - // Objects with no addresses are considered valid as they are harmless - return true; + enqueue_batch(input); + return true; +} + +bool object_store::insert_batches(array_view input) +{ + // An array represents a sequence of input batches; every element must + // itself be a map of addresses - validate all of them before enqueueing so + // that a failure leaves the queue untouched. + for (auto element : input) { + if (!element.is_map()) { + return false; + } + } + + for (auto element : input) { enqueue_batch(element); } + + return true; +} + +bool object_store::insert_batches(owned_object &&input) +{ + // The input object retains ownership of every enqueued batch, so it must + // outlive the queue. + const object_view view = input_objects_.emplace_back(std::move(input)); + if (!view.is_array()) { + return false; + } + + return insert_batches(array_view{view}); +} + +void object_store::enqueue_batch(map_view input) +{ + // Batches with no addresses are considered valid as they are harmless + if (!input.empty()) { + object_queue_.emplace_back(input); + } +} + +bool object_store::next_batch() +{ + if (object_queue_.empty()) { + return false; + } + + apply_batch(object_queue_.front(), /*mark_new=*/true); + object_queue_.pop_front(); + return true; +} + +void object_store::flush_input_queue() +{ + if (!object_queue_.empty()) { + DDWAF_DEBUG("Flushing remaining queued objects"); + for (auto input : object_queue_) { apply_batch(input, /*mark_new=*/false); } + object_queue_.clear(); } + latest_batch_.clear(); +} + +void object_store::apply_batch(map_view input, bool mark_new) +{ + const auto size = input.size(); targets_.reserve(targets_.size() + size); - latest_batch_.reserve(latest_batch_.size() + size); + if (mark_new) { + latest_batch_.reserve(latest_batch_.size() + size); + } for (std::size_t i = 0; i < size; ++i) { auto [key_obj, value] = input.at(i); @@ -43,21 +107,24 @@ bool object_store::insert(map_view input) auto key = key_obj.as(); auto target = get_target_index(key); - insert_target_helper(target, key, value); - } - return true; + if (targets_.contains(target)) { + DDWAF_DEBUG("Replacing target '{}' in object store", key); + } else { + DDWAF_DEBUG("Inserting target '{}' into object store", key); + } + + targets_[target] = value; + if (mark_new) { + latest_batch_.emplace(target); + } + } } -bool object_store::insert(target_index target, std::string_view key, owned_object &&input) +bool object_store::insert_target(target_index target, std::string_view key, owned_object &&input) { const object_view view = input_objects_.emplace_back(std::move(input)); - return insert_target_helper(target, key, view); -} - -bool object_store::insert_target_helper(target_index target, std::string_view key, object_view view) -{ if (targets_.contains(target)) { DDWAF_DEBUG("Replacing target '{}' in object store", key); } else { diff --git a/src/object_store.hpp b/src/object_store.hpp index 15ea95235..00e106c71 100644 --- a/src/object_store.hpp +++ b/src/object_store.hpp @@ -23,9 +23,48 @@ class object_store { object_store &operator=(const object_store &other) = delete; object_store &operator=(object_store &&) = default; - bool insert(owned_object &&input); - bool insert(map_view input); - bool insert(target_index target, std::string_view key, owned_object &&input); + // Enqueue a single batch of input addresses (a map). The batch is not + // applied to the store until consumed via next_batch(); a batch with no + // addresses is accepted as a harmless no-op. Returns false if the object is + // not a map. + bool insert_batch(owned_object &&input); + bool insert_batch(map_view input); + + // Enqueue a sequence of input batches: an array whose every element is a + // map, each queued as a separate batch. Returns false if the object is not + // an array or any element is not a map. + bool insert_batches(owned_object &&input); + bool insert_batches(array_view input); + // Insert a single derived target (e.g. produced by a processor) directly, + // marking it as new. Unlike the batch insert overloads above this takes + // effect immediately rather than being queued. + bool insert_target(target_index target, std::string_view key, owned_object &&input); + + // Consume the next queued input batch, applying its addresses to the store + // and marking them as new. Returns false once the queue is drained. + bool next_batch(); + + // Enqueue and immediately apply a single batch. Used for testing only. + bool insert_and_apply(owned_object &&input) + { + if (!insert_batch(std::move(input))) { + return false; + } + next_batch(); + return true; + } + bool insert_and_apply(map_view input) + { + insert_batch(input); + next_batch(); + return true; + } + + // Apply any remaining queued batches as targets *without* marking them as + // new (so they won't be evaluated) and clear the new-target set. Invoked + // once evaluation finishes, on any exit path including a timeout, mirroring + // the inputs being carried over to a subsequent ddwaf_context_eval call. + void flush_input_queue(); [[nodiscard]] object_view get_target(target_index target) const { @@ -49,7 +88,6 @@ class object_store { } [[nodiscard]] bool has_new_targets() const { return !latest_batch_.empty(); } [[nodiscard]] bool empty() const { return targets_.empty(); } - void clear_last_batch() { latest_batch_.clear(); } // An object store created from an upstream store assumes that the original // store retains ownership and will outlive this store, therefore only the @@ -63,10 +101,16 @@ class object_store { } private: - bool insert_target_helper(target_index target, std::string_view key, object_view view); + // Append a single input batch to the queue, ignoring empty batches. + void enqueue_batch(map_view input); + + // Apply a single input batch's addresses to the store. When mark_new is + // true the addresses are added to the new-target set and will be evaluated. + void apply_batch(map_view input, bool mark_new); memory::list input_objects_; + memory::list object_queue_; memory::unordered_set latest_batch_; memory::unordered_map targets_; }; diff --git a/src/processor/base.hpp b/src/processor/base.hpp index eda4e3c03..2e8b346db 100644 --- a/src/processor/base.hpp +++ b/src/processor/base.hpp @@ -208,12 +208,13 @@ template class structured_processor : public base_processor { // If the object is to be evaluated, we clone it before adding it to the // collector using the user-provided allocator. collector.insert(mapping.output.name, object.clone(alloc)); - store.insert(mapping.output.index, mapping.output.name, std::move(object)); + store.insert_target( + mapping.output.index, mapping.output.name, std::move(object)); } else { collector.insert(mapping.output.name, std::move(object)); } } else { - store.insert(mapping.output.index, mapping.output.name, std::move(object)); + store.insert_target(mapping.output.index, mapping.output.name, std::move(object)); } } } diff --git a/src/serializer.cpp b/src/serializer.cpp index bfd29ebdf..3cd49cac2 100644 --- a/src/serializer.cpp +++ b/src/serializer.cpp @@ -373,7 +373,8 @@ std::pair result_serializer::initialise_result_ {"duration", owned_object::make_unsigned(0)}, {"timeout", owned_object::make_boolean(false)}, {"attributes", object_builder::map({}, alloc_)}, - {"keep", owned_object::make_boolean(false)}}, + {"keep", owned_object::make_boolean(false)}, + {"evaluated", owned_object::make_unsigned(0)}}, alloc_); const result_components res{.events = object.at(0), @@ -381,7 +382,8 @@ std::pair result_serializer::initialise_result_ .duration = object.at(2), .timeout = object.at(3), .attributes = object.at(4), - .keep = object.at(5)}; + .keep = object.at(5), + .evaluated = object.at(6)}; return {std::move(object), res}; } diff --git a/src/serializer.hpp b/src/serializer.hpp index 6a76a72ef..5e87dcfd3 100644 --- a/src/serializer.hpp +++ b/src/serializer.hpp @@ -28,6 +28,7 @@ struct result_components { borrowed_object timeout; borrowed_object attributes; borrowed_object keep; + borrowed_object evaluated; }; // NOLINTEND(cppcoreguidelines-avoid-const-or-ref-data-members) diff --git a/tests/integration/interface/context/multieval/test.cpp b/tests/integration/interface/context/multieval/test.cpp new file mode 100644 index 000000000..5d6eef13a --- /dev/null +++ b/tests/integration/interface/context/multieval/test.cpp @@ -0,0 +1,1033 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2021 Datadog, Inc. + +#include "common/gtest_utils.hpp" +#include "ddwaf.h" + +using namespace ddwaf; + +namespace { + +constexpr std::string_view base_dir = "integration/interface/context/multieval/"; + +//------------------------------------------------------------------------------ +// ddwaf_context_multieval tests +//------------------------------------------------------------------------------ + +TEST(TestContextMultievalIntegration, InvalidArgumentNullContext) +{ + auto *alloc = ddwaf_get_default_allocator(); + + ddwaf_object data; + ddwaf_object_set_array(&data, 2, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 0, alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(nullptr, &data, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_ARGUMENT); + + ddwaf_object_destroy(&data, alloc); +} + +TEST(TestContextMultievalIntegration, InvalidArgumentNullData) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, nullptr, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_ARGUMENT); + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, InvalidArgumentNotArray) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + // Pass a map instead of an array + ddwaf_object data; + ddwaf_object_set_map(&data, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_OBJECT); + + ddwaf_object_destroy(&data, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, SingleMapNoMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 1, alloc); + + ddwaf_object *elem = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem, STRL("value1"), alloc), STRL("no_match"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_OK); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 0); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 1); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, SingleMapWithMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 1, alloc); + + ddwaf_object *elem = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 1); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 1); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, MultipleMapsNoMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 3, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("no_match"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("no_match"), alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value3"), alloc), STRL("no_match"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_OK); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 0); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 3); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, MultipleMapsFirstMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 3, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("no_match"), alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value3"), alloc), STRL("no_match"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 1); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 3); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, MultipleMapsLastMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 3, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("no_match"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("no_match"), alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value3"), alloc), STRL("rule3"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 1); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 3); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, MultipleMapsAllMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 3, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("rule2"), alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value3"), alloc), STRL("rule3"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 3); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 3); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, NullResult) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 2, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("no_match"), alloc); + + // Pass nullptr for result - should still work + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, NullAlloc) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 2, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("no_match"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + // Pass nullptr for alloc - data should be treated as borrowed (not freed) + EXPECT_EQ(ddwaf_context_multieval(context, &data, nullptr, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 1); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 2); + + ddwaf_object_destroy(&result, alloc); + + // Manually destroy data since null alloc means context won't free it + ddwaf_object_destroy(&data, alloc); + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, ContextStateAccumulates) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + // First multieval call + ddwaf_object data1; + ddwaf_object_set_array(&data1, 1, alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data1, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result1; + ddwaf_object_set_invalid(&result1); + + EXPECT_EQ(ddwaf_context_multieval(context, &data1, alloc, &result1, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result1), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result1, STRL("events"))), 1); + ddwaf_object_destroy(&result1, alloc); + + // Second multieval call - same rule should not trigger again (already matched) + ddwaf_object data2; + ddwaf_object_set_array(&data2, 1, alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data2, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result2; + ddwaf_object_set_invalid(&result2); + + EXPECT_EQ(ddwaf_context_multieval(context, &data2, alloc, &result2, LONG_TIME), DDWAF_OK); + + ASSERT_EQ(ddwaf_object_get_type(&result2), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result2, STRL("events"))), 0); + ddwaf_object_destroy(&result2, alloc); + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, EmptyArray) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_OK); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 0); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 0); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, InvalidArgumentArrayContainsNonMap) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + // Array where the middle element is a string, not a map + ddwaf_object data; + ddwaf_object_set_array(&data, 3, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 0, alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_string(elem1, STRL("not_a_map"), alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem2, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_OBJECT); + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, SameRuleDoesNotDoubleFireWithinCall) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + // Both batches provide the same address with a matching value. + // rule1 fires in batch 1; in batch 2 value1 is overwritten (still matching), + // but the rule module cache prevents it from generating a second event. + ddwaf_object data; + ddwaf_object_set_array(&data, 2, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 1); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, CrossBatchDataCombination) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + // rule4 requires both value_a (matching "rule4a") AND value_b (matching "rule4b"). + // Batch 1 satisfies only the first condition; rule4 cannot fire yet. + // Batch 2 satisfies the second condition; the store still holds value_a from + // batch 1, so rule4 now fires. This is the defining semantic of multieval. + ddwaf_object data; + ddwaf_object_set_array(&data, 2, alloc); + + ddwaf_object *batch1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(batch1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(batch1, STRL("value_a"), alloc), STRL("rule4a"), alloc); + + ddwaf_object *batch2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(batch2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(batch2, STRL("value_b"), alloc), STRL("rule4b"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 1); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 2); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +//------------------------------------------------------------------------------ +// ddwaf_subcontext_multieval tests +//------------------------------------------------------------------------------ + +TEST(TestSubcontextMultievalIntegration, InvalidArgumentNullSubcontext) +{ + auto *alloc = ddwaf_get_default_allocator(); + + ddwaf_object data; + ddwaf_object_set_array(&data, 2, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 0, alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(nullptr, &data, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_ARGUMENT); + + ddwaf_object_destroy(&data, alloc); +} + +TEST(TestSubcontextMultievalIntegration, InvalidArgumentNullData) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, nullptr, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_ARGUMENT); + + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, InvalidArgumentNotArray) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + // Pass a map instead of an array + ddwaf_object data; + ddwaf_object_set_map(&data, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_OBJECT); + + ddwaf_object_destroy(&data, alloc); + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, SingleMapNoMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 1, alloc); + + ddwaf_object *elem = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem, STRL("value1"), alloc), STRL("no_match"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data, alloc, &result, LONG_TIME), DDWAF_OK); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 0); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 1); + + ddwaf_object_destroy(&result, alloc); + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, SingleMapWithMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 1, alloc); + + ddwaf_object *elem = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 1); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 1); + + ddwaf_object_destroy(&result, alloc); + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, MultipleMapsAllMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 3, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("rule2"), alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value3"), alloc), STRL("rule3"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 3); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 3); + + ddwaf_object_destroy(&result, alloc); + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, NullResult) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 2, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("no_match"), alloc); + + // Pass nullptr for result - should still work + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data, alloc, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, SubcontextStateAccumulates) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + // First multieval call + ddwaf_object data1; + ddwaf_object_set_array(&data1, 1, alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data1, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result1; + ddwaf_object_set_invalid(&result1); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data1, alloc, &result1, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result1), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result1, STRL("events"))), 1); + ddwaf_object_destroy(&result1, alloc); + + // Second multieval call - same rule should not trigger again + ddwaf_object data2; + ddwaf_object_set_array(&data2, 1, alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data2, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result2; + ddwaf_object_set_invalid(&result2); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data2, alloc, &result2, LONG_TIME), DDWAF_OK); + + ASSERT_EQ(ddwaf_object_get_type(&result2), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result2, STRL("events"))), 0); + ddwaf_object_destroy(&result2, alloc); + + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, MultipleSubcontextsIndependent) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + // Create two subcontexts + ddwaf_subcontext subctx1 = ddwaf_subcontext_init(context); + ASSERT_NE(subctx1, nullptr); + + ddwaf_subcontext subctx2 = ddwaf_subcontext_init(context); + ASSERT_NE(subctx2, nullptr); + + // Trigger rule1 in subctx1 + ddwaf_object data1; + ddwaf_object_set_array(&data1, 1, alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data1, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result1; + ddwaf_object_set_invalid(&result1); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx1, &data1, alloc, &result1, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result1), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result1, STRL("events"))), 1); + ddwaf_object_destroy(&result1, alloc); + + // Same rule should still trigger in subctx2 (independent state) + ddwaf_object data2; + ddwaf_object_set_array(&data2, 1, alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data2, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result2; + ddwaf_object_set_invalid(&result2); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx2, &data2, alloc, &result2, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result2), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result2, STRL("events"))), 1); + ddwaf_object_destroy(&result2, alloc); + + ddwaf_subcontext_destroy(subctx1); + ddwaf_subcontext_destroy(subctx2); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, InvalidArgumentArrayContainsNonMap) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 3, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 0, alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_string(elem1, STRL("not_a_map"), alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem2, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_OBJECT); + + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +} // namespace diff --git a/tests/integration/interface/context/multieval/yaml/rules.yaml b/tests/integration/interface/context/multieval/yaml/rules.yaml new file mode 100644 index 000000000..aba0457eb --- /dev/null +++ b/tests/integration/interface/context/multieval/yaml/rules.yaml @@ -0,0 +1,51 @@ +version: '2.1' +rules: + - id: rule1 + name: rule1 + tags: + type: flow1 + category: category1 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value1 + regex: rule1 + - id: rule2 + name: rule2 + tags: + type: flow2 + category: category2 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value2 + regex: rule2 + - id: rule3 + name: rule3 + tags: + type: flow3 + category: category3 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value3 + regex: rule3 + - id: rule4 + name: rule4 + tags: + type: flow4 + category: category4 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value_a + regex: rule4a + - operator: match_regex + parameters: + inputs: + - address: value_b + regex: rule4b diff --git a/tests/unit/attribute_collector_test.cpp b/tests/unit/attribute_collector_test.cpp index 5c30844ac..60a96fb33 100644 --- a/tests/unit/attribute_collector_test.cpp +++ b/tests/unit/attribute_collector_test.cpp @@ -63,7 +63,7 @@ TEST(TestAttributeCollector, CollectAvailableScalar) auto input = object_builder_da::map({{"input_address", expected}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; EXPECT_TRUE(collector.collect(store, get_target_index("input_address"), {}, "output_address")); @@ -85,7 +85,7 @@ TEST(TestAttributeCollector, CollectAvailableKeyPathScalar) object_builder_da::map({{"first", object_builder_da::map({{"second", expected}})}})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; std::vector> key_path{"first", "second"}; @@ -110,7 +110,7 @@ TEST(TestAttributeCollector, CollectAvailableKeyPathSingleValueArray) object_builder_da::map({{"second", object_builder_da::array({expected})}})}})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; std::vector> key_path{"first", "second"}; @@ -136,7 +136,7 @@ TEST(TestAttributeCollector, CollectAvailableKeyPathMultiValueArray) object_builder_da::array({expected, "value1", "value2"})}})}})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; std::vector> key_path{"first", "second"}; @@ -162,7 +162,7 @@ TEST(TestAttributeCollector, CollectAvailableKeyPathWithinArrayPositiveIndex) object_builder_da::array({"value0", expected, "value2"})}})}})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; std::vector> key_path{"first", "second", 1}; @@ -188,7 +188,7 @@ TEST(TestAttributeCollector, CollectAvailableKeyPathWithinArrayNegativeIndex) object_builder_da::array({"value0", expected, "value2"})}})}})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; std::vector> key_path{"first", "second", -2}; @@ -212,7 +212,7 @@ TEST(TestAttributeCollector, CollectUnavailableKeyPath) object_builder_da::map({{"third", "value"}})}})}})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; std::vector> key_path{"first", "second"}; @@ -240,7 +240,7 @@ TEST(TestAttributeCollector, CollectPendingKeyPathScalar) EXPECT_EQ(attributes.size(), 0); EXPECT_TRUE(collector.has_pending_attributes()); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); attributes = collector.get_available_attributes_and_reset(); EXPECT_FALSE(collector.has_pending_attributes()); @@ -259,7 +259,7 @@ TEST(TestAttributeCollector, CollectAvailableKeyPathInvalidValue) object_builder_da::map({{"second", object_builder_da::map()}})}})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; std::vector> key_path{"first", "second"}; @@ -278,7 +278,7 @@ TEST(TestAttributeCollector, CollectDuplicateScalar) auto input = object_builder_da::map({{"input_address", expected}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; EXPECT_TRUE(collector.collect(store, get_target_index("input_address"), {}, "output_address")); @@ -300,7 +300,7 @@ TEST(TestAttributeCollector, CollectAvailableScalarFromSingleValueArray) auto input = object_builder_da::map({{"input_address", object_builder_da::array({expected})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; EXPECT_TRUE(collector.collect(store, get_target_index("input_address"), {}, "output_address")); @@ -322,7 +322,7 @@ TEST(TestAttributeCollector, CollectAvailableScalarFromMultiValueArray) {{"input_address", object_builder_da::array({expected, "value1", "value2"})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; EXPECT_TRUE(collector.collect(store, get_target_index("input_address"), {}, "output_address")); @@ -343,7 +343,7 @@ TEST(TestAttributeCollector, CollectInvalidObjectFromArray) {{"input_address", object_builder_da::array({object_builder_da::map()})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; EXPECT_FALSE(collector.collect(store, get_target_index("input_address"), {}, "output_address")); @@ -374,7 +374,7 @@ TEST(TestAttributeCollector, CollectUnavailableScalar) std::string_view expected = "value"; auto input = object_builder_da::map({{"input_address", expected}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_FALSE(collector.has_pending_attributes()); @@ -409,7 +409,7 @@ TEST(TestAttributeCollector, CollectUnavailableScalarFromSingleValueArray) std::string_view expected = "value"; auto input = object_builder_da::map({{"input_address", object_builder_da::array({expected})}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_FALSE(collector.has_pending_attributes()); @@ -445,7 +445,7 @@ TEST(TestAttributeCollector, CollectUnavailableScalarFromMultiValueArray) auto input = object_builder_da::map( {{"input_address", object_builder_da::array({expected, "value1", "value2"})}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_FALSE(collector.has_pending_attributes()); @@ -482,7 +482,7 @@ TEST(TestAttributeCollector, CollectUnavailableKeyPathFromWithinArrayPositiveInd auto input = object_builder_da::map( {{"input_address", object_builder_da::array({expected, "value1", "value2"})}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_FALSE(collector.has_pending_attributes()); @@ -519,7 +519,7 @@ TEST(TestAttributeCollector, CollectUnavailableKeyPathFromWithinArrayNegativeInd auto input = object_builder_da::map( {{"input_address", object_builder_da::array({expected, "value1", "value2"})}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_FALSE(collector.has_pending_attributes()); @@ -553,7 +553,7 @@ TEST(TestAttributeCollector, CollectUnavailableInvalidObject) // the expected attribute auto input = object_builder_da::map({{"input_address", object_builder_da::array()}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_FALSE(collector.has_pending_attributes()); @@ -600,7 +600,7 @@ TEST(TestAttributeCollector, CollectMultipleUnavailableScalars) std::string_view expected = "value"; auto input = object_builder_da::map({{"input_address_0", expected}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); EXPECT_TRUE( collector.collect(store, get_target_index("input_address_2"), {}, "output_address_2")); @@ -622,7 +622,7 @@ TEST(TestAttributeCollector, CollectMultipleUnavailableScalars) std::string_view expected = "value"; auto input = object_builder_da::map({{"input_address_2", expected}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_TRUE(collector.has_pending_attributes()); @@ -642,7 +642,7 @@ TEST(TestAttributeCollector, CollectMultipleUnavailableScalars) std::string_view expected = "value"; auto input = object_builder_da::map({{"input_address_1", expected}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_FALSE(collector.has_pending_attributes()); diff --git a/tests/unit/condition/cmdi_detector_test.cpp b/tests/unit/condition/cmdi_detector_test.cpp index 12bf794e3..f1f2a9987 100644 --- a/tests/unit/condition/cmdi_detector_test.cpp +++ b/tests/unit/condition/cmdi_detector_test.cpp @@ -42,7 +42,7 @@ TEST(TestCmdiDetector, InvalidType) {{"server.sys.exec.cmd", object_builder_da::map()}, {"server.request.query", "whatever"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -57,7 +57,7 @@ TEST(TestCmdiDetector, EmptyResource) {"server.request.query", "whatever"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -104,7 +104,7 @@ TEST(TestCmdiDetector, NoInjection) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -141,7 +141,7 @@ TEST(TestCmdiDetector, NoExecutableInjection) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -195,7 +195,7 @@ TEST(TestCmdiDetector, NoShellInjection) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -229,7 +229,7 @@ TEST(TestCmdiDetector, ExecutableInjectionLinux) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -277,7 +277,7 @@ TEST(TestCmdiDetector, ExecutableInjectionWindows) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -320,7 +320,7 @@ TEST(TestCmdiDetector, ExecutableWithSpacesInjection) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -546,7 +546,7 @@ TEST(TestCmdiDetector, LinuxShellInjection) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -633,7 +633,7 @@ TEST(TestCmdiDetector, WindowsShellInjection) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -670,7 +670,7 @@ TEST(TestCmdiDetector, ExecutableInjectionMultipleArguments) for (const auto &[key, value] : params) { map.emplace(key, value); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -705,7 +705,7 @@ TEST(TestCmdiDetector, EmptyExecutable) for (const auto &[key, value] : params) { map.emplace(key, value); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -731,7 +731,7 @@ TEST(TestCmdiDetector, ShellInjectionMultipleArguments) for (const auto &[key, value] : params) { map.emplace(key, value); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/condition/exists_condition_test.cpp b/tests/unit/condition/exists_condition_test.cpp index f5a8d5266..1ee07ac17 100644 --- a/tests/unit/condition/exists_condition_test.cpp +++ b/tests/unit/condition/exists_condition_test.cpp @@ -27,7 +27,7 @@ TEST(TestExistsCondition, AddressAvailable) auto root = object_builder_da::map({{"server.request.uri_raw", owned_object{}}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -46,7 +46,7 @@ TEST(TestExistsCondition, KeyPathAvailable) {{"to", object_builder_da::map({{"object", owned_object{}}})}})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -60,7 +60,7 @@ TEST(TestExistsCondition, AddressNotAvaialble) auto root = object_builder_da::map({{"server.request.query", owned_object{}}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -77,7 +77,7 @@ TEST(TestExistsCondition, KeyPathNotAvailable) object_builder_da::map({{"path", object_builder_da::map({{"to", owned_object{}}})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -94,7 +94,7 @@ TEST(TestExistsCondition, KeyPathPositiveIndexOnArray) object_builder_da::map({{"path", object_builder_da::array({"item"})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -111,7 +111,7 @@ TEST(TestExistsCondition, KeyPathNegativeIndexOnArray) object_builder_da::map({{"path", object_builder_da::array({"item"})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -128,7 +128,7 @@ TEST(TestExistsCondition, KeyPathIndexOnNonArray) {{"server.request.uri_raw", object_builder_da::map({{"path", owned_object{}}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -148,7 +148,7 @@ TEST(TestExistsCondition, KeyPathAvailableButExcluded) std::unordered_set excluded = {root.at(0)}; object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -168,7 +168,7 @@ TEST(TestExistsCondition, MultipleAddresses) auto root = object_builder_da::map({{address, owned_object{}}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -202,7 +202,7 @@ TEST(TestExistsCondition, MultipleAddressesAndKeyPaths) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -233,7 +233,7 @@ TEST(TestNegatedExistsCondition, KeyPathAvailable) {{"to", object_builder_da::map({{"object", owned_object{}}})}})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -250,7 +250,7 @@ TEST(TestNegativeExistsCondition, KeyPathAvailablePositiveIndexOnArray) object_builder_da::map({{"path", object_builder_da::array({"item"})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -267,7 +267,7 @@ TEST(TestNegativeExistsCondition, KeyPathAvailableNegativeIndexOnArray) object_builder_da::map({{"path", object_builder_da::array({"item"})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -284,7 +284,7 @@ TEST(TestNegativeExistsCondition, KeyPathUnvailablePositiveIndexOnArray) object_builder_da::map({{"path", object_builder_da::array({})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -301,7 +301,7 @@ TEST(TestNegativeExistsCondition, KeyPathUnvailableNegativeIndexOnArray) object_builder_da::map({{"path", object_builder_da::array({})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -317,7 +317,7 @@ TEST(TestNegatedExistsCondition, KeyPathNotAvailable) auto root = object_builder_da::map({{"server.request.uri_raw", object_builder_da::map({{"path", object_builder_da::map({{"to", owned_object{}}})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -338,7 +338,7 @@ TEST(TestNegatedExistsCondition, KeyPathAvailableButExcluded) std::unordered_set excluded = {root.at(0)}; object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/condition/lfi_detector_test.cpp b/tests/unit/condition/lfi_detector_test.cpp index 31c842c38..46a3ec62d 100644 --- a/tests/unit/condition/lfi_detector_test.cpp +++ b/tests/unit/condition/lfi_detector_test.cpp @@ -39,7 +39,7 @@ TEST(TestLFIDetector, MatchBasicUnix) object_builder_da::map({{"server.io.fs.file", path}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -101,7 +101,7 @@ TEST(TestLFIDetector, MatchBasicWindows) object_builder_da::map({{"server.io.fs.file", path}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -129,7 +129,7 @@ TEST(TestLFIDetector, MatchWithKeyPath) server.request.query: {array: [ {map: ../etc/passwd}]}})"); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -155,13 +155,13 @@ TEST(TestLFIDetector, PartialSubcontextMatch) { auto root = object_builder_da::map({{"server.io.fs.file", "/var/www/html/../../../etc/passwd"}}); - ctx_store.insert(std::move(root)); + ctx_store.insert_and_apply(std::move(root)); } auto sctx_store = object_store::from_upstream_store(ctx_store); { auto root = object_builder_da::map({{"server.request.query", "../../../etc/passwd"}}); - sctx_store.insert(std::move(root)); + sctx_store.insert_and_apply(std::move(root)); } ddwaf::timer deadline{2s}; @@ -199,7 +199,7 @@ TEST(TestLFIDetector, NoMatchUnix) object_builder_da::map({{"server.io.fs.file", path}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -236,7 +236,7 @@ TEST(TestLFIDetector, NoMatchWindows) object_builder_da::map({{"server.io.fs.file", path}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -258,7 +258,7 @@ TEST(TestLFIDetector, NoMatchExcludedPath) std::unordered_set exclusion{params_map.at(0)}; object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -276,7 +276,7 @@ TEST(TestLFIDetector, NoMatchExcludedAddress) std::unordered_set exclusion{root.at(1)}; object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -294,7 +294,7 @@ TEST(TestLFIDetector, Timeout) std::unordered_set exclusion{root.at(1)}; object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{0s}; condition_cache cache; @@ -311,7 +311,7 @@ TEST(TestLFIDetector, NoParams) }); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{0s}; condition_cache cache; diff --git a/tests/unit/condition/negated_scalar_condition_test.cpp b/tests/unit/condition/negated_scalar_condition_test.cpp index 40a3a8c95..6b4b338c6 100644 --- a/tests/unit/condition/negated_scalar_condition_test.cpp +++ b/tests/unit/condition/negated_scalar_condition_test.cpp @@ -53,7 +53,7 @@ TEST(TestNegatedScalarCondition, NoMatch) auto root = object_builder_da::map({{"server.request.uri.raw", "hello"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -73,7 +73,7 @@ TEST(TestNegatedScalarCondition, NoMatchWithKeyPath) object_builder_da::map({{"object", object_builder_da::array({{"bye"}})}})}})}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -88,7 +88,7 @@ TEST(TestNegatedScalarCondition, Timeout) auto root = object_builder_da::map({{"server.request.uri.raw", "hello"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{0s}; condition_cache cache; @@ -103,7 +103,7 @@ TEST(TestNegatedScalarCondition, SimpleMatch) auto root = object_builder_da::map({{"server.request.uri.raw", "bye"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -127,7 +127,7 @@ TEST(TestNegatedScalarCondition, SimpleMatchWithKeyPath) object_builder_da::map({{"to", object_builder_da::map({{"object", "bye"}})}})}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -149,7 +149,7 @@ TEST(TestNegatedScalarCondition, SingleValueArrayMatch) object_builder_da::map({{"server.request.uri.raw", object_builder_da::array({"bye"})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -175,7 +175,7 @@ TEST(TestNegatedScalarCondition, SingleValueArrayMatchWithKeyPath) object_builder_da::map({{"object", object_builder_da::array({"bye"})}})}})}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -197,7 +197,7 @@ TEST(TestNegatedScalarCondition, MultiValueArrayMatch) {{"server.request.uri.raw", object_builder_da::array({"bye", "greetings"})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -224,7 +224,7 @@ TEST(TestNegatedScalarCondition, MultiValueArrayMatchWithKeyPath) object_builder_da::array({"bye", "greetings"})}})}})}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -252,7 +252,7 @@ TEST(TestNegatedScalarCondition, ExcludedRootObject) object_builder_da::array({"bye", "greetings"})}})}})}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); std::unordered_set excluded_objects; excluded_objects.emplace(store.get_target(target_index)); @@ -281,7 +281,7 @@ TEST(TestNegatedScalarCondition, ExcludedIntermediateObject) excluded_objects.emplace(object_view{root}.find_key_path(kp).at_value(0)); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -307,7 +307,7 @@ TEST(TestNegatedScalarCondition, ExcludedFinalObject) std::unordered_set excluded_objects; excluded_objects.emplace(object_view{root}.find_key_path(kp).at_value(0)); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -326,14 +326,14 @@ TEST(TestNegatedScalarCondition, CachedMatch) { object_store store; - store.insert(root); + store.insert_and_apply(root); ASSERT_TRUE(cond.eval(cache, store, {}, {}, deadline)); } { object_store store; - store.insert(root); + store.insert_and_apply(root); ASSERT_FALSE(cond.eval(cache, store, {}, {}, deadline)); } @@ -351,7 +351,7 @@ TEST(TestNegatedScalarCondition, SimpleMatchOnKeys) {{"server.request.uri.raw", object_builder_da::map({{"bye", "hello"}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -368,7 +368,7 @@ TEST(TestNegatedScalarCondition, SimplesubcontextMatch) object_store ctx_store; { auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(root); + sctx_store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -377,7 +377,7 @@ TEST(TestNegatedScalarCondition, SimplesubcontextMatch) { auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(root); + sctx_store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/condition/scalar_condition_test.cpp b/tests/unit/condition/scalar_condition_test.cpp index 23c708a48..5f763eb06 100644 --- a/tests/unit/condition/scalar_condition_test.cpp +++ b/tests/unit/condition/scalar_condition_test.cpp @@ -44,7 +44,7 @@ TEST(TestScalarCondition, NoMatch) auto root = object_builder_da::map({{"server.request.uri.raw", owned_object{}}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -59,7 +59,7 @@ TEST(TestScalarCondition, Timeout) auto root = object_builder_da::map({{"server.request.uri.raw", owned_object{}}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{0s}; condition_cache cache; @@ -74,7 +74,7 @@ TEST(TestScalarCondition, SimpleMatch) auto root = object_builder_da::map({{"server.request.uri.raw", "hello"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -93,14 +93,14 @@ TEST(TestScalarCondition, CachedMatch) { object_store store; - store.insert(root); + store.insert_and_apply(root); ASSERT_TRUE(cond.eval(cache, store, {}, {}, deadline)); } { object_store store; - store.insert(root); + store.insert_and_apply(root); ASSERT_FALSE(cond.eval(cache, store, {}, {}, deadline)); } @@ -118,7 +118,7 @@ TEST(TestScalarCondition, SimpleMatchOnKeys) {{"server.request.uri.raw", object_builder_da::map({{"hello", "hello"}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/condition/shi_detector_array_test.cpp b/tests/unit/condition/shi_detector_array_test.cpp index d7e7b404e..504cf19af 100644 --- a/tests/unit/condition/shi_detector_array_test.cpp +++ b/tests/unit/condition/shi_detector_array_test.cpp @@ -26,7 +26,7 @@ TEST(TestShiDetectorArray, InvalidType) {{"server.sys.shell.cmd", object_builder_da::map()}, {"server.request.query", "whatever"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -41,7 +41,7 @@ TEST(TestShiDetectorArray, EmptyResource) {"server.request.query", "whatever"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -56,7 +56,7 @@ TEST(TestShiDetectorArray, InvalidTypeWithinArray) object_builder_da::map(), "cat /etc/passwd"})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -112,7 +112,7 @@ TEST(TestShiDetectorArray, NoMatchAndFalsePositives) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -155,7 +155,7 @@ TEST(TestShiDetectorArray, ExecutablesAndRedirections) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -208,7 +208,7 @@ TEST(TestShiDetectorArray, OverlappingInjections) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -247,7 +247,7 @@ TEST(TestShiDetectorArray, InjectionsWithinCommandSubstitution) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -290,7 +290,7 @@ TEST(TestShiDetectorArray, InjectionsWithinProcessSubstitution) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -339,7 +339,7 @@ TEST(TestShiDetectorArray, OffByOnePayloadsMatch) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/condition/shi_detector_string_test.cpp b/tests/unit/condition/shi_detector_string_test.cpp index fce62b2d9..c78b481c9 100644 --- a/tests/unit/condition/shi_detector_string_test.cpp +++ b/tests/unit/condition/shi_detector_string_test.cpp @@ -28,7 +28,7 @@ TEST(TestShiDetectorString, InvalidType) {"server.request.query", "whatever"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -43,7 +43,7 @@ TEST(TestShiDetectorString, EmptyResource) {{"server.sys.shell.cmd", ""}, {"server.request.query", "whatever"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -83,7 +83,7 @@ TEST(TestShiDetectorString, NoMatchAndFalsePositives) }); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -113,7 +113,7 @@ TEST(TestShiDetectorString, ExecutablesAndRedirections) }); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -155,7 +155,7 @@ TEST(TestShiDetectorString, InjectionsWithinCommandSubstitution) }); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -190,7 +190,7 @@ TEST(TestShiDetectorString, InjectionsWithinProcessSubstitution) }); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -227,7 +227,7 @@ TEST(TestShiDetectorString, OffByOnePayloadsMatch) }); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -285,7 +285,7 @@ TEST(TestShiDetectorString, MultipleArgumentsMatch) {"server.request.query", yaml_to_object(params)}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/condition/sqli_detector_test.cpp b/tests/unit/condition/sqli_detector_test.cpp index 54c30300c..a45b3d36f 100644 --- a/tests/unit/condition/sqli_detector_test.cpp +++ b/tests/unit/condition/sqli_detector_test.cpp @@ -39,7 +39,7 @@ TEST_P(DialectTestFixture, InvalidSql) {"server.db.system", dialect}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -63,7 +63,7 @@ TEST_P(DialectTestFixture, InjectionWithoutTokens) {"server.db.system", dialect}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -99,7 +99,7 @@ TEST_P(DialectTestFixture, BenignInjections) {"server.db.system", dialect}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -148,7 +148,7 @@ TEST_P(DialectTestFixture, MaliciousInjections) {"server.db.system", dialect}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -217,7 +217,7 @@ TEST_P(DialectTestFixture, Tautologies) {"server.db.system", dialect}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -260,7 +260,7 @@ TEST_P(DialectTestFixture, Comments) {"server.db.system", dialect}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -300,7 +300,7 @@ TEST(TestSqliDetectorMySql, Comments) {"server.db.system", "mysql"}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -343,7 +343,7 @@ TEST(TestSqliDetectorMySql, Tautologies) {"server.db.system", "mysql"}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -389,7 +389,7 @@ TEST(TestSqliDetectorPgSql, Tautologies) {"server.db.system", "pgsql"}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/condition/ssrf_detector_test.cpp b/tests/unit/condition/ssrf_detector_test.cpp index 636bf98e2..5bffe5fdc 100644 --- a/tests/unit/condition/ssrf_detector_test.cpp +++ b/tests/unit/condition/ssrf_detector_test.cpp @@ -53,7 +53,7 @@ void match_path_and_input(const std::vector> {"server.request.query", yaml_to_object(sample.yaml)}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/evaluation_engine_test.cpp b/tests/unit/evaluation_engine_test.cpp index 28f474279..f1e40a0b6 100644 --- a/tests/unit/evaluation_engine_test.cpp +++ b/tests/unit/evaluation_engine_test.cpp @@ -40,7 +40,7 @@ TEST(TestEvaluationEngine, MatchTimeout) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; EXPECT_THROW(ctx.eval_rules({}, results, deadline), ddwaf::timeout_exception); @@ -64,7 +64,7 @@ TEST(TestEvaluationEngine, NoMatch) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.2"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -89,7 +89,7 @@ TEST(TestEvaluationEngine, Match) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -130,7 +130,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesInCollectionSingleRun) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -195,7 +195,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesWithPrioritySingleRun) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; std::vector results; @@ -216,7 +216,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesWithPrioritySingleRun) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; std::vector results; @@ -268,7 +268,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesInCollectionDoubleRun) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -296,7 +296,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesInCollectionDoubleRun) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -340,7 +340,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -370,7 +370,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) // An existing match in a collection will not inhibit a match in a // priority collection. auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -435,7 +435,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -465,7 +465,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) // An existing match in a collection will not inhibit a match in a // priority collection. auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -508,7 +508,7 @@ TEST(TestEvaluationEngine, MatchMultipleCollectionsSingleRun) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -553,7 +553,7 @@ TEST(TestEvaluationEngine, MatchPriorityCollectionsSingleRun) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -595,7 +595,7 @@ TEST(TestEvaluationEngine, MatchMultipleCollectionsDoubleRun) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -604,7 +604,7 @@ TEST(TestEvaluationEngine, MatchMultipleCollectionsDoubleRun) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -650,7 +650,7 @@ TEST(TestEvaluationEngine, MatchMultiplePriorityCollectionsDoubleRun) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -659,7 +659,7 @@ TEST(TestEvaluationEngine, MatchMultiplePriorityCollectionsDoubleRun) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -703,7 +703,7 @@ TEST(TestEvaluationEngine, RuleFilterWithCondition) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto rules_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(rules_to_exclude.size(), 1); @@ -751,10 +751,10 @@ TEST(TestEvaluationEngine, RuleFilterWithSubcontextConditionMatch) auto persistent = object_builder_da::map({{"usr.id", "admin"}}); auto ephemeral = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - EXPECT_TRUE(ctx.insert(std::move(persistent))); + EXPECT_TRUE(ctx.insert_batch(std::move(persistent))); auto sctx = ctx.create_subcontext(); - EXPECT_TRUE(sctx.insert(std::move(ephemeral))); + EXPECT_TRUE(sctx.insert_batch(std::move(ephemeral))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = sctx.eval(deadline); @@ -763,7 +763,7 @@ TEST(TestEvaluationEngine, RuleFilterWithSubcontextConditionMatch) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - EXPECT_TRUE(ctx.insert(std::move(root))); + EXPECT_TRUE(ctx.insert_batch(std::move(root))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = ctx.eval(deadline); EXPECT_EQ(code, DDWAF_MATCH); @@ -820,10 +820,10 @@ TEST(TestEvaluationEngine, OverlappingRuleFiltersSubcontextBypassPersistentMonit auto persistent = object_builder_da::map({{"usr.id", "admin"}, {"http.route", "unrouted"}}); auto ephemeral = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - EXPECT_TRUE(ctx.insert(std::move(persistent))); + EXPECT_TRUE(ctx.insert_batch(std::move(persistent))); auto sctx = ctx.create_subcontext(); - EXPECT_TRUE(sctx.insert(std::move(ephemeral))); + EXPECT_TRUE(sctx.insert_batch(std::move(ephemeral))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = sctx.eval(deadline); @@ -832,7 +832,7 @@ TEST(TestEvaluationEngine, OverlappingRuleFiltersSubcontextBypassPersistentMonit { auto root = object_builder_da::map({{"usr.id", "admin"}}); - EXPECT_TRUE(ctx.insert(std::move(root))); + EXPECT_TRUE(ctx.insert_batch(std::move(root))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = ctx.eval(deadline); @@ -892,10 +892,10 @@ TEST(TestEvaluationEngine, OverlappingRuleFiltersSubcontextMonitorPersistentBypa auto persistent = object_builder_da::map({{"usr.id", "admin"}, {"http.route", "unrouted"}}); auto ephemeral = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - EXPECT_TRUE(ctx.insert(std::move(persistent))); + EXPECT_TRUE(ctx.insert_batch(std::move(persistent))); auto sctx = ctx.create_subcontext(); - EXPECT_TRUE(sctx.insert(std::move(ephemeral))); + EXPECT_TRUE(sctx.insert_batch(std::move(ephemeral))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = sctx.eval(deadline); @@ -904,7 +904,7 @@ TEST(TestEvaluationEngine, OverlappingRuleFiltersSubcontextMonitorPersistentBypa { auto root = object_builder_da::map({{"usr.id", "admin"}}); - EXPECT_TRUE(ctx.insert(std::move(root))); + EXPECT_TRUE(ctx.insert_batch(std::move(root))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = ctx.eval(deadline); @@ -948,7 +948,7 @@ TEST(TestEvaluationEngine, RuleFilterTimeout) auto root = object_builder_da::map({{"usr.id", "admin"}, {"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); EXPECT_THROW(ctx.eval_filters(deadline), ddwaf::timeout_exception); } @@ -989,7 +989,7 @@ TEST(TestEvaluationEngine, NoRuleFilterWithCondition) auto root = object_builder_da::map({{"usr.id", "admin"}, {"http.client_ip", "192.168.0.2"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto rules_to_exclude = ctx.eval_filters(deadline); EXPECT_TRUE(rules_to_exclude.empty()); @@ -1221,7 +1221,7 @@ TEST(TestEvaluationEngine, MultipleRuleFiltersNonOverlappingRulesWithConditions) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto rules_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(rules_to_exclude.size(), 5); @@ -1234,7 +1234,7 @@ TEST(TestEvaluationEngine, MultipleRuleFiltersNonOverlappingRulesWithConditions) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto rules_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(rules_to_exclude.size(), 10); @@ -1298,7 +1298,7 @@ TEST(TestEvaluationEngine, MultipleRuleFiltersOverlappingRulesWithConditions) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto rules_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(rules_to_exclude.size(), 7); @@ -1313,7 +1313,7 @@ TEST(TestEvaluationEngine, MultipleRuleFiltersOverlappingRulesWithConditions) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto rules_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(rules_to_exclude.size(), 10); @@ -1355,7 +1355,7 @@ TEST(TestEvaluationEngine, InputFilterExclude) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 1); @@ -1393,7 +1393,7 @@ TEST(TestEvaluationEngine, InputFilterExcludeSubcontext) auto sctx = ctx.create_subcontext(); - EXPECT_TRUE(sctx.insert(std::move(root))); + EXPECT_TRUE(sctx.insert_batch(std::move(root))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = sctx.eval(deadline); EXPECT_EQ(code, DDWAF_OK); @@ -1404,7 +1404,7 @@ TEST(TestEvaluationEngine, InputFilterExcludeSubcontext) auto sctx = ctx.create_subcontext(); - EXPECT_TRUE(sctx.insert(std::move(root))); + EXPECT_TRUE(sctx.insert_batch(std::move(root))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = sctx.eval(deadline); EXPECT_EQ(code, DDWAF_OK); @@ -1415,7 +1415,7 @@ TEST(TestEvaluationEngine, InputFilterExcludeSubcontext) auto sctx = ctx.create_subcontext(); - EXPECT_TRUE(sctx.insert(std::move(root))); + EXPECT_TRUE(sctx.insert_batch(std::move(root))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = sctx.eval(deadline); EXPECT_EQ(code, DDWAF_MATCH); @@ -1448,7 +1448,7 @@ TEST(TestEvaluationEngine, InputFilterExcludeRule) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); // The rule is added to the filter stage so that it's excluded from the // final result, since we're not actually excluding the rule from the match @@ -1493,7 +1493,7 @@ TEST(TestEvaluationEngine, InputFilterExcludeRuleSubcontext) auto sctx = ctx.create_subcontext(); - sctx.insert(std::move(root)); + sctx.insert_and_apply(std::move(root)); auto policy = sctx.eval_filters(deadline); EXPECT_EQ(policy.size(), 1); @@ -1531,7 +1531,7 @@ TEST(TestEvaluationEngine, InputFilterMonitorRuleSubcontext) auto sctx = ctx.create_subcontext(); - sctx.insert(std::move(root)); + sctx.insert_and_apply(std::move(root)); auto policy = sctx.eval_filters(deadline); EXPECT_EQ(policy.size(), 1); @@ -1567,14 +1567,14 @@ TEST(TestEvaluationEngine, InputFilterExcluderRuleSubcontextAndPersistent) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); } auto sctx = ctx.create_subcontext(); { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - sctx.insert(std::move(root)); + sctx.insert_and_apply(std::move(root)); } auto objects_to_exclude = sctx.eval_filters(deadline); @@ -1611,14 +1611,14 @@ TEST(TestEvaluationEngine, InputFilterMonitorRuleSubcontextAndPersistent) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); } auto sctx = ctx.create_subcontext(); { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - sctx.insert(std::move(root)); + sctx.insert_and_apply(std::move(root)); } auto objects_to_exclude = sctx.eval_filters(deadline); @@ -1668,7 +1668,7 @@ TEST(TestEvaluationEngine, InputFilterWithCondition) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 0); @@ -1685,7 +1685,7 @@ TEST(TestEvaluationEngine, InputFilterWithCondition) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admino"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 0); @@ -1702,7 +1702,7 @@ TEST(TestEvaluationEngine, InputFilterWithCondition) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 1); @@ -1751,11 +1751,11 @@ TEST(TestEvaluationEngine, InputFilterWithSubcontextCondition) auto persistent = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); auto ephemeral = object_builder_da::map({{"usr.id", "admin"}}); - EXPECT_TRUE(ctx.insert(std::move(persistent))); + EXPECT_TRUE(ctx.insert_batch(std::move(persistent))); auto sctx = ctx.create_subcontext(); - EXPECT_TRUE(sctx.insert(std::move(ephemeral))); + EXPECT_TRUE(sctx.insert_batch(std::move(ephemeral))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = sctx.eval(deadline); EXPECT_EQ(code, DDWAF_OK); @@ -1763,7 +1763,7 @@ TEST(TestEvaluationEngine, InputFilterWithSubcontextCondition) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - EXPECT_TRUE(ctx.insert(std::move(root))); + EXPECT_TRUE(ctx.insert_batch(std::move(root))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = ctx.eval(deadline); EXPECT_EQ(code, DDWAF_MATCH); @@ -1817,7 +1817,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRules) context ctx{rbuilder.build()}; auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 2); @@ -1837,7 +1837,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRules) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admino"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 2); @@ -1857,7 +1857,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRules) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 2); @@ -1925,7 +1925,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFilters) context ctx{rbuilder.build()}; auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 1); @@ -1946,7 +1946,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFilters) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admino"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 2); @@ -1967,7 +1967,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFilters) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 2); @@ -2066,7 +2066,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFiltersMultipleObject context ctx{rbuilder.build()}; auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(object_view{root}); + ctx.insert_and_apply(object_view{root}); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 3); @@ -2086,7 +2086,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFiltersMultipleObject context ctx{rbuilder.build()}; auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(object_view{root}); + ctx.insert_and_apply(object_view{root}); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 3); @@ -2107,7 +2107,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFiltersMultipleObject auto root = object_builder_da::map( {{"server.request.headers", object_builder_da::map({{"cookie", "mycookie"}})}}); - ctx.insert(object_view{root}); + ctx.insert_and_apply(object_view{root}); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 3); @@ -2128,7 +2128,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFiltersMultipleObject auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(object_view{root}); + ctx.insert_and_apply(object_view{root}); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 3); @@ -2150,7 +2150,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFiltersMultipleObject auto root = object_builder_da::map( {{"server.request.headers", object_builder_da::map({{"cookie", "mycookie"}})}, {"usr.id", "admin"}}); - ctx.insert(object_view{root}); + ctx.insert_and_apply(object_view{root}); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 3); @@ -2172,7 +2172,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFiltersMultipleObject auto root = object_builder_da::map( {{"server.request.headers", object_builder_da::map({{"cookie", "mycookie"}})}, {"usr.id", "admin"}, {"http.client_ip", "192.168.0.1"}}); - ctx.insert(object_view{root}); + ctx.insert_and_apply(object_view{root}); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 3); diff --git a/tests/unit/exclusion/input_filter_test.cpp b/tests/unit/exclusion/input_filter_test.cpp index 064c4ea11..50988ab1f 100644 --- a/tests/unit/exclusion/input_filter_test.cpp +++ b/tests/unit/exclusion/input_filter_test.cpp @@ -19,7 +19,7 @@ TEST(TestInputFilter, InputExclusionNoConditions) object_store store; auto root = object_builder_da::map({{"query", "value"}}); - store.insert(root); + store.insert_and_apply(root); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {}); @@ -45,7 +45,7 @@ TEST(TestInputFilter, ObjectExclusionNoConditions) auto child = root.emplace("query", object_builder_da::map()); child.emplace("params", "param"); - store.insert(root); + store.insert_and_apply(root); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); @@ -73,7 +73,7 @@ TEST(TestInputFilter, PersistentInputExclusionWithPersistentCondition) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip", {}); @@ -101,7 +101,7 @@ TEST(TestInputFilter, InputExclusionWithConditionAndTransformers) auto root = object_builder_da::map({{"usr.id", "ADMIN"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("usr.id"), "usr.id", {}); @@ -129,7 +129,7 @@ TEST(TestInputFilter, InputExclusionFailedCondition) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.2"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip", {}); @@ -158,7 +158,7 @@ TEST(TestInputFilter, ObjectExclusionWithCondition) auto child = root.emplace("query", object_builder_da::map({{"params", "value"}})); object_store store; - store.insert(root); + store.insert_and_apply(root); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); @@ -188,7 +188,7 @@ TEST(TestInputFilter, ObjectExclusionFailedCondition) {"query", object_builder_da::map({{"params", "value"}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); @@ -229,7 +229,7 @@ TEST(TestInputFilter, InputValidateCachedMatch) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline).has_value()); @@ -239,7 +239,7 @@ TEST(TestInputFilter, InputValidateCachedMatch) auto root = object_builder_da::map({{"usr.id", "admin"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; auto opt_spec = filter.match(store, cache, {}, deadline); @@ -277,19 +277,19 @@ TEST(TestInputFilter, InputValidateCachedSubcontextMatch) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; - ctx_store.insert(objects[1]); + ctx_store.insert_and_apply(objects[1]); ddwaf::timer deadline{2s}; ASSERT_FALSE(filter.match(ctx_store, ctx_cache, {}, deadline)); } { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(objects[0]); + sctx_store.insert_and_apply(objects[0]); input_filter::cache_type sctx_cache = ctx_cache; ddwaf::timer deadline{2s}; @@ -301,19 +301,19 @@ TEST(TestInputFilter, InputValidateCachedSubcontextMatch) } { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; - ctx_store.insert(objects[2]); + ctx_store.insert_and_apply(objects[2]); ddwaf::timer deadline{2s}; ASSERT_FALSE(filter.match(ctx_store, ctx_cache, {}, deadline)); } { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(objects[3]); + sctx_store.insert_and_apply(objects[3]); input_filter::cache_type sctx_cache = ctx_cache; ddwaf::timer deadline{2s}; @@ -348,7 +348,7 @@ TEST(TestInputFilter, InputMatchWithoutCache) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; input_filter::cache_type cache; @@ -359,7 +359,7 @@ TEST(TestInputFilter, InputMatchWithoutCache) auto root = object_builder_da::map({{"usr.id", "admin"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; input_filter::cache_type cache; @@ -395,7 +395,7 @@ TEST(TestInputFilter, InputNoMatchWithoutCache) objects.emplace_back(object_builder_da::map({{"usr.id", "admin"}})); { - store.insert(objects[0]); + store.insert_and_apply(objects[0]); ddwaf::timer deadline{2s}; input_filter::cache_type cache; @@ -403,7 +403,7 @@ TEST(TestInputFilter, InputNoMatchWithoutCache) } { - store.insert(objects[1]); + store.insert_and_apply(objects[1]); auto client_ip_ptr = store.get_target("http.client_ip"); @@ -447,8 +447,8 @@ TEST(TestInputFilter, InputCachedMatchSecondRun) objects.emplace_back(object_builder_da::map({{"random", "random"}})); { - defer cleanup{[&]() { store.clear_last_batch(); }}; - store.insert(objects[0]); + defer cleanup{[&]() { store.flush_input_queue(); }}; + store.insert_and_apply(objects[0]); ddwaf::timer deadline{2s}; auto opt_spec = filter.match(store, cache, {}, deadline); @@ -459,8 +459,8 @@ TEST(TestInputFilter, InputCachedMatchSecondRun) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; - store.insert(objects[1]); + defer cleanup{[&]() { store.flush_input_queue(); }}; + store.insert_and_apply(objects[1]); ddwaf::timer deadline{2s}; ASSERT_FALSE(filter.match(store, cache, {}, deadline).has_value()); @@ -498,7 +498,7 @@ TEST(TestInputFilter, ObjectValidateCachedMatch) { object_store store; - store.insert(objects[0]); + store.insert_and_apply(objects[0]); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline).has_value()); @@ -506,7 +506,7 @@ TEST(TestInputFilter, ObjectValidateCachedMatch) { object_store store; - store.insert(objects[1]); + store.insert_and_apply(objects[1]); ddwaf::timer deadline{2s}; auto opt_spec = filter.match(store, cache, {}, deadline); @@ -540,7 +540,7 @@ TEST(TestInputFilter, ObjectMatchWithoutCache) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"query", object_builder_da::map({{"params", "value"}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; input_filter::cache_type cache; @@ -552,7 +552,7 @@ TEST(TestInputFilter, ObjectMatchWithoutCache) auto root = object_builder_da::map( {{"usr.id", "admin"}, {"query", object_builder_da::map({{"params", "value"}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; input_filter::cache_type cache; @@ -589,7 +589,7 @@ TEST(TestInputFilter, ObjectNoMatchWithoutCache) objects.emplace_back(object_builder_da::map({{"usr.id", "admin"}})); { - store.insert(objects[0]); + store.insert_and_apply(objects[0]); ddwaf::timer deadline{2s}; input_filter::cache_type cache; @@ -597,7 +597,7 @@ TEST(TestInputFilter, ObjectNoMatchWithoutCache) } { - store.insert(objects[1]); + store.insert_and_apply(objects[1]); ddwaf::timer deadline{2s}; input_filter::cache_type cache; @@ -638,8 +638,8 @@ TEST(TestInputFilter, ObjectCachedMatchSecondRun) objects.emplace_back(object_builder_da::map({{"random", "random"}})); { - defer cleanup{[&]() { store.clear_last_batch(); }}; - store.insert(objects[0]); + defer cleanup{[&]() { store.flush_input_queue(); }}; + store.insert_and_apply(objects[0]); ddwaf::timer deadline{2s}; auto opt_spec = filter.match(store, cache, {}, deadline); @@ -649,8 +649,8 @@ TEST(TestInputFilter, ObjectCachedMatchSecondRun) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; - store.insert(objects[1]); + defer cleanup{[&]() { store.flush_input_queue(); }}; + store.insert_and_apply(objects[1]); ddwaf::timer deadline{2s}; ASSERT_FALSE(filter.match(store, cache, {}, deadline).has_value()); @@ -685,7 +685,7 @@ TEST(TestInputFilter, MatchWithDynamicMatcher) object_store store; input_filter::cache_type cache; - store.insert(objects[0]); + store.insert_and_apply(objects[0]); ddwaf::timer deadline{2s}; auto opt_spec = filter.match(store, cache, {}, deadline); @@ -696,7 +696,7 @@ TEST(TestInputFilter, MatchWithDynamicMatcher) object_store store; input_filter::cache_type cache; - store.insert(objects[1]); + store.insert_and_apply(objects[1]); std::unordered_map> matchers; matchers["ip_data"] = diff --git a/tests/unit/exclusion/object_filter_test.cpp b/tests/unit/exclusion/object_filter_test.cpp index 331713697..3fdf6f2b4 100644 --- a/tests/unit/exclusion/object_filter_test.cpp +++ b/tests/unit/exclusion/object_filter_test.cpp @@ -23,7 +23,7 @@ TEST(TestObjectFilter, RootTarget) auto root = object_builder_da::map({ {"query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})}, }); - store.insert(root); + store.insert_and_apply(root); object_filter filter; filter.insert(query, "query", {}); @@ -58,7 +58,7 @@ TEST(TestObjectFilter, DuplicateTarget) {"query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})}, })); { - store.insert(objects[0]); + store.insert_and_apply(objects[0]); auto objects_filtered = filter.match(store, cache, deadline); @@ -67,7 +67,7 @@ TEST(TestObjectFilter, DuplicateTarget) } { - store.insert(objects[1]); + store.insert_and_apply(objects[1]); auto objects_filtered = filter.match(store, cache, deadline); @@ -91,7 +91,7 @@ TEST(TestObjectFilter, DuplicateCachedTarget) auto root = object_builder_da::map({ {"query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})}, }); - store.insert(root); + store.insert_and_apply(root); { auto objects_filtered = filter.match(store, cache, deadline); @@ -115,7 +115,7 @@ TEST(TestObjectFilter, SingleTarget) auto child = root.emplace( "query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})); - store.insert(root); + store.insert_and_apply(root); object_filter filter; filter.insert(query, "query", {"params"}); @@ -145,7 +145,7 @@ TEST(TestObjectFilter, DuplicateSingleTarget) auto child = root.emplace( "query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 1); @@ -157,7 +157,7 @@ TEST(TestObjectFilter, DuplicateSingleTarget) auto child = root.emplace( "query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 1); @@ -184,7 +184,7 @@ TEST(TestObjectFilter, MultipleTargets) auto object = sibling.emplace( "token", object_builder_da::map({{"value", "naskjdnakjsd"}, {"expiration", "yesterday"}})); - store.insert(root); + store.insert_and_apply(root); object_filter filter; filter.insert(query, "query", {"uri"}); @@ -225,7 +225,7 @@ TEST(TestObjectFilter, DuplicateMultipleTargets) auto object = sibling.emplace("token", object_builder_da::map({{"value", "naskjdnakjsd"}, {"expiration", "yesterday"}})); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); auto objects_filtered = filter.match(store, cache, deadline); @@ -246,7 +246,7 @@ TEST(TestObjectFilter, DuplicateMultipleTargets) auto object = sibling.emplace("token", object_builder_da::map({{"value", "naskjdnakjsd"}, {"expiration", "yesterday"}})); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); auto objects_filtered = filter.match(store, cache, deadline); @@ -270,7 +270,7 @@ TEST(TestObjectFilter, MissingTarget) {"token", object_builder_da::map({{"value", "naskjdnakjsd"}, {"expiration", "yesterday"}})}})}, }); - store.insert(root); + store.insert_and_apply(root); object_filter filter; filter.insert(status, "status", {"value"}); @@ -291,7 +291,7 @@ TEST(TestObjectFilter, SingleTargetCache) auto child = root.emplace( "query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})); - store.insert(root); + store.insert_and_apply(root); object_filter filter; filter.insert(query, "query", {"params"}); @@ -328,7 +328,7 @@ TEST(TestObjectFilter, MultipleTargetsCache) auto child = root.emplace( "query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 1); @@ -343,7 +343,7 @@ TEST(TestObjectFilter, MultipleTargetsCache) auto object = sibling.emplace("token", object_builder_da::map({{"value", "naskjdnakjsd"}, {"expiration", "yesterday"}})); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 1); @@ -371,7 +371,7 @@ TEST(TestObjectFilter, SingleGlobTarget) auto child = root.emplace( "query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 2); @@ -388,7 +388,7 @@ TEST(TestObjectFilter, SingleGlobTarget) object_builder_da::map({{"params", object_builder_da::map({{"value", "paramsvalue"}})}, {"uri", "uri_value"}})); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 2); @@ -401,7 +401,7 @@ TEST(TestObjectFilter, SingleGlobTarget) object_filter::cache_type cache; auto root = object_builder_da::map({{"query", owned_object{}}}); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 0); @@ -425,7 +425,7 @@ TEST(TestObjectFilter, GlobAndKeyTarget) auto child = root.emplace( "query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 2); @@ -442,7 +442,7 @@ TEST(TestObjectFilter, GlobAndKeyTarget) object_builder_da::map({{"params", object_builder_da::map({{"value", "paramsvalue"}})}, {"uri", "uri_value"}})); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 2); @@ -455,7 +455,7 @@ TEST(TestObjectFilter, GlobAndKeyTarget) object_filter::cache_type cache; auto root = object_builder_da::map({{"query", owned_object{}}}); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 0); @@ -482,7 +482,7 @@ TEST(TestObjectFilter, MultipleComponentsGlobAndKeyTargets) {{"params", object_builder_da::map({{"other", "paramsvalue"}})}})); auto grandnephew = child.emplace("uri", object_builder_da::map({{"other", "paramsvalue"}})); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 1); @@ -499,7 +499,7 @@ TEST(TestObjectFilter, MultipleComponentsGlobAndKeyTargets) child.emplace("params", object_builder_da::map({{"value", "paramsvalue"}})); auto grandnephew = child.emplace("uri", object_builder_da::map({{"value", "paramsvalue"}})); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 2); @@ -515,7 +515,7 @@ TEST(TestObjectFilter, MultipleComponentsGlobAndKeyTargets) {{"value", object_builder_da::map({{"whatever", "paramsvalue"}})}, {"other", object_builder_da::map({{"random", "paramsvalue"}})}})}}); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 0); @@ -528,7 +528,7 @@ TEST(TestObjectFilter, MultipleComponentsGlobAndKeyTargets) owned_object root = object_builder_da::map({{"query", object_builder_da::map({{"value", "value"}})}}); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 0); @@ -557,7 +557,7 @@ TEST(TestObjectFilter, MultipleGlobsTargets) auto greatgrandnephew = grandnephew.emplace("random", object_builder_da::map({{"other", "paramsvalue"}, {"somethingelse", "paramsvalue"}})); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 4); @@ -575,7 +575,7 @@ TEST(TestObjectFilter, MultipleGlobsTargets) object_builder_da::map({{"params", object_builder_da::map({{"something", "value"}})}, {"uri", object_builder_da::map({{"random", "value"}})}})}}); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 0); @@ -588,7 +588,7 @@ TEST(TestObjectFilter, MultipleGlobsTargets) auto root = object_builder_da::map( {{"query", object_builder_da::map({{"params", "value"}, {"uri", "value"}})}}); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 0); @@ -624,7 +624,7 @@ TEST(TestObjectFilter, MultipleComponentsMultipleGlobAndKeyTargets) object_store store; object_filter::cache_type cache; auto root = yaml_to_object(object); - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; auto objects_filtered = filter.match(store, cache, deadline); @@ -651,7 +651,7 @@ TEST(TestObjectFilter, MultipleComponentsMultipleGlobAndKeyTargets) object_store store; object_filter::cache_type cache; auto root = yaml_to_object(object); - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; auto objects_filtered = filter.match(store, cache, deadline); @@ -674,7 +674,7 @@ TEST(TestObjectFilter, ArrayWithGlobTargets) object_builder_da::map({{"a", object_builder_da::array({object_builder_da::map({{"c", object_builder_da::map({{"d", "value"}})}})})}})}}); - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; auto objects_filtered = filter.match(store, cache, deadline); @@ -690,7 +690,7 @@ TEST(TestObjectFilter, Timeout) auto root = object_builder_da::map( {{"query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})}}); - store.insert(root); + store.insert_and_apply(root); object_filter filter; filter.insert(query, "query", {}); diff --git a/tests/unit/exclusion/rule_filter_test.cpp b/tests/unit/exclusion/rule_filter_test.cpp index 5e71f1667..40171fbdd 100644 --- a/tests/unit/exclusion/rule_filter_test.cpp +++ b/tests/unit/exclusion/rule_filter_test.cpp @@ -35,7 +35,7 @@ TEST(TestRuleFilter, Match) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -67,7 +67,7 @@ TEST(TestRuleFilter, MatchWithDynamicMatcher) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -80,7 +80,7 @@ TEST(TestRuleFilter, MatchWithDynamicMatcher) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -110,7 +110,7 @@ TEST(TestRuleFilter, NoMatch) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -144,7 +144,7 @@ TEST(TestRuleFilter, ValidateCachedMatch) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)); @@ -154,7 +154,7 @@ TEST(TestRuleFilter, ValidateCachedMatch) auto root = object_builder_da::map({{"usr.id", "admin"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -190,9 +190,9 @@ TEST(TestRuleFilter, CachedMatchAndSubcontextMatch) // only the latest address. This ensures that the IP condition can't be // matched on the second run. { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; - store.insert(object_builder_da::map({{"http.client_ip", "192.168.0.1"}})); + store.insert_and_apply(object_builder_da::map({{"http.client_ip", "192.168.0.1"}})); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)); @@ -202,7 +202,7 @@ TEST(TestRuleFilter, CachedMatchAndSubcontextMatch) auto root = object_builder_da::map({{"usr.id", "admin"}}); auto sctx_store = object_store::from_upstream_store(store); - sctx_store.insert(std::move(root)); + sctx_store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; rule_filter::excluded_set default_set{.rules = {}, .mode = {}, .action = {}}; @@ -238,7 +238,7 @@ TEST(TestRuleFilter, MatchWithoutCache) ddwaf::rule_filter::cache_type cache; auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)); @@ -248,7 +248,7 @@ TEST(TestRuleFilter, MatchWithoutCache) ddwaf::rule_filter::cache_type cache; auto root = object_builder_da::map({{"usr.id", "admin"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)->rules.empty()); @@ -279,7 +279,7 @@ TEST(TestRuleFilter, NoMatchWithoutCache) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)); @@ -290,7 +290,7 @@ TEST(TestRuleFilter, NoMatchWithoutCache) auto root = object_builder_da::map({{"usr.id", "admin"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)); @@ -323,7 +323,7 @@ TEST(TestRuleFilter, FullCachedMatchSecondRun) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)->rules.empty()); @@ -333,7 +333,7 @@ TEST(TestRuleFilter, FullCachedMatchSecondRun) { auto root = object_builder_da::map({{"random", "random"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)); diff --git a/tests/unit/expression_test.cpp b/tests/unit/expression_test.cpp index e0ba15a80..8e6852f48 100644 --- a/tests/unit/expression_test.cpp +++ b/tests/unit/expression_test.cpp @@ -27,7 +27,7 @@ TEST(TestExpression, SimpleMatch) auto root = object_builder_da::map({{"server.request.query", "value"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -58,7 +58,7 @@ TEST(TestExpression, SimpleNegatedMatch) auto root = object_builder_da::map({{"server.request.query", "val"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -91,11 +91,11 @@ TEST(TestExpression, MultiInputMatchOnSecondEval) expression::cache_type cache; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "bad"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -103,11 +103,11 @@ TEST(TestExpression, MultiInputMatchOnSecondEval) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.body", "value"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -138,11 +138,11 @@ TEST(TestExpression, DuplicateInput) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "bad"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -150,11 +150,11 @@ TEST(TestExpression, DuplicateInput) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "value"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -174,11 +174,11 @@ TEST(TestExpression, MatchDuplicateInputNoCache) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "bad"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -187,11 +187,11 @@ TEST(TestExpression, MatchDuplicateInputNoCache) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "value"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -229,11 +229,11 @@ TEST(TestExpression, TwoConditionsSingleInputNoMatch) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "bad_value"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -241,11 +241,11 @@ TEST(TestExpression, TwoConditionsSingleInputNoMatch) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "value"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -271,7 +271,7 @@ TEST(TestExpression, TwoConditionsSingleInputMatch) auto root = object_builder_da::map({{"server.request.query", "value"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -300,7 +300,7 @@ TEST(TestExpression, TwoConditionsMultiInputSingleEvalMatch) auto root = object_builder_da::map( {{"server.request.query", "query"}, {"server.request.body", "body"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -326,11 +326,11 @@ TEST(TestExpression, TwoConditionsMultiInputMultiEvalMatch) expression::cache_type cache; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "query"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -338,12 +338,12 @@ TEST(TestExpression, TwoConditionsMultiInputMultiEvalMatch) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map( {{"server.request.query", "red-herring"}, {"server.request.body", "body"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -364,7 +364,7 @@ TEST(TestExpression, MatchWithKeyPath) {{"server.request.query", object_builder_da::map({{"key", "value"}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -393,7 +393,7 @@ TEST(TestExpression, MatchWithTransformer) auto root = object_builder_da::map({{"server.request.query", "VALUE"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -422,7 +422,7 @@ TEST(TestExpression, MatchWithMultipleTransformers) auto root = object_builder_da::map({{"server.request.query", " VALUE "}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -451,7 +451,7 @@ TEST(TestExpression, MatchOnKeys) {{"server.request.query", object_builder_da::map({{"value", "1729"}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -481,7 +481,7 @@ TEST(TestExpression, MatchOnKeysWithTransformer) {{"server.request.query", object_builder_da::map({{"VALUE", "1729"}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -510,7 +510,7 @@ TEST(TestExpression, ExcludeInput) auto root = object_builder_da::map({{"server.request.query", "value"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; std::unordered_set excluded_objects{store.get_target("server.request.query")}; @@ -532,7 +532,7 @@ TEST(TestExpression, ExcludeKeyPath) {{"server.request.query", object_builder_da::map({{"key", "value"}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; std::unordered_set excluded_objects{store.get_target("server.request.query")}; diff --git a/tests/unit/module_test.cpp b/tests/unit/module_test.cpp index baaa063e8..68b2ddb0a 100644 --- a/tests/unit/module_test.cpp +++ b/tests/unit/module_test.cpp @@ -53,7 +53,7 @@ TEST(TestModuleUngrouped, SingleRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -65,7 +65,7 @@ TEST(TestModuleUngrouped, SingleRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(root); + store.insert_and_apply(root); std::vector results; ddwaf::timer deadline = endless_timer(); mod.eval(results, store, cache, {}, {}, deadline); @@ -118,7 +118,7 @@ TEST(TestModuleUngrouped, MultipleMonitoringRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -132,7 +132,7 @@ TEST(TestModuleUngrouped, MultipleMonitoringRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(root); + store.insert_and_apply(root); std::vector results; ddwaf::timer deadline = endless_timer(); mod.eval(results, store, cache, {}, {}, deadline); @@ -186,7 +186,7 @@ TEST(TestModuleUngrouped, BlockingRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -243,7 +243,7 @@ TEST(TestModuleUngrouped, MonitoringRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -256,7 +256,7 @@ TEST(TestModuleUngrouped, MonitoringRuleMatch) // Check that we can still match the blocking rule { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.2"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -313,7 +313,7 @@ TEST(TestModuleUngrouped, BlockingRuleMatchBasePrecedence) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -373,7 +373,7 @@ TEST(TestModuleUngrouped, BlockingRuleMatchUserPrecedence) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -412,7 +412,7 @@ TEST(TestModuleUngrouped, NonExpiringModule) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline{0s}; @@ -449,7 +449,7 @@ TEST(TestModuleUngrouped, ExpiringModule) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline{0s}; @@ -487,7 +487,7 @@ TEST(TestModuleUngrouped, DisabledRules) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -539,7 +539,7 @@ TEST(TestModuleGrouped, MultipleGroupsMonitoringRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -595,7 +595,7 @@ TEST(TestModuleGrouped, MultipleGroupsBlockingRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -650,7 +650,7 @@ TEST(TestModuleGrouped, SingleGroupBlockingRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -704,7 +704,7 @@ TEST(TestModuleGrouped, SingleGroupMonitoringRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -759,7 +759,7 @@ TEST(TestModuleGrouped, UserPrecedenceSingleGroupMonitoringUserMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -814,7 +814,7 @@ TEST(TestModuleGrouped, BasePrecedenceSingleGroupMonitoringBaseMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -870,7 +870,7 @@ TEST(TestModuleGrouped, UserPrecedenceSingleGroupBlockingBaseMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -926,7 +926,7 @@ TEST(TestModuleGrouped, UserPrecedenceSingleGroupBlockingUserMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -982,7 +982,7 @@ TEST(TestModuleGrouped, BasePrecedenceSingleGroupBlockingBaseMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1038,7 +1038,7 @@ TEST(TestModuleGrouped, BasePrecedenceSingleGroupBlockingUserMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1093,7 +1093,7 @@ TEST(TestModuleGrouped, UserPrecedenceMultipleGroupsMonitoringMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1149,7 +1149,7 @@ TEST(TestModuleGrouped, UserPrecedenceMultipleGroupsBlockingMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1204,7 +1204,7 @@ TEST(TestModuleGrouped, BasePrecedenceMultipleGroupsMonitoringMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1260,7 +1260,7 @@ TEST(TestModuleGrouped, BasePrecedenceMultipleGroupsBlockingMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1404,7 +1404,7 @@ TEST(TestModuleGrouped, MultipleGroupsRulesAndMatches) object_store store; auto root = object_builder_da::map({{"http.client_ip", "192.168.0.2"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1507,7 +1507,7 @@ TEST(TestModuleGrouped, MultipleGroupsSingleMatchPerGroup) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1610,7 +1610,7 @@ TEST(TestModuleGrouped, MultipleGroupsOnlyBlockingMatch) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1651,7 +1651,7 @@ TEST(TestModuleGrouped, DisabledRules) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1686,7 +1686,7 @@ TEST(TestModuleGrouped, NonExpiringModule) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline{0s}; @@ -1723,7 +1723,7 @@ TEST(TestModuleGrouped, ExpiringModule) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline{0s}; diff --git a/tests/unit/object_store_test.cpp b/tests/unit/object_store_test.cpp index ea29da7a6..47d755c32 100644 --- a/tests/unit/object_store_test.cpp +++ b/tests/unit/object_store_test.cpp @@ -20,7 +20,9 @@ TEST(TestObjectStore, InsertInvalidObject) auto url = get_target_index("url"); object_store store; - store.insert(owned_object{}); + // An empty owned_object is not a map, so insert_batch returns false and + // nothing is queued; no next_batch() call needed. + store.insert_batch(owned_object{}); EXPECT_TRUE(store.empty()); EXPECT_FALSE(store.has_new_targets()); @@ -37,7 +39,9 @@ TEST(TestObjectStore, InsertStringObject) object_store store; - store.insert(test::ddwaf_object_da::make_string("hello")); + // A string is not a map, so insert_batch returns false and nothing is + // queued; no next_batch() call needed. + store.insert_batch(test::ddwaf_object_da::make_string("hello")); EXPECT_TRUE(store.empty()); EXPECT_FALSE(store.has_new_targets()); @@ -56,7 +60,7 @@ TEST(TestObjectStore, InsertAndGetObject) root.emplace("query", test::ddwaf_object_da::make_string("hello")); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -73,13 +77,13 @@ TEST(TestObjectStore, InsertAndGetSubcontextObject) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; auto root = test::ddwaf_object_da::make_map(); root.emplace("query", test::ddwaf_object_da::make_string("hello")); auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(std::move(root)); + sctx_store.insert_and_apply(std::move(root)); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); @@ -105,7 +109,7 @@ TEST(TestObjectStore, InsertMultipleUniqueObjects) object_store ctx_store; { - ctx_store.insert(object_builder_da::map({{"query", "hello"}})); + ctx_store.insert_and_apply(object_builder_da::map({{"query", "hello"}})); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -117,7 +121,7 @@ TEST(TestObjectStore, InsertMultipleUniqueObjects) { auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(object_builder_da::map({{"url", "hello"}})); + sctx_store.insert_and_apply(object_builder_da::map({{"url", "hello"}})); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); @@ -128,7 +132,9 @@ TEST(TestObjectStore, InsertMultipleUniqueObjects) } { - ctx_store.insert(owned_object{}); + // An empty owned_object is not a map, so nothing is queued; latest_batch_ + // still contains query from the first insert above. + ctx_store.insert_batch(owned_object{}); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -138,7 +144,7 @@ TEST(TestObjectStore, InsertMultipleUniqueObjects) EXPECT_FALSE(ctx_store.get_target(url).has_value()); } - ctx_store.clear_last_batch(); + ctx_store.flush_input_queue(); EXPECT_FALSE(ctx_store.empty()); EXPECT_FALSE(ctx_store.has_new_targets()); @@ -155,12 +161,12 @@ TEST(TestObjectStore, InsertMultipleUniqueObjectBatches) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto first = test::ddwaf_object_da::make_map(); first.emplace("query", test::ddwaf_object_da::make_string("hello")); - store.insert(std::move(first)); + store.insert_and_apply(std::move(first)); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -171,12 +177,12 @@ TEST(TestObjectStore, InsertMultipleUniqueObjectBatches) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto second = test::ddwaf_object_da::make_map(); second.emplace("url", test::ddwaf_object_da::make_string("hello")); - store.insert(std::move(second)); + store.insert_and_apply(std::move(second)); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -187,10 +193,12 @@ TEST(TestObjectStore, InsertMultipleUniqueObjectBatches) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; + // An empty owned_object is not a map, so nothing is queued; latest_batch_ + // was already cleared by the previous flush_input_queue(). owned_object third = owned_object{}; - store.insert(std::move(third)); + store.insert_batch(std::move(third)); EXPECT_FALSE(store.empty()); EXPECT_FALSE(store.has_new_targets()); EXPECT_FALSE(store.is_new_target(query)); @@ -207,11 +215,11 @@ TEST(TestObjectStore, InsertMultipleOverlappingObjects) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto first = test::ddwaf_object_da::make_map(); first.emplace("query", test::ddwaf_object_da::make_string("hello")); - store.insert(std::move(first)); + store.insert_and_apply(std::move(first)); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -227,13 +235,13 @@ TEST(TestObjectStore, InsertMultipleOverlappingObjects) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; // Reinsert query auto second = test::ddwaf_object_da::make_map(); second.emplace("url", test::ddwaf_object_da::make_string("hello")); second.emplace("query", test::ddwaf_object_da::make_string("bye")); - store.insert(std::move(second)); + store.insert_and_apply(std::move(second)); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -256,11 +264,11 @@ TEST(TestObjectStore, InsertMultipleOverlappingObjects) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; // Reinsert url auto third = test::ddwaf_object_da::make_map(); third.emplace("url", test::ddwaf_object_da::make_string("bye")); - store.insert(std::move(third)); + store.insert_and_apply(std::move(third)); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -282,7 +290,7 @@ TEST(TestObjectStore, InsertSingleTargets) object_store ctx_store; - ctx_store.insert(query, "query", test::ddwaf_object_da::make_string("hello")); + ctx_store.insert_target(query, "query", test::ddwaf_object_da::make_string("hello")); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -293,7 +301,7 @@ TEST(TestObjectStore, InsertSingleTargets) { auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(url, "url", test::ddwaf_object_da::make_string("hello")); + sctx_store.insert_target(url, "url", test::ddwaf_object_da::make_string("hello")); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); @@ -303,7 +311,7 @@ TEST(TestObjectStore, InsertSingleTargets) EXPECT_TRUE(sctx_store.get_target(url).has_value()); } - ctx_store.clear_last_batch(); + ctx_store.flush_input_queue(); EXPECT_FALSE(ctx_store.empty()); EXPECT_FALSE(ctx_store.has_new_targets()); @@ -320,9 +328,9 @@ TEST(TestObjectStore, InsertSingleTargetBatches) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; - ctx_store.insert(query, "query", test::ddwaf_object_da::make_string("hello")); + ctx_store.insert_target(query, "query", test::ddwaf_object_da::make_string("hello")); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -333,10 +341,10 @@ TEST(TestObjectStore, InsertSingleTargetBatches) } { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(url, "url", test::ddwaf_object_da::make_string("hello")); + sctx_store.insert_target(url, "url", test::ddwaf_object_da::make_string("hello")); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); @@ -360,9 +368,10 @@ TEST(TestObjectStore, DuplicatePersistentTarget) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; - EXPECT_TRUE(store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE( + store.insert_target(query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -374,9 +383,9 @@ TEST(TestObjectStore, DuplicatePersistentTarget) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; - EXPECT_TRUE(store.insert(query, "query", test::ddwaf_object_da::make_string("bye"))); + EXPECT_TRUE(store.insert_target(query, "query", test::ddwaf_object_da::make_string("bye"))); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -401,9 +410,10 @@ TEST(TestObjectStore, DuplicateSubcontextTarget) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; { - EXPECT_TRUE(store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE( + store.insert_target(query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -415,7 +425,8 @@ TEST(TestObjectStore, DuplicateSubcontextTarget) } { - EXPECT_TRUE(store.insert(query, "query", test::ddwaf_object_da::make_string("bye"))); + EXPECT_TRUE( + store.insert_target(query, "query", test::ddwaf_object_da::make_string("bye"))); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -441,11 +452,11 @@ TEST(TestObjectStore, ReplaceSubcontextWithPersistent) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; { object_store sctx_store; - EXPECT_TRUE( - sctx_store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE(sctx_store.insert_target( + query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); @@ -458,7 +469,7 @@ TEST(TestObjectStore, ReplaceSubcontextWithPersistent) { EXPECT_TRUE( - ctx_store.insert(query, "query", test::ddwaf_object_da::make_string("bye"))); + ctx_store.insert_target(query, "query", test::ddwaf_object_da::make_string("bye"))); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -484,10 +495,10 @@ TEST(TestObjectStore, ReplacePersistentWithSubcontextSameBatch) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; { - EXPECT_TRUE( - ctx_store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE(ctx_store.insert_target( + query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -500,8 +511,8 @@ TEST(TestObjectStore, ReplacePersistentWithSubcontextSameBatch) { object_store sctx_store; - EXPECT_TRUE( - sctx_store.insert(query, "query", test::ddwaf_object_da::make_string("bye"))); + EXPECT_TRUE(sctx_store.insert_target( + query, "query", test::ddwaf_object_da::make_string("bye"))); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); @@ -527,9 +538,10 @@ TEST(TestObjectStore, ReplacePersistentWithSubcontextDifferentBatch) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; - EXPECT_TRUE(ctx_store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE( + ctx_store.insert_target(query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -542,7 +554,8 @@ TEST(TestObjectStore, ReplacePersistentWithSubcontextDifferentBatch) { auto sctx_store = object_store::from_upstream_store(ctx_store); - EXPECT_TRUE(sctx_store.insert(query, "query", test::ddwaf_object_da::make_string("bye"))); + EXPECT_TRUE( + sctx_store.insert_target(query, "query", test::ddwaf_object_da::make_string("bye"))); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); diff --git a/tests/unit/processor/processor_test.cpp b/tests/unit/processor/processor_test.cpp index 1ecd6bf39..7362974a7 100644 --- a/tests/unit/processor/processor_test.cpp +++ b/tests/unit/processor/processor_test.cpp @@ -50,7 +50,7 @@ TEST(TestProcessor, SingleMappingOutputNoEvalUnconditional) auto input_map = object_builder_da::map({{"input_address", "input_string"}}); object_store store; - store.insert(input_map); + store.insert_and_apply(input_map); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -91,7 +91,7 @@ TEST(TestProcessor, MultiMappingOutputNoEvalUnconditional) {{"input_address", "first_input_string"}, {"input_address.second", "second_input_string"}}); object_store store; - store.insert(input_map); + store.insert_and_apply(input_map); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -145,7 +145,7 @@ TEST(TestProcessor, SingleMappingOutputNoEvalConditionalTrue) object_builder_da::map({{"input_address", "input_string"}, {"enabled?", true}}); object_store store; - store.insert(input_map); + store.insert_and_apply(input_map); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -189,7 +189,7 @@ TEST(TestProcessor, SingleMappingOutputNoEvalConditionalCached) auto input_map = object_builder_da::map({{"enabled?", true}}); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -224,7 +224,7 @@ TEST(TestProcessor, SingleMappingOutputNoEvalConditionalCached) {"input_address", "input_string"}, }); - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); proc.eval(store, collector, cache, alloc, deadline); attributes = collector.get_available_attributes_and_reset(); @@ -245,7 +245,7 @@ TEST(TestProcessor, SingleMappingOutputNoEvalConditionalFalse) object_builder_da::map({{"input_address", "input_string"}, {"enabled?", false}}); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -285,7 +285,7 @@ TEST(TestProcessor, SingleMappingNoOutputEvalUnconditional) }); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -331,7 +331,7 @@ TEST(TestProcessor, SingleMappingNoOutputEvalConditionalTrue) object_builder_da::map({{"input_address", "input_string"}, {"enabled?", true}}); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -380,7 +380,7 @@ TEST(TestProcessor, SingleMappingNoOutputEvalConditionalFalse) object_builder_da::map({{"input_address", "input_string"}, {"enabled?", false}}); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -422,7 +422,7 @@ TEST(TestProcessor, MultiMappingNoOutputEvalUnconditional) {{"input_address", "first_input_string"}, {"input_address.second", "second_input_string"}}); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -479,7 +479,7 @@ TEST(TestProcessor, SingleMappingOutputEvalUnconditional) }); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -529,7 +529,7 @@ TEST(TestProcessor, OutputAlreadyAvailableInStore) {{"input_address", "input_string"}, {"output_address", owned_object::make_null()}}); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -562,7 +562,7 @@ TEST(TestProcessor, OutputAlreadyGenerated) }); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -595,7 +595,7 @@ TEST(TestProcessor, EvalAlreadyAvailableInStore) {{"input_address", "input_string"}, {"output_address", owned_object::make_null()}}); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -629,7 +629,7 @@ TEST(TestProcessor, OutputEvalWithoutattributesMap) }); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), diff --git a/tests/unit/processor/structured_processor_test.cpp b/tests/unit/processor/structured_processor_test.cpp index c1408a326..1a081733d 100644 --- a/tests/unit/processor/structured_processor_test.cpp +++ b/tests/unit/processor/structured_processor_test.cpp @@ -53,7 +53,7 @@ TEST(TestStructuredProcessor, AllParametersAvailable) {{"unary_address", "unary_string"}, {"optional_address", "optional_string"}, {"variadic_address_1", 1U}, {"variadic_address_2", 1U}}); object_store store; - store.insert(input_map); + store.insert_and_apply(input_map); std::vector mappings{ {.inputs = {{{{.index = get_target_index("unary_address"), @@ -102,7 +102,7 @@ TEST(TestStructuredProcessor, OptionalParametersNotAvailable) {"variadic_address_1", 1U}, {"variadic_address_2", 1U}}); object_store store; - store.insert(input_map); + store.insert_and_apply(input_map); std::vector mappings{ {.inputs = {{{{.index = get_target_index("unary_address"), @@ -149,7 +149,7 @@ TEST(TestStructuredProcessor, RequiredParameterNotAvailable) {"variadic_address_1", 1U}, {"variadic_address_2", 1U}}); object_store store; - store.insert(input_map); + store.insert_and_apply(input_map); std::vector mappings{ {.inputs = {{{{.index = get_target_index("unary_address"), @@ -193,7 +193,7 @@ TEST(TestStructuredProcessor, NoVariadocParametersAvailable) }); object_store store; - store.insert(input_map); + store.insert_and_apply(input_map); std::vector mappings{ {.inputs = {{{{.index = get_target_index("unary_address"), diff --git a/tests/unit/rule_test.cpp b/tests/unit/rule_test.cpp index 3cffbf188..64670bce6 100644 --- a/tests/unit/rule_test.cpp +++ b/tests/unit/rule_test.cpp @@ -38,8 +38,8 @@ TEST(TestRule, Match) core_rule::cache_type cache; { - defer cleanup{[&]() { store.clear_last_batch(); }}; - store.insert(root.clone(memory::get_default_resource())); + defer cleanup{[&]() { store.flush_input_queue(); }}; + store.insert_and_apply(root.clone(memory::get_default_resource())); auto [verdict, result] = rule.match(store, cache, {}, {}, deadline); ASSERT_TRUE(result.has_value()); @@ -63,7 +63,7 @@ TEST(TestRule, Match) } { - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); auto [verdict, result] = rule.match(store, cache, {}, {}, deadline); EXPECT_FALSE(result.has_value()); @@ -86,7 +86,7 @@ TEST(TestRule, NoMatch) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -120,7 +120,7 @@ TEST(TestRule, ValidateCachedMatch) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; auto [verdict, result] = rule.match(store, cache, {}, {}, deadline); @@ -131,7 +131,7 @@ TEST(TestRule, ValidateCachedMatch) auto root = object_builder_da::map({{"usr.id", "admin"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; auto [verdict, result] = rule.match(store, cache, {}, {}, deadline); @@ -190,7 +190,7 @@ TEST(TestRule, MatchWithoutCache) object_store store; { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; core_rule::cache_type cache; @@ -201,7 +201,7 @@ TEST(TestRule, MatchWithoutCache) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; core_rule::cache_type cache; @@ -254,7 +254,7 @@ TEST(TestRule, NoMatchWithoutCache) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; core_rule::cache_type cache; @@ -266,7 +266,7 @@ TEST(TestRule, NoMatchWithoutCache) auto root = object_builder_da::map({{"usr.id", "admin"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; core_rule::cache_type cache; @@ -301,7 +301,7 @@ TEST(TestRule, FullCachedMatchSecondRun) object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; auto [verdict, result] = rule.match(store, cache, {}, {}, deadline); @@ -314,7 +314,7 @@ TEST(TestRule, FullCachedMatchSecondRun) object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; auto [verdict, result] = rule.match(store, cache, {}, {}, deadline); @@ -336,7 +336,7 @@ TEST(TestRule, ExcludeObject) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::unordered_set excluded_set{store.get_target("http.client_ip")}; diff --git a/tests/unit/waf_test.cpp b/tests/unit/waf_test.cpp index 00420921a..f31417947 100644 --- a/tests/unit/waf_test.cpp +++ b/tests/unit/waf_test.cpp @@ -53,7 +53,7 @@ TEST(TestWaf, BasicContextRun) auto root = object_builder_da::map({{"value1", "rule1"}}); auto ctx = instance.create_context(memory::get_default_resource()); - EXPECT_TRUE(ctx.insert(std::move(root))); + EXPECT_TRUE(ctx.insert_batch(std::move(root))); ddwaf::timer deadline{2s}; auto [code, res] = ctx.eval(deadline); EXPECT_EQ(code, DDWAF_MATCH); From 15a02468a68225f058b4cfc0896db2f247bcebc9 Mon Sep 17 00:00:00 2001 From: datadog-bits Date: Tue, 9 Jun 2026 21:05:29 +0000 Subject: [PATCH 2/3] test: cover multieval and store branches --- .../interface/context/multieval/test.cpp | 138 ++++++++++++++++++ .../interface/context/result/test.cpp | 60 ++++++++ tests/unit/evaluation_engine_test.cpp | 28 ++++ tests/unit/object_store_test.cpp | 40 +++++ 4 files changed, 266 insertions(+) diff --git a/tests/integration/interface/context/multieval/test.cpp b/tests/integration/interface/context/multieval/test.cpp index 5d6eef13a..d238028a3 100644 --- a/tests/integration/interface/context/multieval/test.cpp +++ b/tests/integration/interface/context/multieval/test.cpp @@ -6,6 +6,7 @@ #include "common/gtest_utils.hpp" #include "ddwaf.h" +#include using namespace ddwaf; @@ -13,6 +14,9 @@ namespace { constexpr std::string_view base_dir = "integration/interface/context/multieval/"; +void *throwing_alloc(void *, size_t, size_t) { throw std::runtime_error("allocation failure"); } +void noop_free(void *, void *, size_t, size_t) {} + //------------------------------------------------------------------------------ // ddwaf_context_multieval tests //------------------------------------------------------------------------------ @@ -90,6 +94,69 @@ TEST(TestContextMultievalIntegration, InvalidArgumentNotArray) ddwaf_destroy(handle); } +TEST(TestContextMultievalIntegration, InvalidArgumentNotArrayWithNullAlloc) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_map(&data, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, nullptr, &result, LONG_TIME), + DDWAF_ERR_INVALID_OBJECT); + + ddwaf_object_destroy(&data, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, InternalErrorWhenInputAllocatorThrows) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto *throwing_alloc_handle = + ddwaf_user_allocator_init(throwing_alloc, noop_free, nullptr, nullptr); + ASSERT_NE(throwing_alloc_handle, nullptr); + + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 1, alloc); + ddwaf_object *elem = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, throwing_alloc_handle, &result, LONG_TIME), + DDWAF_ERR_INTERNAL); + + ddwaf_object_destroy(&data, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); + ddwaf_allocator_destroy(throwing_alloc_handle); +} + TEST(TestContextMultievalIntegration, SingleMapNoMatch) { auto *alloc = ddwaf_get_default_allocator(); @@ -708,6 +775,77 @@ TEST(TestSubcontextMultievalIntegration, InvalidArgumentNotArray) ddwaf_destroy(handle); } +TEST(TestSubcontextMultievalIntegration, InvalidArgumentNotArrayWithNullAlloc) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_object data; + ddwaf_object_set_map(&data, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data, nullptr, &result, LONG_TIME), + DDWAF_ERR_INVALID_OBJECT); + + ddwaf_object_destroy(&data, alloc); + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, InternalErrorWhenInputAllocatorThrows) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto *throwing_alloc_handle = + ddwaf_user_allocator_init(throwing_alloc, noop_free, nullptr, nullptr); + ASSERT_NE(throwing_alloc_handle, nullptr); + + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 1, alloc); + ddwaf_object *elem = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data, throwing_alloc_handle, &result, LONG_TIME), + DDWAF_ERR_INTERNAL); + + ddwaf_object_destroy(&data, alloc); + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); + ddwaf_allocator_destroy(throwing_alloc_handle); +} + TEST(TestSubcontextMultievalIntegration, SingleMapNoMatch) { auto *alloc = ddwaf_get_default_allocator(); diff --git a/tests/integration/interface/context/result/test.cpp b/tests/integration/interface/context/result/test.cpp index 8eb63e61f..f8bd5aec6 100644 --- a/tests/integration/interface/context/result/test.cpp +++ b/tests/integration/interface/context/result/test.cpp @@ -98,6 +98,35 @@ TEST(TestContextResultIntegration, ResultInvalidObjectInvalidPersistentDataSchem ddwaf_destroy(handle); } +TEST(TestContextResultIntegration, ResultInvalidObjectInvalidPersistentDataSchemaNullAlloc) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("interface.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object persistent; + ddwaf_object_set_array(&persistent, 1, alloc); + ddwaf_object_set_string(ddwaf_object_insert(&persistent, alloc), STRL("rule1"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_eval(context, &persistent, nullptr, &result, LONG_TIME), + DDWAF_ERR_INVALID_OBJECT); + EXPECT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_INVALID); + + ddwaf_object_destroy(&persistent, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + TEST(TestContextResultIntegration, ResultInvalidObjectInvalidSubcontextDataSchema) { auto *alloc = ddwaf_get_default_allocator(); @@ -131,6 +160,37 @@ TEST(TestContextResultIntegration, ResultInvalidObjectInvalidSubcontextDataSchem ddwaf_destroy(handle); } +TEST(TestContextResultIntegration, ResultInvalidObjectInvalidSubcontextDataSchemaNullAlloc) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("interface.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object ephemeral; + ddwaf_object_set_array(&ephemeral, 1, alloc); + ddwaf_object_set_string(ddwaf_object_insert(&ephemeral, alloc), STRL("rule1"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + auto *subctx = ddwaf_subcontext_init(context); + EXPECT_EQ(ddwaf_subcontext_eval(subctx, &ephemeral, nullptr, &result, LONG_TIME), + DDWAF_ERR_INVALID_OBJECT); + EXPECT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_INVALID); + ddwaf_subcontext_destroy(subctx); + + ddwaf_object_destroy(&ephemeral, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + TEST(TestContextResultIntegration, ResultOk) { auto *alloc = ddwaf_get_default_allocator(); diff --git a/tests/unit/evaluation_engine_test.cpp b/tests/unit/evaluation_engine_test.cpp index f1e40a0b6..3d916a228 100644 --- a/tests/unit/evaluation_engine_test.cpp +++ b/tests/unit/evaluation_engine_test.cpp @@ -2189,4 +2189,32 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFiltersMultipleObject } } +TEST(TestEvaluationEngine, ContextAndSubcontextMapViewInsertBatch) +{ + test::ruleset_builder rbuilder{}; + context ctx{rbuilder.build()}; + + auto root = object_builder_da::map({{"value", "hello"}}); + EXPECT_TRUE(ctx.insert_batch(object_view{root}.as())); + EXPECT_TRUE(ctx.next_batch()); + + auto sctx = ctx.create_subcontext(); + auto ephemeral = object_builder_da::map({{"ephemeral", "world"}}); + EXPECT_TRUE(sctx.insert_batch(object_view{ephemeral}.as())); + EXPECT_TRUE(sctx.next_batch()); +} + +TEST(TestEvaluationEngine, ContextAndSubcontextArrayViewInsertBatchesInvalid) +{ + test::ruleset_builder rbuilder{}; + context ctx{rbuilder.build()}; + + auto invalid_batches = + object_builder_da::array({object_builder_da::map({{"value", "hello"}}), "bad"}); + EXPECT_FALSE(ctx.insert_batches(object_view{invalid_batches}.as())); + + auto sctx = ctx.create_subcontext(); + EXPECT_FALSE(sctx.insert_batches(object_view{invalid_batches}.as())); +} + } // namespace diff --git a/tests/unit/object_store_test.cpp b/tests/unit/object_store_test.cpp index 47d755c32..27a82a707 100644 --- a/tests/unit/object_store_test.cpp +++ b/tests/unit/object_store_test.cpp @@ -70,6 +70,14 @@ TEST(TestObjectStore, InsertAndGetObject) EXPECT_FALSE(store.get_target(url).has_value()); } +TEST(TestObjectStore, InsertAndApplyInvalidObject) +{ + object_store store; + EXPECT_FALSE(store.insert_and_apply(owned_object{})); + EXPECT_TRUE(store.empty()); + EXPECT_FALSE(store.next_batch()); +} + TEST(TestObjectStore, InsertAndGetSubcontextObject) { auto query = get_target_index("query"); @@ -154,6 +162,38 @@ TEST(TestObjectStore, InsertMultipleUniqueObjects) EXPECT_FALSE(ctx_store.get_target(url).has_value()); } +TEST(TestObjectStore, InsertBatchesWithInvalidElement) +{ + object_store store; + + auto batches = + object_builder_da::array({object_builder_da::map({{"query", "hello"}}), "invalid"}); + EXPECT_FALSE(store.insert_batches(object_view{batches}.as())); + EXPECT_FALSE(store.next_batch()); + EXPECT_TRUE(store.empty()); +} + +TEST(TestObjectStore, FlushAppliesQueuedBatchesWithoutMarkingNewTargets) +{ + object_store store; + + auto batches = object_builder_da::array( + {object_builder_da::map({{"query", "hello"}}), object_builder_da::map({{"url", "world"}})}); + ASSERT_TRUE(store.insert_batches(std::move(batches))); + + EXPECT_FALSE(store.empty()); + EXPECT_FALSE(store.has_new_targets()); + EXPECT_FALSE(store.get_target("query").has_value()); + EXPECT_FALSE(store.get_target("url").has_value()); + + store.flush_input_queue(); + + EXPECT_TRUE(store.get_target("query").has_value()); + EXPECT_TRUE(store.get_target("url").has_value()); + EXPECT_FALSE(store.has_new_targets()); + EXPECT_FALSE(store.next_batch()); +} + TEST(TestObjectStore, InsertMultipleUniqueObjectBatches) { auto query = get_target_index("query"); From 47ed904d187e309a78f8690128c6b6f65d0fdc6e Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Tue, 9 Jun 2026 20:01:32 +0100 Subject: [PATCH 3/3] Introduce ddwaf_(context|subcontext)_multieval to evaluate multiple batches in sequence --- fuzzer/cmdi_detector/src/main.cpp | 2 +- fuzzer/lfi_detector/src/main.cpp | 2 +- fuzzer/shi_detector_array/src/main.cpp | 2 +- fuzzer/shi_detector_string/src/main.cpp | 2 +- fuzzer/sqli_detector/src/main.cpp | 2 +- fuzzer/ssrf_detector/src/main.cpp | 2 +- include/ddwaf.h | 179 +++ libddwaf.def | 2 + smoketest/smoke.c | 29 + src/context.hpp | 70 +- src/evaluation_engine.cpp | 74 +- src/evaluation_engine.hpp | 34 +- src/interface.cpp | 100 +- src/object_store.cpp | 101 +- src/object_store.hpp | 54 +- src/processor/base.hpp | 5 +- src/serializer.cpp | 6 +- src/serializer.hpp | 1 + tests/integration/context/test.cpp | 60 + .../interface/context/multieval/test.cpp | 1033 +++++++++++++++++ .../context/multieval/yaml/rules.yaml | 51 + tests/unit/attribute_collector_test.cpp | 44 +- tests/unit/condition/cmdi_detector_test.cpp | 26 +- .../unit/condition/exists_condition_test.cpp | 34 +- tests/unit/condition/lfi_detector_test.cpp | 22 +- .../negated_scalar_condition_test.cpp | 34 +- .../unit/condition/scalar_condition_test.cpp | 12 +- .../condition/shi_detector_array_test.cpp | 18 +- .../condition/shi_detector_string_test.cpp | 16 +- tests/unit/condition/sqli_detector_test.cpp | 18 +- tests/unit/condition/ssrf_detector_test.cpp | 2 +- tests/unit/evaluation_engine_test.cpp | 126 +- tests/unit/exclusion/input_filter_test.cpp | 74 +- tests/unit/exclusion/object_filter_test.cpp | 62 +- tests/unit/exclusion/rule_filter_test.cpp | 30 +- tests/unit/expression_test.cpp | 62 +- tests/unit/module_test.cpp | 64 +- tests/unit/object_store_test.cpp | 107 +- tests/unit/processor/processor_test.cpp | 30 +- .../processor/structured_processor_test.cpp | 8 +- tests/unit/rule_test.cpp | 26 +- tests/unit/waf_test.cpp | 2 +- 42 files changed, 2138 insertions(+), 490 deletions(-) create mode 100644 tests/integration/interface/context/multieval/test.cpp create mode 100644 tests/integration/interface/context/multieval/yaml/rules.yaml diff --git a/fuzzer/cmdi_detector/src/main.cpp b/fuzzer/cmdi_detector/src/main.cpp index 302614ff1..e48288b40 100644 --- a/fuzzer/cmdi_detector/src/main.cpp +++ b/fuzzer/cmdi_detector/src/main.cpp @@ -199,7 +199,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/fuzzer/lfi_detector/src/main.cpp b/fuzzer/lfi_detector/src/main.cpp index 8a2d481d1..703c93358 100644 --- a/fuzzer/lfi_detector/src/main.cpp +++ b/fuzzer/lfi_detector/src/main.cpp @@ -118,7 +118,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) owned_object::make_string(resource, ddwaf::memory::get_default_resource())); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/fuzzer/shi_detector_array/src/main.cpp b/fuzzer/shi_detector_array/src/main.cpp index 753678e6c..c92b61252 100644 --- a/fuzzer/shi_detector_array/src/main.cpp +++ b/fuzzer/shi_detector_array/src/main.cpp @@ -199,7 +199,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/fuzzer/shi_detector_string/src/main.cpp b/fuzzer/shi_detector_string/src/main.cpp index 10a540ae2..de645b80e 100644 --- a/fuzzer/shi_detector_string/src/main.cpp +++ b/fuzzer/shi_detector_string/src/main.cpp @@ -117,7 +117,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) owned_object::make_string(resource, ddwaf::memory::get_default_resource())); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/fuzzer/sqli_detector/src/main.cpp b/fuzzer/sqli_detector/src/main.cpp index 5c381ead1..ea455329f 100644 --- a/fuzzer/sqli_detector/src/main.cpp +++ b/fuzzer/sqli_detector/src/main.cpp @@ -135,7 +135,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) owned_object::make_string(resource, ddwaf::memory::get_default_resource())); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/fuzzer/ssrf_detector/src/main.cpp b/fuzzer/ssrf_detector/src/main.cpp index b2a988227..c6d95cc05 100644 --- a/fuzzer/ssrf_detector/src/main.cpp +++ b/fuzzer/ssrf_detector/src/main.cpp @@ -118,7 +118,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) owned_object::make_string(resource, ddwaf::memory::get_default_resource())); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/include/ddwaf.h b/include/ddwaf.h index 043d21d29..95594bb9d 100644 --- a/include/ddwaf.h +++ b/include/ddwaf.h @@ -317,6 +317,12 @@ ddwaf_context ddwaf_context_init(const ddwaf_handle handle, ddwaf_allocator outp * format: {tag, value} * - keep: whether the data contained herein must override any * transport sampling through the relevant mechanism. + * - evaluated: an unsigned integer indicating the number of input + * batches that were fully evaluated. For this single + * evaluation it is 1 when a non-empty batch was + * evaluated and 0 otherwise (e.g. an empty input or a + * timeout before evaluation completed). See + * ddwaf_context_multieval for the multi-batch case. * This structure must be freed by the caller using the output * allocator provided through ddwaf_context_init. The object will * contain all specified keys when the value returned by @@ -349,6 +355,90 @@ ddwaf_context ddwaf_context_init(const ddwaf_handle handle, ddwaf_allocator outp DDWAF_RET_CODE ddwaf_context_eval(ddwaf_context context, ddwaf_object *data, ddwaf_allocator alloc, ddwaf_object *result, uint64_t timeout); +/** + * Perform multiple matching operations on the provided data, evaluating each + * batch in sequence and returning a single combined result. + * + * This function operates identically to ddwaf_context_eval, with the + * distinction that data must be an array of maps. Each element is treated as + * a separate input batch and is evaluated in order. Addresses provided in + * earlier batches persist in the store and remain available when evaluating + * later batches, so a rule that requires multiple addresses can be satisfied + * by data spread across different batches within a single call. The result + * reflects all events, actions, and attributes accumulated across the entire + * sequence of batches. + * + * @param context WAF context to be used in this run, this will determine the + * ruleset which will be used and it will also ensure that + * parameters are taken into account across runs. (nonnull) + * + * @param data (nonnull) Array of input batches to evaluate. Each element must + * be a map of {string, } where each key represents the relevant + * address associated to the value, which can be of an arbitrary type. The + * batches are evaluated in order. If the same address appears in multiple + * batches, the later value replaces the earlier one. Passing a non-array + * object will return DDWAF_ERR_INVALID_OBJECT. + * Ownership and lifetime semantics are identical to ddwaf_context_eval: + * the data is stored by the context and the provided allocator is used to + * free it once the context is destroyed. + * + * @param alloc (nullable) Allocator used to free the data provided. If NULL, + * the data will not be freed. + * + * @param result (nullable) Object map containing the following items: + * - events: an array of all events generated across all batches. + * - actions: a map of all actions generated across all batches + * in the format: "{action type: { }, ...}" + * - duration: an unsigned specifying the total runtime of the + * call in nanoseconds. + * - timeout: whether there has been a timeout during the call. + * - attributes: a map containing all derived objects in the + * format: {tag, value} + * - keep: whether the data contained herein must override any + * transport sampling through the relevant mechanism. + * - evaluated: an unsigned integer indicating the number of + * batches that were fully evaluated. In the normal + * case this equals the number of non-empty batches. + * On timeout or error occurring during batch I + * (0-based, counting only non-empty batches), this + * value equals I, which is also the index of the + * batch where the problem occurred. Empty batches are + * skipped and do not count towards this value. + * This structure must be freed by the caller using the output + * allocator provided through ddwaf_context_init. The object will + * contain all specified keys when the value returned by + * ddwaf_context_multieval is either DDWAF_OK or DDWAF_MATCH and + * will be empty otherwise. + * IMPORTANT: This object is not allocated with the allocator + * passed in this call. It uses the allocator given to + * ddwaf_context_init instead. + * @param timeout Maximum time budget in microseconds. + * + * @return Return code of the operation. + * @retval DDWAF_ERR_INVALID_ARGUMENT The context is invalid, the data will not + * be freed. + * @retval DDWAF_ERR_INVALID_OBJECT The data provided didn't match the desired + * structure or contained invalid objects, the + * data will be freed by this function. + * @retval DDWAF_ERR_INTERNAL There was an unexpected error and the operation did + * not succeed. The state of the WAF is undefined if + * this error is produced and the ownership of the + * data is unknown. The result structure will not be + * filled if this error occurs. + * + * Notes on addresses: + * - Within a single batch, addresses provided should be unique. + * If duplicate addresses are provided within the same batch, the latest one + * in the structure will be used for evaluation. + * - Addresses from earlier batches persist in the store and are accessible + * during evaluation of subsequent batches within the same call. If the same + * address appears in a later batch, the later value replaces the earlier one. + * - A rule that requires multiple addresses can be satisfied by data spread + * across different batches within a single call. + **/ +DDWAF_RET_CODE ddwaf_context_multieval(ddwaf_context context, ddwaf_object *data, + ddwaf_allocator alloc, ddwaf_object *result, uint64_t timeout); + /** * Performs the destruction of the context, freeing the data passed to it through * ddwaf_context_eval using the provided allocator during evaluation. @@ -395,6 +485,12 @@ ddwaf_subcontext ddwaf_subcontext_init(ddwaf_context context); * format: {tag, value} * - keep: whether the data contained herein must override any * transport sampling through the relevant mechanism. + * - evaluated: an unsigned integer indicating the number of input + * batches that were fully evaluated. For this single + * evaluation it is 1 when a non-empty batch was + * evaluated and 0 otherwise (e.g. an empty input or a + * timeout before evaluation completed). See + * ddwaf_subcontext_multieval for the multi-batch case. * This structure must be freed by the caller and will contain all * specified keys when the value returned by ddwaf_subcontext_eval * is either DDWAF_OK or DDWAF_MATCH and will be empty otherwise. @@ -425,6 +521,89 @@ ddwaf_subcontext ddwaf_subcontext_init(ddwaf_context context); DDWAF_RET_CODE ddwaf_subcontext_eval(ddwaf_subcontext subcontext, ddwaf_object *data, ddwaf_allocator alloc, ddwaf_object *result, uint64_t timeout); +/** + * Perform multiple matching operations on the provided data, evaluating each + * batch in sequence and returning a single combined result. + * + * This function operates identically to ddwaf_subcontext_eval, with the + * distinction that data must be an array of maps. Each element is treated as + * a separate input batch and is evaluated in order. Addresses provided in + * earlier batches persist in the store and remain available when evaluating + * later batches, so a rule that requires multiple addresses can be satisfied + * by data spread across different batches within a single call. The result + * reflects all events, actions, and attributes accumulated across the entire + * sequence of batches. + * + * @param subcontext WAF subcontext to be used in this run, this will determine + * the ruleset which will be used and it will also ensure that + * parameters are taken into account across runs. (nonnull) + * + * @param data (nonnull) Array of input batches to evaluate. Each element must + * be a map of {string, } where each key represents the relevant + * address associated to the value, which can be of an arbitrary type. The + * batches are evaluated in order. If the same address appears in multiple + * batches, the later value replaces the earlier one. Passing a non-array + * object will return DDWAF_ERR_INVALID_OBJECT. + * Ownership and lifetime semantics are identical to ddwaf_subcontext_eval: + * the data is stored by the subcontext and the provided allocator is used + * to free it once the subcontext is destroyed. + * + * @param alloc (nullable) Allocator used to free the data provided. If NULL, + * the data will not be freed. + * + * @param result (nullable) Object map containing the following items: + * - events: an array of all events generated across all batches. + * - actions: a map of all actions generated across all batches + * in the format: "{action type: { }, ...}" + * - duration: an unsigned specifying the total runtime of the + * call in nanoseconds. + * - timeout: whether there has been a timeout during the call. + * - attributes: a map containing all derived objects in the + * format: {tag, value} + * - keep: whether the data contained herein must override any + * transport sampling through the relevant mechanism. + * - evaluated: an unsigned integer indicating the number of + * batches that were fully evaluated. In the normal + * case this equals the number of non-empty batches. + * On timeout or error occurring during batch I + * (0-based, counting only non-empty batches), this + * value equals I, which is also the index of the + * batch where the problem occurred. Empty batches are + * skipped and do not count towards this value. + * This structure must be freed by the caller and will contain all + * specified keys when the value returned by + * ddwaf_subcontext_multieval is either DDWAF_OK or DDWAF_MATCH + * and will be empty otherwise. + * IMPORTANT: This object is not allocated with the allocator + * passed in this call. It uses the allocator given to + * ddwaf_context_init instead. + * @param timeout Maximum time budget in microseconds. + * + * @return Return code of the operation. + * @retval DDWAF_ERR_INVALID_ARGUMENT The subcontext is invalid, the data will + * not be freed. + * @retval DDWAF_ERR_INVALID_OBJECT The data provided didn't match the desired + * structure or contained invalid objects, the + * data will be freed by this function. + * @retval DDWAF_ERR_INTERNAL There was an unexpected error and the operation did + * not succeed. The state of the WAF is undefined if + * this error is produced and the ownership of the + * data is unknown. The result structure will not be + * filled if this error occurs. + * + * Notes on addresses: + * - Within a single batch, addresses provided should be unique. + * If duplicate addresses are provided within the same batch, the latest one + * in the structure will be used for evaluation. + * - Addresses from earlier batches persist in the store and are accessible + * during evaluation of subsequent batches within the same call. If the same + * address appears in a later batch, the later value replaces the earlier one. + * - A rule that requires multiple addresses can be satisfied by data spread + * across different batches within a single call. + **/ +DDWAF_RET_CODE ddwaf_subcontext_multieval(ddwaf_subcontext subcontext, ddwaf_object *data, + ddwaf_allocator alloc, ddwaf_object *result, uint64_t timeout); + /** * Performs the destruction of the subcontext, freeing the data passed to it through * ddwaf_subcontext_eval using the used-defined allocator. diff --git a/libddwaf.def b/libddwaf.def index aff52982c..0badfb6fa 100644 --- a/libddwaf.def +++ b/libddwaf.def @@ -11,9 +11,11 @@ EXPORTS ddwaf_known_addresses ddwaf_context_init ddwaf_context_eval + ddwaf_context_multieval ddwaf_context_destroy ddwaf_subcontext_init ddwaf_subcontext_eval + ddwaf_subcontext_multieval ddwaf_subcontext_destroy ddwaf_object_destroy ddwaf_get_version diff --git a/smoketest/smoke.c b/smoketest/smoke.c index a49d69221..b659a46c4 100644 --- a/smoketest/smoke.c +++ b/smoketest/smoke.c @@ -273,5 +273,34 @@ int main() { puts("result is valid"); ddwaf_object_destroy(&result, alloc); + // Exercise the multieval entrypoint. It takes an array of input batches + // (each a map of addresses); the actual data is irrelevant here, this just + // verifies the symbol is exported and the library loads correctly. + ddwaf_context multi_ctx = ddwaf_context_init(handle, alloc); + if (!multi_ctx) { + puts("multi_ctx is null"); + return 1; + } + + ddwaf_object multi_data; + ddwaf_object_set_array(&multi_data, 1, alloc); + + ddwaf_object *batch = ddwaf_object_insert(&multi_data, alloc); + ddwaf_object_set_map(batch, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(batch, STRL("key"), alloc), STRL("Arachni"), alloc); + + ddwaf_object multi_result = {0}; + ddwaf_context_multieval(multi_ctx, &multi_data, alloc, &multi_result, (uint32_t)-1); + + const ddwaf_object *multi_events = + ddwaf_object_find(&multi_result, "events", sizeof("events") - 1); + if (ddwaf_object_get_size(multi_events) == 0) { + puts("multieval result is empty"); + return 1; + } + puts("multieval result is valid"); + ddwaf_object_destroy(&multi_result, alloc); + return 0; } diff --git a/src/context.hpp b/src/context.hpp index 1c498529f..9b1b1c950 100644 --- a/src/context.hpp +++ b/src/context.hpp @@ -46,16 +46,28 @@ class subcontext { subcontext &operator=(subcontext &&) noexcept = delete; subcontext &operator=(const subcontext &) = delete; - bool insert(owned_object data) + bool insert_batch(owned_object data) { const memory::memory_resource_guard guard(mr_.get()); - return engine_->insert(std::move(data)); + return engine_->insert_batch(std::move(data)); } - bool insert(map_view data) + bool insert_batch(map_view data) { const memory::memory_resource_guard guard(mr_.get()); - return engine_->insert(data); + return engine_->insert_batch(data); + } + + bool insert_batches(owned_object data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_batches(std::move(data)); + } + + bool insert_batches(array_view data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_batches(data); } std::pair eval(timer &deadline) @@ -65,6 +77,21 @@ class subcontext { } // Internals exposed for testing + bool next_batch() + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->next_batch(); + } + bool insert_and_apply(owned_object data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_and_apply(std::move(data)); + } + bool insert_and_apply(map_view data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_and_apply(data); + } void eval_preprocessors(timer &deadline) { const memory::memory_resource_guard guard(mr_.get()); @@ -141,16 +168,28 @@ class context { context &operator=(context &&) noexcept = delete; context &operator=(const context &) = delete; - bool insert(owned_object data) + bool insert_batch(owned_object data) { const memory::memory_resource_guard guard(mr_.get()); - return engine_->insert(std::move(data)); + return engine_->insert_batch(std::move(data)); } - bool insert(map_view data) + bool insert_batch(map_view data) { const memory::memory_resource_guard guard(mr_.get()); - return engine_->insert(data); + return engine_->insert_batch(data); + } + + bool insert_batches(owned_object data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_batches(std::move(data)); + } + + bool insert_batches(array_view data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_batches(data); } std::pair eval(timer &deadline) @@ -162,6 +201,21 @@ class context { subcontext create_subcontext() { return subcontext{*engine_, store_, mr_}; } // Internals exposed for testing + bool next_batch() + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->next_batch(); + } + bool insert_and_apply(owned_object data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_and_apply(std::move(data)); + } + bool insert_and_apply(map_view data) + { + const memory::memory_resource_guard guard(mr_.get()); + return engine_->insert_and_apply(data); + } void eval_preprocessors(timer &deadline) { const memory::memory_resource_guard guard(mr_.get()); diff --git a/src/evaluation_engine.cpp b/src/evaluation_engine.cpp index 73046ed54..962744d6c 100644 --- a/src/evaluation_engine.cpp +++ b/src/evaluation_engine.cpp @@ -42,57 +42,63 @@ void set_context_event_address(object_store &store) return; } - store.insert(event_addr_idx, event_addr, owned_object::make_boolean(true)); + store.insert_target(event_addr_idx, event_addr, owned_object::make_boolean(true)); } } // namespace std::pair evaluation_engine::eval(timer &deadline) { - // Clear the last batch of targets on exit so that the process can identify - // new targets in the next eval - auto on_exit = defer([this]() { store_.clear_last_batch(); }); - result_serializer serializer(ruleset_->obfuscator.get(), *ruleset_->actions, output_alloc_); // Generate result object once relevant checks have been made auto [result_object, output] = serializer.initialise_result_object(); - if (!store_.has_new_targets()) { - return {false, std::move(result_object)}; - } - - try { - // Evaluate preprocessors first in their own try-catch, if there's a - // timeout we still need to evaluate rules unaffected by it. - eval_preprocessors(deadline); - // NOLINTNEXTLINE(bugprone-empty-catch) - } catch (const ddwaf::timeout_exception &) {} + // Once evaluation finishes (on any exit path, including a timeout) flush any + // input batches left unevaluated and reset the new-target set so that the + // next eval can identify new targets. + auto on_exit = defer([this]() { store_.flush_input_queue(); }); std::vector results; - - try { - // If no rule targets are available, there is no point in evaluating them - const bool should_eval_rules = check_new_rule_targets(); - const bool should_eval_filters = should_eval_rules || check_new_filter_targets(); - - if (should_eval_filters) { - // Filters need to be evaluated even if rules don't, otherwise it'll - // break the current condition cache mechanism which requires knowing - // if an address is new to this run. - const auto &policy = eval_filters(deadline); - - if (should_eval_rules) { - eval_rules(policy, results, deadline); - if (!results.empty()) { - set_context_event_address(store_); + std::size_t batches_evaluated = 0; + + // Each queued input batch is evaluated as if it were a separate eval call, + // draining the store's queue one batch at a time. + while (store_.next_batch()) { + try { + // Evaluate preprocessors first in their own try-catch, if there's a + // timeout we still need to evaluate rules unaffected by it. + eval_preprocessors(deadline); + // NOLINTNEXTLINE(bugprone-empty-catch) + } catch (const ddwaf::timeout_exception &) {} + + try { + // If no rule targets are available, there is no point in evaluating them + const bool should_eval_rules = check_new_rule_targets(); + const bool should_eval_filters = should_eval_rules || check_new_filter_targets(); + + if (should_eval_filters) { + // Filters need to be evaluated even if rules don't, otherwise it'll + // break the current condition cache mechanism which requires knowing + // if an address is new to this run. + const auto &policy = eval_filters(deadline); + + if (should_eval_rules) { + eval_rules(policy, results, deadline); + if (!results.empty()) { + set_context_event_address(store_); + } } } + + eval_postprocessors(deadline); + ++batches_evaluated; + } catch (const ddwaf::timeout_exception &) { + break; } + } - eval_postprocessors(deadline); - // NOLINTNEXTLINE(bugprone-empty-catch) - } catch (const ddwaf::timeout_exception &) {} + output.evaluated = owned_object::make_unsigned(batches_evaluated); // Collect pending attributes, this will check if any new attributes are // available (e.g. from a postprocessor) and return a map of all attributes diff --git a/src/evaluation_engine.hpp b/src/evaluation_engine.hpp index 2a19efa1a..5414eca73 100644 --- a/src/evaluation_engine.hpp +++ b/src/evaluation_engine.hpp @@ -48,23 +48,49 @@ class evaluation_engine { evaluation_engine &operator=(evaluation_engine &&) = delete; ~evaluation_engine() = default; - bool insert(owned_object data) noexcept + bool insert_batch(owned_object data) noexcept { - if (!store_.insert(std::move(data))) { + if (!store_.insert_batch(std::move(data))) { DDWAF_WARN("Illegal WAF call: parameter structure invalid!"); return false; } return true; } - bool insert(map_view data) noexcept + bool insert_batch(map_view data) noexcept { - if (!store_.insert(data)) { + if (!store_.insert_batch(data)) { DDWAF_WARN("Illegal WAF call: parameter structure invalid!"); return false; } return true; } + + bool insert_batches(owned_object data) noexcept + { + if (!store_.insert_batches(std::move(data))) { + DDWAF_WARN("Illegal WAF call: parameter structure invalid!"); + return false; + } + return true; + } + + bool insert_batches(array_view data) noexcept + { + if (!store_.insert_batches(data)) { + DDWAF_WARN("Illegal WAF call: parameter structure invalid!"); + return false; + } + return true; + } + + // Internals exposed for testing + bool next_batch() { return store_.next_batch(); } + bool insert_and_apply(owned_object data) noexcept + { + return store_.insert_and_apply(std::move(data)); + } + bool insert_and_apply(map_view data) noexcept { return store_.insert_and_apply(data); } std::pair eval(timer &deadline); static evaluation_engine context_engine(std::shared_ptr ruleset, object_store &store, diff --git a/src/interface.cpp b/src/interface.cpp index cd773fdce..d91756b6e 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -263,15 +263,16 @@ DDWAF_RET_CODE ddwaf_context_eval(ddwaf_context context, ddwaf_object *data, try { if (alloc != nullptr) { - if (!context->insert( - // safety: caller is responsible to ensure that the passed - // allocator can deallocate memory allocated for `data` - owned_object{to_ref(data), to_alloc_ptr(alloc)})) { + // safety: caller is responsible to ensure that the passed allocator + // can deallocate memory allocated for `data`. An array carries + // multiple input batches, anything else is a single (map) batch. + owned_object input{to_ref(data), to_alloc_ptr(alloc)}; + if (!context->insert_batch(std::move(input))) { return DDWAF_ERR_INVALID_OBJECT; } } else { const object_view input{to_ref(data)}; - if (!input.is_map() || !context->insert(input.as())) { + if (!input.is_map() || !context->insert_batch(input.as())) { return DDWAF_ERR_INVALID_OBJECT; } } @@ -299,6 +300,45 @@ DDWAF_RET_CODE ddwaf_context_eval(ddwaf_context context, ddwaf_object *data, return DDWAF_ERR_INTERNAL; } +DDWAF_RET_CODE ddwaf_context_multieval(ddwaf_context context, ddwaf_object *data, + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + ddwaf_allocator alloc, ddwaf_object *result, uint64_t timeout) +{ + if (context == nullptr || data == nullptr) { + DDWAF_WARN("Illegal WAF call: context or data was null"); + return DDWAF_ERR_INVALID_ARGUMENT; + } + + try { + if (alloc != nullptr) { + if (!context->insert_batches(owned_object{to_ref(data), to_alloc_ptr(alloc)})) { + return DDWAF_ERR_INVALID_OBJECT; + } + } else { + const object_view input{to_ref(data)}; + if (!input.is_array() || !context->insert_batches(input.as())) { + return DDWAF_ERR_INVALID_OBJECT; + } + } + + constexpr uint64_t max_timeout_us = std::chrono::nanoseconds::max().count() / 1000; + timeout = std::min(timeout, max_timeout_us); + + timer deadline{std::chrono::microseconds(timeout)}; + auto [code, res] = context->eval(deadline); + if (result != nullptr) { + *to_ptr(result) = res.move(); + } + return code ? DDWAF_MATCH : DDWAF_OK; + } catch (const std::exception &e) { + DDWAF_ERROR("{}", e.what()); + } catch (...) { + DDWAF_ERROR("unknown exception"); + } + + return DDWAF_ERR_INTERNAL; +} + void ddwaf_context_destroy(ddwaf_context context) { try { @@ -335,15 +375,16 @@ DDWAF_RET_CODE ddwaf_subcontext_eval(ddwaf_subcontext subcontext, ddwaf_object * try { if (alloc != nullptr) { - if (!subcontext->insert( - // safety: caller is responsible to ensure that the passed - // allocator can deallocate memory allocated for `data` - owned_object{to_ref(data), to_alloc_ptr(alloc)})) { + // safety: caller is responsible to ensure that the passed allocator + // can deallocate memory allocated for `data`. An array carries + // multiple input batches, anything else is a single (map) batch. + owned_object input{to_ref(data), to_alloc_ptr(alloc)}; + if (!subcontext->insert_batch(std::move(input))) { return DDWAF_ERR_INVALID_OBJECT; } } else { const object_view input{to_ref(data)}; - if (!input.is_map() || !subcontext->insert(input.as())) { + if (!input.is_map() || !subcontext->insert_batch(input.as())) { return DDWAF_ERR_INVALID_OBJECT; } } @@ -367,6 +408,45 @@ DDWAF_RET_CODE ddwaf_subcontext_eval(ddwaf_subcontext subcontext, ddwaf_object * return DDWAF_ERR_INTERNAL; } +DDWAF_RET_CODE ddwaf_subcontext_multieval(ddwaf_subcontext subcontext, ddwaf_object *data, + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + ddwaf_allocator alloc, ddwaf_object *result, uint64_t timeout) +{ + if (subcontext == nullptr || data == nullptr) { + DDWAF_WARN("Illegal WAF call: subcontext or data was null"); + return DDWAF_ERR_INVALID_ARGUMENT; + } + + try { + if (alloc != nullptr) { + if (!subcontext->insert_batches(owned_object{to_ref(data), to_alloc_ptr(alloc)})) { + return DDWAF_ERR_INVALID_OBJECT; + } + } else { + const object_view input{to_ref(data)}; + if (!input.is_array() || !subcontext->insert_batches(input.as())) { + return DDWAF_ERR_INVALID_OBJECT; + } + } + + constexpr uint64_t max_timeout_us = std::chrono::nanoseconds::max().count() / 1000; + timeout = std::min(timeout, max_timeout_us); + + timer deadline{std::chrono::microseconds(timeout)}; + auto [code, res] = subcontext->eval(deadline); + if (result != nullptr) { + *to_ptr(result) = res.move(); + } + return code ? DDWAF_MATCH : DDWAF_OK; + } catch (const std::exception &e) { + DDWAF_ERROR("{}", e.what()); + } catch (...) { + DDWAF_ERROR("unknown exception"); + } + + return DDWAF_ERR_INTERNAL; +} + void ddwaf_subcontext_destroy(ddwaf_subcontext subcontext) { try { diff --git a/src/object_store.cpp b/src/object_store.cpp index 81c4e1316..b43c8df51 100644 --- a/src/object_store.cpp +++ b/src/object_store.cpp @@ -14,26 +14,90 @@ namespace ddwaf { -bool object_store::insert(owned_object &&input) +bool object_store::insert_batch(owned_object &&input) { + // The input object retains ownership of the enqueued batch, so it must + // outlive the queue. const object_view view = input_objects_.emplace_back(std::move(input)); if (!view.is_map()) { return false; } - return insert(view); + enqueue_batch(view); + return true; } -bool object_store::insert(map_view input) +bool object_store::insert_batch(map_view input) { - const auto size = input.size(); - if (size == 0) { - // Objects with no addresses are considered valid as they are harmless - return true; + enqueue_batch(input); + return true; +} + +bool object_store::insert_batches(array_view input) +{ + // An array represents a sequence of input batches; every element must + // itself be a map of addresses - validate all of them before enqueueing so + // that a failure leaves the queue untouched. + for (auto element : input) { + if (!element.is_map()) { + return false; + } + } + + for (auto element : input) { enqueue_batch(element); } + + return true; +} + +bool object_store::insert_batches(owned_object &&input) +{ + // The input object retains ownership of every enqueued batch, so it must + // outlive the queue. + const object_view view = input_objects_.emplace_back(std::move(input)); + if (!view.is_array()) { + return false; + } + + return insert_batches(array_view{view}); +} + +void object_store::enqueue_batch(map_view input) +{ + // Batches with no addresses are considered valid as they are harmless + if (!input.empty()) { + object_queue_.emplace_back(input); + } +} + +bool object_store::next_batch() +{ + if (object_queue_.empty()) { + return false; + } + + apply_batch(object_queue_.front(), /*mark_new=*/true); + object_queue_.pop_front(); + return true; +} + +void object_store::flush_input_queue() +{ + if (!object_queue_.empty()) { + DDWAF_DEBUG("Flushing remaining queued objects"); + for (auto input : object_queue_) { apply_batch(input, /*mark_new=*/false); } + object_queue_.clear(); } + latest_batch_.clear(); +} + +void object_store::apply_batch(map_view input, bool mark_new) +{ + const auto size = input.size(); targets_.reserve(targets_.size() + size); - latest_batch_.reserve(latest_batch_.size() + size); + if (mark_new) { + latest_batch_.reserve(latest_batch_.size() + size); + } for (std::size_t i = 0; i < size; ++i) { auto [key_obj, value] = input.at(i); @@ -43,21 +107,24 @@ bool object_store::insert(map_view input) auto key = key_obj.as(); auto target = get_target_index(key); - insert_target_helper(target, key, value); - } - return true; + if (targets_.contains(target)) { + DDWAF_DEBUG("Replacing target '{}' in object store", key); + } else { + DDWAF_DEBUG("Inserting target '{}' into object store", key); + } + + targets_[target] = value; + if (mark_new) { + latest_batch_.emplace(target); + } + } } -bool object_store::insert(target_index target, std::string_view key, owned_object &&input) +bool object_store::insert_target(target_index target, std::string_view key, owned_object &&input) { const object_view view = input_objects_.emplace_back(std::move(input)); - return insert_target_helper(target, key, view); -} - -bool object_store::insert_target_helper(target_index target, std::string_view key, object_view view) -{ if (targets_.contains(target)) { DDWAF_DEBUG("Replacing target '{}' in object store", key); } else { diff --git a/src/object_store.hpp b/src/object_store.hpp index 15ea95235..00e106c71 100644 --- a/src/object_store.hpp +++ b/src/object_store.hpp @@ -23,9 +23,48 @@ class object_store { object_store &operator=(const object_store &other) = delete; object_store &operator=(object_store &&) = default; - bool insert(owned_object &&input); - bool insert(map_view input); - bool insert(target_index target, std::string_view key, owned_object &&input); + // Enqueue a single batch of input addresses (a map). The batch is not + // applied to the store until consumed via next_batch(); a batch with no + // addresses is accepted as a harmless no-op. Returns false if the object is + // not a map. + bool insert_batch(owned_object &&input); + bool insert_batch(map_view input); + + // Enqueue a sequence of input batches: an array whose every element is a + // map, each queued as a separate batch. Returns false if the object is not + // an array or any element is not a map. + bool insert_batches(owned_object &&input); + bool insert_batches(array_view input); + // Insert a single derived target (e.g. produced by a processor) directly, + // marking it as new. Unlike the batch insert overloads above this takes + // effect immediately rather than being queued. + bool insert_target(target_index target, std::string_view key, owned_object &&input); + + // Consume the next queued input batch, applying its addresses to the store + // and marking them as new. Returns false once the queue is drained. + bool next_batch(); + + // Enqueue and immediately apply a single batch. Used for testing only. + bool insert_and_apply(owned_object &&input) + { + if (!insert_batch(std::move(input))) { + return false; + } + next_batch(); + return true; + } + bool insert_and_apply(map_view input) + { + insert_batch(input); + next_batch(); + return true; + } + + // Apply any remaining queued batches as targets *without* marking them as + // new (so they won't be evaluated) and clear the new-target set. Invoked + // once evaluation finishes, on any exit path including a timeout, mirroring + // the inputs being carried over to a subsequent ddwaf_context_eval call. + void flush_input_queue(); [[nodiscard]] object_view get_target(target_index target) const { @@ -49,7 +88,6 @@ class object_store { } [[nodiscard]] bool has_new_targets() const { return !latest_batch_.empty(); } [[nodiscard]] bool empty() const { return targets_.empty(); } - void clear_last_batch() { latest_batch_.clear(); } // An object store created from an upstream store assumes that the original // store retains ownership and will outlive this store, therefore only the @@ -63,10 +101,16 @@ class object_store { } private: - bool insert_target_helper(target_index target, std::string_view key, object_view view); + // Append a single input batch to the queue, ignoring empty batches. + void enqueue_batch(map_view input); + + // Apply a single input batch's addresses to the store. When mark_new is + // true the addresses are added to the new-target set and will be evaluated. + void apply_batch(map_view input, bool mark_new); memory::list input_objects_; + memory::list object_queue_; memory::unordered_set latest_batch_; memory::unordered_map targets_; }; diff --git a/src/processor/base.hpp b/src/processor/base.hpp index eda4e3c03..2e8b346db 100644 --- a/src/processor/base.hpp +++ b/src/processor/base.hpp @@ -208,12 +208,13 @@ template class structured_processor : public base_processor { // If the object is to be evaluated, we clone it before adding it to the // collector using the user-provided allocator. collector.insert(mapping.output.name, object.clone(alloc)); - store.insert(mapping.output.index, mapping.output.name, std::move(object)); + store.insert_target( + mapping.output.index, mapping.output.name, std::move(object)); } else { collector.insert(mapping.output.name, std::move(object)); } } else { - store.insert(mapping.output.index, mapping.output.name, std::move(object)); + store.insert_target(mapping.output.index, mapping.output.name, std::move(object)); } } } diff --git a/src/serializer.cpp b/src/serializer.cpp index bfd29ebdf..3cd49cac2 100644 --- a/src/serializer.cpp +++ b/src/serializer.cpp @@ -373,7 +373,8 @@ std::pair result_serializer::initialise_result_ {"duration", owned_object::make_unsigned(0)}, {"timeout", owned_object::make_boolean(false)}, {"attributes", object_builder::map({}, alloc_)}, - {"keep", owned_object::make_boolean(false)}}, + {"keep", owned_object::make_boolean(false)}, + {"evaluated", owned_object::make_unsigned(0)}}, alloc_); const result_components res{.events = object.at(0), @@ -381,7 +382,8 @@ std::pair result_serializer::initialise_result_ .duration = object.at(2), .timeout = object.at(3), .attributes = object.at(4), - .keep = object.at(5)}; + .keep = object.at(5), + .evaluated = object.at(6)}; return {std::move(object), res}; } diff --git a/src/serializer.hpp b/src/serializer.hpp index 6a76a72ef..5e87dcfd3 100644 --- a/src/serializer.hpp +++ b/src/serializer.hpp @@ -28,6 +28,7 @@ struct result_components { borrowed_object timeout; borrowed_object attributes; borrowed_object keep; + borrowed_object evaluated; }; // NOLINTEND(cppcoreguidelines-avoid-const-or-ref-data-members) diff --git a/tests/integration/context/test.cpp b/tests/integration/context/test.cpp index 3bff452c7..5ce5c3157 100644 --- a/tests/integration/context/test.cpp +++ b/tests/integration/context/test.cpp @@ -551,6 +551,66 @@ TEST(TestContextIntegration, DuplicateSubcontextMatch) ddwaf_destroy(handle); } +TEST(TestContextIntegration, SubcontextRemainsFunctionalAfterContextDestruction) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("processor3.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + // Load non-matching persistent data into the context. This populates the + // context store so that the subcontext, once derived, inherits object views + // pointing into it. The value doesn't match, so the shared flow1 collection + // is left unmatched and rule1 can still fire in the subcontext later. + { + ddwaf_object persistent; + ddwaf_object_set_map(&persistent, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(&persistent, STRL("param2"), alloc), STRL("harmless"), alloc); + + ddwaf_object ret; + EXPECT_EQ(ddwaf_context_eval(context, &persistent, alloc, &ret, LONG_TIME), DDWAF_OK); + ddwaf_object_destroy(&ret, alloc); + } + + // Derive a subcontext, then destroy the originating context. The subcontext + // shares ownership of the context-level state it depends on (object store, + // memory resource and ruleset), so it must remain fully functional - both + // the inherited context store and its own evaluation. + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_context_destroy(context); + + ddwaf_object param1; + ddwaf_object_set_map(¶m1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(¶m1, STRL("param1"), alloc), STRL("Sqreen"), alloc); + + ddwaf_object ret; + EXPECT_EQ(ddwaf_subcontext_eval(subctx, ¶m1, alloc, &ret, LONG_TIME), DDWAF_MATCH); + EXPECT_EVENTS(ret, {.id = "1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}}, + .matches = {{.op = "match_regex", + .op_value = "Sqreen", + .highlight = "Sqreen"sv, + .args = {{ + .value = "Sqreen"sv, + .address = "param1", + }}}}}); + + ddwaf_object_destroy(&ret, alloc); + ddwaf_subcontext_destroy(subctx); + ddwaf_destroy(handle); +} + TEST(TestContextIntegration, SubcontextAndContextMatches) { auto *alloc = ddwaf_get_default_allocator(); diff --git a/tests/integration/interface/context/multieval/test.cpp b/tests/integration/interface/context/multieval/test.cpp new file mode 100644 index 000000000..5d6eef13a --- /dev/null +++ b/tests/integration/interface/context/multieval/test.cpp @@ -0,0 +1,1033 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2021 Datadog, Inc. + +#include "common/gtest_utils.hpp" +#include "ddwaf.h" + +using namespace ddwaf; + +namespace { + +constexpr std::string_view base_dir = "integration/interface/context/multieval/"; + +//------------------------------------------------------------------------------ +// ddwaf_context_multieval tests +//------------------------------------------------------------------------------ + +TEST(TestContextMultievalIntegration, InvalidArgumentNullContext) +{ + auto *alloc = ddwaf_get_default_allocator(); + + ddwaf_object data; + ddwaf_object_set_array(&data, 2, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 0, alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(nullptr, &data, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_ARGUMENT); + + ddwaf_object_destroy(&data, alloc); +} + +TEST(TestContextMultievalIntegration, InvalidArgumentNullData) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, nullptr, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_ARGUMENT); + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, InvalidArgumentNotArray) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + // Pass a map instead of an array + ddwaf_object data; + ddwaf_object_set_map(&data, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_OBJECT); + + ddwaf_object_destroy(&data, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, SingleMapNoMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 1, alloc); + + ddwaf_object *elem = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem, STRL("value1"), alloc), STRL("no_match"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_OK); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 0); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 1); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, SingleMapWithMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 1, alloc); + + ddwaf_object *elem = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 1); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 1); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, MultipleMapsNoMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 3, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("no_match"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("no_match"), alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value3"), alloc), STRL("no_match"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_OK); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 0); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 3); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, MultipleMapsFirstMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 3, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("no_match"), alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value3"), alloc), STRL("no_match"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 1); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 3); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, MultipleMapsLastMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 3, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("no_match"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("no_match"), alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value3"), alloc), STRL("rule3"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 1); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 3); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, MultipleMapsAllMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 3, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("rule2"), alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value3"), alloc), STRL("rule3"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 3); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 3); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, NullResult) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 2, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("no_match"), alloc); + + // Pass nullptr for result - should still work + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, NullAlloc) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 2, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("no_match"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + // Pass nullptr for alloc - data should be treated as borrowed (not freed) + EXPECT_EQ(ddwaf_context_multieval(context, &data, nullptr, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 1); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 2); + + ddwaf_object_destroy(&result, alloc); + + // Manually destroy data since null alloc means context won't free it + ddwaf_object_destroy(&data, alloc); + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, ContextStateAccumulates) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + // First multieval call + ddwaf_object data1; + ddwaf_object_set_array(&data1, 1, alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data1, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result1; + ddwaf_object_set_invalid(&result1); + + EXPECT_EQ(ddwaf_context_multieval(context, &data1, alloc, &result1, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result1), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result1, STRL("events"))), 1); + ddwaf_object_destroy(&result1, alloc); + + // Second multieval call - same rule should not trigger again (already matched) + ddwaf_object data2; + ddwaf_object_set_array(&data2, 1, alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data2, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result2; + ddwaf_object_set_invalid(&result2); + + EXPECT_EQ(ddwaf_context_multieval(context, &data2, alloc, &result2, LONG_TIME), DDWAF_OK); + + ASSERT_EQ(ddwaf_object_get_type(&result2), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result2, STRL("events"))), 0); + ddwaf_object_destroy(&result2, alloc); + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, EmptyArray) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_OK); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 0); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 0); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, InvalidArgumentArrayContainsNonMap) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + // Array where the middle element is a string, not a map + ddwaf_object data; + ddwaf_object_set_array(&data, 3, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 0, alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_string(elem1, STRL("not_a_map"), alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem2, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_OBJECT); + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, SameRuleDoesNotDoubleFireWithinCall) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + // Both batches provide the same address with a matching value. + // rule1 fires in batch 1; in batch 2 value1 is overwritten (still matching), + // but the rule module cache prevents it from generating a second event. + ddwaf_object data; + ddwaf_object_set_array(&data, 2, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 1); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestContextMultievalIntegration, CrossBatchDataCombination) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + // rule4 requires both value_a (matching "rule4a") AND value_b (matching "rule4b"). + // Batch 1 satisfies only the first condition; rule4 cannot fire yet. + // Batch 2 satisfies the second condition; the store still holds value_a from + // batch 1, so rule4 now fires. This is the defining semantic of multieval. + ddwaf_object data; + ddwaf_object_set_array(&data, 2, alloc); + + ddwaf_object *batch1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(batch1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(batch1, STRL("value_a"), alloc), STRL("rule4a"), alloc); + + ddwaf_object *batch2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(batch2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(batch2, STRL("value_b"), alloc), STRL("rule4b"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_context_multieval(context, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 1); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 2); + + ddwaf_object_destroy(&result, alloc); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +//------------------------------------------------------------------------------ +// ddwaf_subcontext_multieval tests +//------------------------------------------------------------------------------ + +TEST(TestSubcontextMultievalIntegration, InvalidArgumentNullSubcontext) +{ + auto *alloc = ddwaf_get_default_allocator(); + + ddwaf_object data; + ddwaf_object_set_array(&data, 2, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 0, alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(nullptr, &data, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_ARGUMENT); + + ddwaf_object_destroy(&data, alloc); +} + +TEST(TestSubcontextMultievalIntegration, InvalidArgumentNullData) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, nullptr, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_ARGUMENT); + + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, InvalidArgumentNotArray) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + // Pass a map instead of an array + ddwaf_object data; + ddwaf_object_set_map(&data, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_OBJECT); + + ddwaf_object_destroy(&data, alloc); + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, SingleMapNoMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 1, alloc); + + ddwaf_object *elem = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem, STRL("value1"), alloc), STRL("no_match"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data, alloc, &result, LONG_TIME), DDWAF_OK); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 0); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 1); + + ddwaf_object_destroy(&result, alloc); + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, SingleMapWithMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 1, alloc); + + ddwaf_object *elem = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 1); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 1); + + ddwaf_object_destroy(&result, alloc); + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, MultipleMapsAllMatch) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 3, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("rule2"), alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value3"), alloc), STRL("rule3"), alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data, alloc, &result, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result, STRL("events"))), 3); + EXPECT_EQ(ddwaf_object_get_unsigned(ddwaf_object_find(&result, STRL("evaluated"))), 3); + + ddwaf_object_destroy(&result, alloc); + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, NullResult) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 2, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem0, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value2"), alloc), STRL("no_match"), alloc); + + // Pass nullptr for result - should still work + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data, alloc, nullptr, LONG_TIME), DDWAF_MATCH); + + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, SubcontextStateAccumulates) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + // First multieval call + ddwaf_object data1; + ddwaf_object_set_array(&data1, 1, alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data1, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result1; + ddwaf_object_set_invalid(&result1); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data1, alloc, &result1, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result1), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result1, STRL("events"))), 1); + ddwaf_object_destroy(&result1, alloc); + + // Second multieval call - same rule should not trigger again + ddwaf_object data2; + ddwaf_object_set_array(&data2, 1, alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data2, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result2; + ddwaf_object_set_invalid(&result2); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data2, alloc, &result2, LONG_TIME), DDWAF_OK); + + ASSERT_EQ(ddwaf_object_get_type(&result2), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result2, STRL("events"))), 0); + ddwaf_object_destroy(&result2, alloc); + + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, MultipleSubcontextsIndependent) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + // Create two subcontexts + ddwaf_subcontext subctx1 = ddwaf_subcontext_init(context); + ASSERT_NE(subctx1, nullptr); + + ddwaf_subcontext subctx2 = ddwaf_subcontext_init(context); + ASSERT_NE(subctx2, nullptr); + + // Trigger rule1 in subctx1 + ddwaf_object data1; + ddwaf_object_set_array(&data1, 1, alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data1, alloc); + ddwaf_object_set_map(elem1, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem1, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result1; + ddwaf_object_set_invalid(&result1); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx1, &data1, alloc, &result1, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result1), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result1, STRL("events"))), 1); + ddwaf_object_destroy(&result1, alloc); + + // Same rule should still trigger in subctx2 (independent state) + ddwaf_object data2; + ddwaf_object_set_array(&data2, 1, alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data2, alloc); + ddwaf_object_set_map(elem2, 1, alloc); + ddwaf_object_set_string( + ddwaf_object_insert_key(elem2, STRL("value1"), alloc), STRL("rule1"), alloc); + + ddwaf_object result2; + ddwaf_object_set_invalid(&result2); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx2, &data2, alloc, &result2, LONG_TIME), DDWAF_MATCH); + + ASSERT_EQ(ddwaf_object_get_type(&result2), DDWAF_OBJ_MAP); + EXPECT_EQ(ddwaf_object_get_size(ddwaf_object_find(&result2, STRL("events"))), 1); + ddwaf_object_destroy(&result2, alloc); + + ddwaf_subcontext_destroy(subctx1); + ddwaf_subcontext_destroy(subctx2); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestSubcontextMultievalIntegration, InvalidArgumentArrayContainsNonMap) +{ + auto *alloc = ddwaf_get_default_allocator(); + auto rule = read_file("rules.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + + ddwaf_handle handle = ddwaf_init(&rule, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_destroy(&rule, alloc); + + ddwaf_context context = ddwaf_context_init(handle, alloc); + ASSERT_NE(context, nullptr); + + ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + ASSERT_NE(subctx, nullptr); + + ddwaf_object data; + ddwaf_object_set_array(&data, 3, alloc); + + ddwaf_object *elem0 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem0, 0, alloc); + + ddwaf_object *elem1 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_string(elem1, STRL("not_a_map"), alloc); + + ddwaf_object *elem2 = ddwaf_object_insert(&data, alloc); + ddwaf_object_set_map(elem2, 0, alloc); + + ddwaf_object result; + ddwaf_object_set_invalid(&result); + + EXPECT_EQ(ddwaf_subcontext_multieval(subctx, &data, alloc, &result, LONG_TIME), + DDWAF_ERR_INVALID_OBJECT); + + ddwaf_subcontext_destroy(subctx); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +} // namespace diff --git a/tests/integration/interface/context/multieval/yaml/rules.yaml b/tests/integration/interface/context/multieval/yaml/rules.yaml new file mode 100644 index 000000000..aba0457eb --- /dev/null +++ b/tests/integration/interface/context/multieval/yaml/rules.yaml @@ -0,0 +1,51 @@ +version: '2.1' +rules: + - id: rule1 + name: rule1 + tags: + type: flow1 + category: category1 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value1 + regex: rule1 + - id: rule2 + name: rule2 + tags: + type: flow2 + category: category2 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value2 + regex: rule2 + - id: rule3 + name: rule3 + tags: + type: flow3 + category: category3 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value3 + regex: rule3 + - id: rule4 + name: rule4 + tags: + type: flow4 + category: category4 + conditions: + - operator: match_regex + parameters: + inputs: + - address: value_a + regex: rule4a + - operator: match_regex + parameters: + inputs: + - address: value_b + regex: rule4b diff --git a/tests/unit/attribute_collector_test.cpp b/tests/unit/attribute_collector_test.cpp index 5c30844ac..60a96fb33 100644 --- a/tests/unit/attribute_collector_test.cpp +++ b/tests/unit/attribute_collector_test.cpp @@ -63,7 +63,7 @@ TEST(TestAttributeCollector, CollectAvailableScalar) auto input = object_builder_da::map({{"input_address", expected}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; EXPECT_TRUE(collector.collect(store, get_target_index("input_address"), {}, "output_address")); @@ -85,7 +85,7 @@ TEST(TestAttributeCollector, CollectAvailableKeyPathScalar) object_builder_da::map({{"first", object_builder_da::map({{"second", expected}})}})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; std::vector> key_path{"first", "second"}; @@ -110,7 +110,7 @@ TEST(TestAttributeCollector, CollectAvailableKeyPathSingleValueArray) object_builder_da::map({{"second", object_builder_da::array({expected})}})}})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; std::vector> key_path{"first", "second"}; @@ -136,7 +136,7 @@ TEST(TestAttributeCollector, CollectAvailableKeyPathMultiValueArray) object_builder_da::array({expected, "value1", "value2"})}})}})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; std::vector> key_path{"first", "second"}; @@ -162,7 +162,7 @@ TEST(TestAttributeCollector, CollectAvailableKeyPathWithinArrayPositiveIndex) object_builder_da::array({"value0", expected, "value2"})}})}})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; std::vector> key_path{"first", "second", 1}; @@ -188,7 +188,7 @@ TEST(TestAttributeCollector, CollectAvailableKeyPathWithinArrayNegativeIndex) object_builder_da::array({"value0", expected, "value2"})}})}})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; std::vector> key_path{"first", "second", -2}; @@ -212,7 +212,7 @@ TEST(TestAttributeCollector, CollectUnavailableKeyPath) object_builder_da::map({{"third", "value"}})}})}})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; std::vector> key_path{"first", "second"}; @@ -240,7 +240,7 @@ TEST(TestAttributeCollector, CollectPendingKeyPathScalar) EXPECT_EQ(attributes.size(), 0); EXPECT_TRUE(collector.has_pending_attributes()); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); attributes = collector.get_available_attributes_and_reset(); EXPECT_FALSE(collector.has_pending_attributes()); @@ -259,7 +259,7 @@ TEST(TestAttributeCollector, CollectAvailableKeyPathInvalidValue) object_builder_da::map({{"second", object_builder_da::map()}})}})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; std::vector> key_path{"first", "second"}; @@ -278,7 +278,7 @@ TEST(TestAttributeCollector, CollectDuplicateScalar) auto input = object_builder_da::map({{"input_address", expected}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; EXPECT_TRUE(collector.collect(store, get_target_index("input_address"), {}, "output_address")); @@ -300,7 +300,7 @@ TEST(TestAttributeCollector, CollectAvailableScalarFromSingleValueArray) auto input = object_builder_da::map({{"input_address", object_builder_da::array({expected})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; EXPECT_TRUE(collector.collect(store, get_target_index("input_address"), {}, "output_address")); @@ -322,7 +322,7 @@ TEST(TestAttributeCollector, CollectAvailableScalarFromMultiValueArray) {{"input_address", object_builder_da::array({expected, "value1", "value2"})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; EXPECT_TRUE(collector.collect(store, get_target_index("input_address"), {}, "output_address")); @@ -343,7 +343,7 @@ TEST(TestAttributeCollector, CollectInvalidObjectFromArray) {{"input_address", object_builder_da::array({object_builder_da::map()})}}); object_store store; - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); attribute_collector collector; EXPECT_FALSE(collector.collect(store, get_target_index("input_address"), {}, "output_address")); @@ -374,7 +374,7 @@ TEST(TestAttributeCollector, CollectUnavailableScalar) std::string_view expected = "value"; auto input = object_builder_da::map({{"input_address", expected}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_FALSE(collector.has_pending_attributes()); @@ -409,7 +409,7 @@ TEST(TestAttributeCollector, CollectUnavailableScalarFromSingleValueArray) std::string_view expected = "value"; auto input = object_builder_da::map({{"input_address", object_builder_da::array({expected})}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_FALSE(collector.has_pending_attributes()); @@ -445,7 +445,7 @@ TEST(TestAttributeCollector, CollectUnavailableScalarFromMultiValueArray) auto input = object_builder_da::map( {{"input_address", object_builder_da::array({expected, "value1", "value2"})}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_FALSE(collector.has_pending_attributes()); @@ -482,7 +482,7 @@ TEST(TestAttributeCollector, CollectUnavailableKeyPathFromWithinArrayPositiveInd auto input = object_builder_da::map( {{"input_address", object_builder_da::array({expected, "value1", "value2"})}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_FALSE(collector.has_pending_attributes()); @@ -519,7 +519,7 @@ TEST(TestAttributeCollector, CollectUnavailableKeyPathFromWithinArrayNegativeInd auto input = object_builder_da::map( {{"input_address", object_builder_da::array({expected, "value1", "value2"})}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_FALSE(collector.has_pending_attributes()); @@ -553,7 +553,7 @@ TEST(TestAttributeCollector, CollectUnavailableInvalidObject) // the expected attribute auto input = object_builder_da::map({{"input_address", object_builder_da::array()}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_FALSE(collector.has_pending_attributes()); @@ -600,7 +600,7 @@ TEST(TestAttributeCollector, CollectMultipleUnavailableScalars) std::string_view expected = "value"; auto input = object_builder_da::map({{"input_address_0", expected}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); EXPECT_TRUE( collector.collect(store, get_target_index("input_address_2"), {}, "output_address_2")); @@ -622,7 +622,7 @@ TEST(TestAttributeCollector, CollectMultipleUnavailableScalars) std::string_view expected = "value"; auto input = object_builder_da::map({{"input_address_2", expected}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_TRUE(collector.has_pending_attributes()); @@ -642,7 +642,7 @@ TEST(TestAttributeCollector, CollectMultipleUnavailableScalars) std::string_view expected = "value"; auto input = object_builder_da::map({{"input_address_1", expected}}); - store.insert(std::move(input)); + store.insert_and_apply(std::move(input)); collector.collect_pending(store); EXPECT_FALSE(collector.has_pending_attributes()); diff --git a/tests/unit/condition/cmdi_detector_test.cpp b/tests/unit/condition/cmdi_detector_test.cpp index 12bf794e3..f1f2a9987 100644 --- a/tests/unit/condition/cmdi_detector_test.cpp +++ b/tests/unit/condition/cmdi_detector_test.cpp @@ -42,7 +42,7 @@ TEST(TestCmdiDetector, InvalidType) {{"server.sys.exec.cmd", object_builder_da::map()}, {"server.request.query", "whatever"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -57,7 +57,7 @@ TEST(TestCmdiDetector, EmptyResource) {"server.request.query", "whatever"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -104,7 +104,7 @@ TEST(TestCmdiDetector, NoInjection) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -141,7 +141,7 @@ TEST(TestCmdiDetector, NoExecutableInjection) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -195,7 +195,7 @@ TEST(TestCmdiDetector, NoShellInjection) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -229,7 +229,7 @@ TEST(TestCmdiDetector, ExecutableInjectionLinux) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -277,7 +277,7 @@ TEST(TestCmdiDetector, ExecutableInjectionWindows) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -320,7 +320,7 @@ TEST(TestCmdiDetector, ExecutableWithSpacesInjection) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -546,7 +546,7 @@ TEST(TestCmdiDetector, LinuxShellInjection) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -633,7 +633,7 @@ TEST(TestCmdiDetector, WindowsShellInjection) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -670,7 +670,7 @@ TEST(TestCmdiDetector, ExecutableInjectionMultipleArguments) for (const auto &[key, value] : params) { map.emplace(key, value); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -705,7 +705,7 @@ TEST(TestCmdiDetector, EmptyExecutable) for (const auto &[key, value] : params) { map.emplace(key, value); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -731,7 +731,7 @@ TEST(TestCmdiDetector, ShellInjectionMultipleArguments) for (const auto &[key, value] : params) { map.emplace(key, value); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/condition/exists_condition_test.cpp b/tests/unit/condition/exists_condition_test.cpp index f5a8d5266..1ee07ac17 100644 --- a/tests/unit/condition/exists_condition_test.cpp +++ b/tests/unit/condition/exists_condition_test.cpp @@ -27,7 +27,7 @@ TEST(TestExistsCondition, AddressAvailable) auto root = object_builder_da::map({{"server.request.uri_raw", owned_object{}}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -46,7 +46,7 @@ TEST(TestExistsCondition, KeyPathAvailable) {{"to", object_builder_da::map({{"object", owned_object{}}})}})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -60,7 +60,7 @@ TEST(TestExistsCondition, AddressNotAvaialble) auto root = object_builder_da::map({{"server.request.query", owned_object{}}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -77,7 +77,7 @@ TEST(TestExistsCondition, KeyPathNotAvailable) object_builder_da::map({{"path", object_builder_da::map({{"to", owned_object{}}})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -94,7 +94,7 @@ TEST(TestExistsCondition, KeyPathPositiveIndexOnArray) object_builder_da::map({{"path", object_builder_da::array({"item"})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -111,7 +111,7 @@ TEST(TestExistsCondition, KeyPathNegativeIndexOnArray) object_builder_da::map({{"path", object_builder_da::array({"item"})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -128,7 +128,7 @@ TEST(TestExistsCondition, KeyPathIndexOnNonArray) {{"server.request.uri_raw", object_builder_da::map({{"path", owned_object{}}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -148,7 +148,7 @@ TEST(TestExistsCondition, KeyPathAvailableButExcluded) std::unordered_set excluded = {root.at(0)}; object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -168,7 +168,7 @@ TEST(TestExistsCondition, MultipleAddresses) auto root = object_builder_da::map({{address, owned_object{}}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -202,7 +202,7 @@ TEST(TestExistsCondition, MultipleAddressesAndKeyPaths) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -233,7 +233,7 @@ TEST(TestNegatedExistsCondition, KeyPathAvailable) {{"to", object_builder_da::map({{"object", owned_object{}}})}})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -250,7 +250,7 @@ TEST(TestNegativeExistsCondition, KeyPathAvailablePositiveIndexOnArray) object_builder_da::map({{"path", object_builder_da::array({"item"})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -267,7 +267,7 @@ TEST(TestNegativeExistsCondition, KeyPathAvailableNegativeIndexOnArray) object_builder_da::map({{"path", object_builder_da::array({"item"})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -284,7 +284,7 @@ TEST(TestNegativeExistsCondition, KeyPathUnvailablePositiveIndexOnArray) object_builder_da::map({{"path", object_builder_da::array({})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -301,7 +301,7 @@ TEST(TestNegativeExistsCondition, KeyPathUnvailableNegativeIndexOnArray) object_builder_da::map({{"path", object_builder_da::array({})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -317,7 +317,7 @@ TEST(TestNegatedExistsCondition, KeyPathNotAvailable) auto root = object_builder_da::map({{"server.request.uri_raw", object_builder_da::map({{"path", object_builder_da::map({{"to", owned_object{}}})}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -338,7 +338,7 @@ TEST(TestNegatedExistsCondition, KeyPathAvailableButExcluded) std::unordered_set excluded = {root.at(0)}; object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/condition/lfi_detector_test.cpp b/tests/unit/condition/lfi_detector_test.cpp index 31c842c38..46a3ec62d 100644 --- a/tests/unit/condition/lfi_detector_test.cpp +++ b/tests/unit/condition/lfi_detector_test.cpp @@ -39,7 +39,7 @@ TEST(TestLFIDetector, MatchBasicUnix) object_builder_da::map({{"server.io.fs.file", path}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -101,7 +101,7 @@ TEST(TestLFIDetector, MatchBasicWindows) object_builder_da::map({{"server.io.fs.file", path}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -129,7 +129,7 @@ TEST(TestLFIDetector, MatchWithKeyPath) server.request.query: {array: [ {map: ../etc/passwd}]}})"); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -155,13 +155,13 @@ TEST(TestLFIDetector, PartialSubcontextMatch) { auto root = object_builder_da::map({{"server.io.fs.file", "/var/www/html/../../../etc/passwd"}}); - ctx_store.insert(std::move(root)); + ctx_store.insert_and_apply(std::move(root)); } auto sctx_store = object_store::from_upstream_store(ctx_store); { auto root = object_builder_da::map({{"server.request.query", "../../../etc/passwd"}}); - sctx_store.insert(std::move(root)); + sctx_store.insert_and_apply(std::move(root)); } ddwaf::timer deadline{2s}; @@ -199,7 +199,7 @@ TEST(TestLFIDetector, NoMatchUnix) object_builder_da::map({{"server.io.fs.file", path}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -236,7 +236,7 @@ TEST(TestLFIDetector, NoMatchWindows) object_builder_da::map({{"server.io.fs.file", path}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -258,7 +258,7 @@ TEST(TestLFIDetector, NoMatchExcludedPath) std::unordered_set exclusion{params_map.at(0)}; object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -276,7 +276,7 @@ TEST(TestLFIDetector, NoMatchExcludedAddress) std::unordered_set exclusion{root.at(1)}; object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -294,7 +294,7 @@ TEST(TestLFIDetector, Timeout) std::unordered_set exclusion{root.at(1)}; object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{0s}; condition_cache cache; @@ -311,7 +311,7 @@ TEST(TestLFIDetector, NoParams) }); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{0s}; condition_cache cache; diff --git a/tests/unit/condition/negated_scalar_condition_test.cpp b/tests/unit/condition/negated_scalar_condition_test.cpp index 40a3a8c95..6b4b338c6 100644 --- a/tests/unit/condition/negated_scalar_condition_test.cpp +++ b/tests/unit/condition/negated_scalar_condition_test.cpp @@ -53,7 +53,7 @@ TEST(TestNegatedScalarCondition, NoMatch) auto root = object_builder_da::map({{"server.request.uri.raw", "hello"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -73,7 +73,7 @@ TEST(TestNegatedScalarCondition, NoMatchWithKeyPath) object_builder_da::map({{"object", object_builder_da::array({{"bye"}})}})}})}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -88,7 +88,7 @@ TEST(TestNegatedScalarCondition, Timeout) auto root = object_builder_da::map({{"server.request.uri.raw", "hello"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{0s}; condition_cache cache; @@ -103,7 +103,7 @@ TEST(TestNegatedScalarCondition, SimpleMatch) auto root = object_builder_da::map({{"server.request.uri.raw", "bye"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -127,7 +127,7 @@ TEST(TestNegatedScalarCondition, SimpleMatchWithKeyPath) object_builder_da::map({{"to", object_builder_da::map({{"object", "bye"}})}})}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -149,7 +149,7 @@ TEST(TestNegatedScalarCondition, SingleValueArrayMatch) object_builder_da::map({{"server.request.uri.raw", object_builder_da::array({"bye"})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -175,7 +175,7 @@ TEST(TestNegatedScalarCondition, SingleValueArrayMatchWithKeyPath) object_builder_da::map({{"object", object_builder_da::array({"bye"})}})}})}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -197,7 +197,7 @@ TEST(TestNegatedScalarCondition, MultiValueArrayMatch) {{"server.request.uri.raw", object_builder_da::array({"bye", "greetings"})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -224,7 +224,7 @@ TEST(TestNegatedScalarCondition, MultiValueArrayMatchWithKeyPath) object_builder_da::array({"bye", "greetings"})}})}})}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -252,7 +252,7 @@ TEST(TestNegatedScalarCondition, ExcludedRootObject) object_builder_da::array({"bye", "greetings"})}})}})}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); std::unordered_set excluded_objects; excluded_objects.emplace(store.get_target(target_index)); @@ -281,7 +281,7 @@ TEST(TestNegatedScalarCondition, ExcludedIntermediateObject) excluded_objects.emplace(object_view{root}.find_key_path(kp).at_value(0)); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -307,7 +307,7 @@ TEST(TestNegatedScalarCondition, ExcludedFinalObject) std::unordered_set excluded_objects; excluded_objects.emplace(object_view{root}.find_key_path(kp).at_value(0)); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -326,14 +326,14 @@ TEST(TestNegatedScalarCondition, CachedMatch) { object_store store; - store.insert(root); + store.insert_and_apply(root); ASSERT_TRUE(cond.eval(cache, store, {}, {}, deadline)); } { object_store store; - store.insert(root); + store.insert_and_apply(root); ASSERT_FALSE(cond.eval(cache, store, {}, {}, deadline)); } @@ -351,7 +351,7 @@ TEST(TestNegatedScalarCondition, SimpleMatchOnKeys) {{"server.request.uri.raw", object_builder_da::map({{"bye", "hello"}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -368,7 +368,7 @@ TEST(TestNegatedScalarCondition, SimplesubcontextMatch) object_store ctx_store; { auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(root); + sctx_store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; @@ -377,7 +377,7 @@ TEST(TestNegatedScalarCondition, SimplesubcontextMatch) { auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(root); + sctx_store.insert_and_apply(root); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/condition/scalar_condition_test.cpp b/tests/unit/condition/scalar_condition_test.cpp index 23c708a48..5f763eb06 100644 --- a/tests/unit/condition/scalar_condition_test.cpp +++ b/tests/unit/condition/scalar_condition_test.cpp @@ -44,7 +44,7 @@ TEST(TestScalarCondition, NoMatch) auto root = object_builder_da::map({{"server.request.uri.raw", owned_object{}}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -59,7 +59,7 @@ TEST(TestScalarCondition, Timeout) auto root = object_builder_da::map({{"server.request.uri.raw", owned_object{}}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{0s}; condition_cache cache; @@ -74,7 +74,7 @@ TEST(TestScalarCondition, SimpleMatch) auto root = object_builder_da::map({{"server.request.uri.raw", "hello"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -93,14 +93,14 @@ TEST(TestScalarCondition, CachedMatch) { object_store store; - store.insert(root); + store.insert_and_apply(root); ASSERT_TRUE(cond.eval(cache, store, {}, {}, deadline)); } { object_store store; - store.insert(root); + store.insert_and_apply(root); ASSERT_FALSE(cond.eval(cache, store, {}, {}, deadline)); } @@ -118,7 +118,7 @@ TEST(TestScalarCondition, SimpleMatchOnKeys) {{"server.request.uri.raw", object_builder_da::map({{"hello", "hello"}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/condition/shi_detector_array_test.cpp b/tests/unit/condition/shi_detector_array_test.cpp index d7e7b404e..504cf19af 100644 --- a/tests/unit/condition/shi_detector_array_test.cpp +++ b/tests/unit/condition/shi_detector_array_test.cpp @@ -26,7 +26,7 @@ TEST(TestShiDetectorArray, InvalidType) {{"server.sys.shell.cmd", object_builder_da::map()}, {"server.request.query", "whatever"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -41,7 +41,7 @@ TEST(TestShiDetectorArray, EmptyResource) {"server.request.query", "whatever"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -56,7 +56,7 @@ TEST(TestShiDetectorArray, InvalidTypeWithinArray) object_builder_da::map(), "cat /etc/passwd"})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -112,7 +112,7 @@ TEST(TestShiDetectorArray, NoMatchAndFalsePositives) for (const auto &arg : resource) { array.emplace_back(arg); } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -155,7 +155,7 @@ TEST(TestShiDetectorArray, ExecutablesAndRedirections) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -208,7 +208,7 @@ TEST(TestShiDetectorArray, OverlappingInjections) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -247,7 +247,7 @@ TEST(TestShiDetectorArray, InjectionsWithinCommandSubstitution) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -290,7 +290,7 @@ TEST(TestShiDetectorArray, InjectionsWithinProcessSubstitution) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -339,7 +339,7 @@ TEST(TestShiDetectorArray, OffByOnePayloadsMatch) } object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/condition/shi_detector_string_test.cpp b/tests/unit/condition/shi_detector_string_test.cpp index fce62b2d9..c78b481c9 100644 --- a/tests/unit/condition/shi_detector_string_test.cpp +++ b/tests/unit/condition/shi_detector_string_test.cpp @@ -28,7 +28,7 @@ TEST(TestShiDetectorString, InvalidType) {"server.request.query", "whatever"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -43,7 +43,7 @@ TEST(TestShiDetectorString, EmptyResource) {{"server.sys.shell.cmd", ""}, {"server.request.query", "whatever"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -83,7 +83,7 @@ TEST(TestShiDetectorString, NoMatchAndFalsePositives) }); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -113,7 +113,7 @@ TEST(TestShiDetectorString, ExecutablesAndRedirections) }); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -155,7 +155,7 @@ TEST(TestShiDetectorString, InjectionsWithinCommandSubstitution) }); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -190,7 +190,7 @@ TEST(TestShiDetectorString, InjectionsWithinProcessSubstitution) }); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -227,7 +227,7 @@ TEST(TestShiDetectorString, OffByOnePayloadsMatch) }); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -285,7 +285,7 @@ TEST(TestShiDetectorString, MultipleArgumentsMatch) {"server.request.query", yaml_to_object(params)}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/condition/sqli_detector_test.cpp b/tests/unit/condition/sqli_detector_test.cpp index 54c30300c..a45b3d36f 100644 --- a/tests/unit/condition/sqli_detector_test.cpp +++ b/tests/unit/condition/sqli_detector_test.cpp @@ -39,7 +39,7 @@ TEST_P(DialectTestFixture, InvalidSql) {"server.db.system", dialect}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -63,7 +63,7 @@ TEST_P(DialectTestFixture, InjectionWithoutTokens) {"server.db.system", dialect}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -99,7 +99,7 @@ TEST_P(DialectTestFixture, BenignInjections) {"server.db.system", dialect}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -148,7 +148,7 @@ TEST_P(DialectTestFixture, MaliciousInjections) {"server.db.system", dialect}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -217,7 +217,7 @@ TEST_P(DialectTestFixture, Tautologies) {"server.db.system", dialect}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -260,7 +260,7 @@ TEST_P(DialectTestFixture, Comments) {"server.db.system", dialect}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -300,7 +300,7 @@ TEST(TestSqliDetectorMySql, Comments) {"server.db.system", "mysql"}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -343,7 +343,7 @@ TEST(TestSqliDetectorMySql, Tautologies) {"server.db.system", "mysql"}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; @@ -389,7 +389,7 @@ TEST(TestSqliDetectorPgSql, Tautologies) {"server.db.system", "pgsql"}, {"server.request.query", input}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/condition/ssrf_detector_test.cpp b/tests/unit/condition/ssrf_detector_test.cpp index 636bf98e2..5bffe5fdc 100644 --- a/tests/unit/condition/ssrf_detector_test.cpp +++ b/tests/unit/condition/ssrf_detector_test.cpp @@ -53,7 +53,7 @@ void match_path_and_input(const std::vector> {"server.request.query", yaml_to_object(sample.yaml)}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; condition_cache cache; diff --git a/tests/unit/evaluation_engine_test.cpp b/tests/unit/evaluation_engine_test.cpp index 28f474279..f1e40a0b6 100644 --- a/tests/unit/evaluation_engine_test.cpp +++ b/tests/unit/evaluation_engine_test.cpp @@ -40,7 +40,7 @@ TEST(TestEvaluationEngine, MatchTimeout) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; EXPECT_THROW(ctx.eval_rules({}, results, deadline), ddwaf::timeout_exception); @@ -64,7 +64,7 @@ TEST(TestEvaluationEngine, NoMatch) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.2"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -89,7 +89,7 @@ TEST(TestEvaluationEngine, Match) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -130,7 +130,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesInCollectionSingleRun) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -195,7 +195,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesWithPrioritySingleRun) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; std::vector results; @@ -216,7 +216,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesWithPrioritySingleRun) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; std::vector results; @@ -268,7 +268,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesInCollectionDoubleRun) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -296,7 +296,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesInCollectionDoubleRun) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -340,7 +340,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -370,7 +370,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesWithPriorityDoubleRunPriorityLast) // An existing match in a collection will not inhibit a match in a // priority collection. auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -435,7 +435,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -465,7 +465,7 @@ TEST(TestEvaluationEngine, MatchMultipleRulesWithPriorityDoubleRunPriorityFirst) // An existing match in a collection will not inhibit a match in a // priority collection. auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -508,7 +508,7 @@ TEST(TestEvaluationEngine, MatchMultipleCollectionsSingleRun) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -553,7 +553,7 @@ TEST(TestEvaluationEngine, MatchPriorityCollectionsSingleRun) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -595,7 +595,7 @@ TEST(TestEvaluationEngine, MatchMultipleCollectionsDoubleRun) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -604,7 +604,7 @@ TEST(TestEvaluationEngine, MatchMultipleCollectionsDoubleRun) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -650,7 +650,7 @@ TEST(TestEvaluationEngine, MatchMultiplePriorityCollectionsDoubleRun) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -659,7 +659,7 @@ TEST(TestEvaluationEngine, MatchMultiplePriorityCollectionsDoubleRun) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); std::vector results; ctx.eval_rules({}, results, deadline); @@ -703,7 +703,7 @@ TEST(TestEvaluationEngine, RuleFilterWithCondition) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto rules_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(rules_to_exclude.size(), 1); @@ -751,10 +751,10 @@ TEST(TestEvaluationEngine, RuleFilterWithSubcontextConditionMatch) auto persistent = object_builder_da::map({{"usr.id", "admin"}}); auto ephemeral = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - EXPECT_TRUE(ctx.insert(std::move(persistent))); + EXPECT_TRUE(ctx.insert_batch(std::move(persistent))); auto sctx = ctx.create_subcontext(); - EXPECT_TRUE(sctx.insert(std::move(ephemeral))); + EXPECT_TRUE(sctx.insert_batch(std::move(ephemeral))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = sctx.eval(deadline); @@ -763,7 +763,7 @@ TEST(TestEvaluationEngine, RuleFilterWithSubcontextConditionMatch) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - EXPECT_TRUE(ctx.insert(std::move(root))); + EXPECT_TRUE(ctx.insert_batch(std::move(root))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = ctx.eval(deadline); EXPECT_EQ(code, DDWAF_MATCH); @@ -820,10 +820,10 @@ TEST(TestEvaluationEngine, OverlappingRuleFiltersSubcontextBypassPersistentMonit auto persistent = object_builder_da::map({{"usr.id", "admin"}, {"http.route", "unrouted"}}); auto ephemeral = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - EXPECT_TRUE(ctx.insert(std::move(persistent))); + EXPECT_TRUE(ctx.insert_batch(std::move(persistent))); auto sctx = ctx.create_subcontext(); - EXPECT_TRUE(sctx.insert(std::move(ephemeral))); + EXPECT_TRUE(sctx.insert_batch(std::move(ephemeral))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = sctx.eval(deadline); @@ -832,7 +832,7 @@ TEST(TestEvaluationEngine, OverlappingRuleFiltersSubcontextBypassPersistentMonit { auto root = object_builder_da::map({{"usr.id", "admin"}}); - EXPECT_TRUE(ctx.insert(std::move(root))); + EXPECT_TRUE(ctx.insert_batch(std::move(root))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = ctx.eval(deadline); @@ -892,10 +892,10 @@ TEST(TestEvaluationEngine, OverlappingRuleFiltersSubcontextMonitorPersistentBypa auto persistent = object_builder_da::map({{"usr.id", "admin"}, {"http.route", "unrouted"}}); auto ephemeral = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - EXPECT_TRUE(ctx.insert(std::move(persistent))); + EXPECT_TRUE(ctx.insert_batch(std::move(persistent))); auto sctx = ctx.create_subcontext(); - EXPECT_TRUE(sctx.insert(std::move(ephemeral))); + EXPECT_TRUE(sctx.insert_batch(std::move(ephemeral))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = sctx.eval(deadline); @@ -904,7 +904,7 @@ TEST(TestEvaluationEngine, OverlappingRuleFiltersSubcontextMonitorPersistentBypa { auto root = object_builder_da::map({{"usr.id", "admin"}}); - EXPECT_TRUE(ctx.insert(std::move(root))); + EXPECT_TRUE(ctx.insert_batch(std::move(root))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = ctx.eval(deadline); @@ -948,7 +948,7 @@ TEST(TestEvaluationEngine, RuleFilterTimeout) auto root = object_builder_da::map({{"usr.id", "admin"}, {"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); EXPECT_THROW(ctx.eval_filters(deadline), ddwaf::timeout_exception); } @@ -989,7 +989,7 @@ TEST(TestEvaluationEngine, NoRuleFilterWithCondition) auto root = object_builder_da::map({{"usr.id", "admin"}, {"http.client_ip", "192.168.0.2"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto rules_to_exclude = ctx.eval_filters(deadline); EXPECT_TRUE(rules_to_exclude.empty()); @@ -1221,7 +1221,7 @@ TEST(TestEvaluationEngine, MultipleRuleFiltersNonOverlappingRulesWithConditions) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto rules_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(rules_to_exclude.size(), 5); @@ -1234,7 +1234,7 @@ TEST(TestEvaluationEngine, MultipleRuleFiltersNonOverlappingRulesWithConditions) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto rules_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(rules_to_exclude.size(), 10); @@ -1298,7 +1298,7 @@ TEST(TestEvaluationEngine, MultipleRuleFiltersOverlappingRulesWithConditions) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto rules_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(rules_to_exclude.size(), 7); @@ -1313,7 +1313,7 @@ TEST(TestEvaluationEngine, MultipleRuleFiltersOverlappingRulesWithConditions) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto rules_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(rules_to_exclude.size(), 10); @@ -1355,7 +1355,7 @@ TEST(TestEvaluationEngine, InputFilterExclude) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 1); @@ -1393,7 +1393,7 @@ TEST(TestEvaluationEngine, InputFilterExcludeSubcontext) auto sctx = ctx.create_subcontext(); - EXPECT_TRUE(sctx.insert(std::move(root))); + EXPECT_TRUE(sctx.insert_batch(std::move(root))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = sctx.eval(deadline); EXPECT_EQ(code, DDWAF_OK); @@ -1404,7 +1404,7 @@ TEST(TestEvaluationEngine, InputFilterExcludeSubcontext) auto sctx = ctx.create_subcontext(); - EXPECT_TRUE(sctx.insert(std::move(root))); + EXPECT_TRUE(sctx.insert_batch(std::move(root))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = sctx.eval(deadline); EXPECT_EQ(code, DDWAF_OK); @@ -1415,7 +1415,7 @@ TEST(TestEvaluationEngine, InputFilterExcludeSubcontext) auto sctx = ctx.create_subcontext(); - EXPECT_TRUE(sctx.insert(std::move(root))); + EXPECT_TRUE(sctx.insert_batch(std::move(root))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = sctx.eval(deadline); EXPECT_EQ(code, DDWAF_MATCH); @@ -1448,7 +1448,7 @@ TEST(TestEvaluationEngine, InputFilterExcludeRule) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); // The rule is added to the filter stage so that it's excluded from the // final result, since we're not actually excluding the rule from the match @@ -1493,7 +1493,7 @@ TEST(TestEvaluationEngine, InputFilterExcludeRuleSubcontext) auto sctx = ctx.create_subcontext(); - sctx.insert(std::move(root)); + sctx.insert_and_apply(std::move(root)); auto policy = sctx.eval_filters(deadline); EXPECT_EQ(policy.size(), 1); @@ -1531,7 +1531,7 @@ TEST(TestEvaluationEngine, InputFilterMonitorRuleSubcontext) auto sctx = ctx.create_subcontext(); - sctx.insert(std::move(root)); + sctx.insert_and_apply(std::move(root)); auto policy = sctx.eval_filters(deadline); EXPECT_EQ(policy.size(), 1); @@ -1567,14 +1567,14 @@ TEST(TestEvaluationEngine, InputFilterExcluderRuleSubcontextAndPersistent) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); } auto sctx = ctx.create_subcontext(); { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - sctx.insert(std::move(root)); + sctx.insert_and_apply(std::move(root)); } auto objects_to_exclude = sctx.eval_filters(deadline); @@ -1611,14 +1611,14 @@ TEST(TestEvaluationEngine, InputFilterMonitorRuleSubcontextAndPersistent) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); } auto sctx = ctx.create_subcontext(); { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - sctx.insert(std::move(root)); + sctx.insert_and_apply(std::move(root)); } auto objects_to_exclude = sctx.eval_filters(deadline); @@ -1668,7 +1668,7 @@ TEST(TestEvaluationEngine, InputFilterWithCondition) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 0); @@ -1685,7 +1685,7 @@ TEST(TestEvaluationEngine, InputFilterWithCondition) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admino"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 0); @@ -1702,7 +1702,7 @@ TEST(TestEvaluationEngine, InputFilterWithCondition) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 1); @@ -1751,11 +1751,11 @@ TEST(TestEvaluationEngine, InputFilterWithSubcontextCondition) auto persistent = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); auto ephemeral = object_builder_da::map({{"usr.id", "admin"}}); - EXPECT_TRUE(ctx.insert(std::move(persistent))); + EXPECT_TRUE(ctx.insert_batch(std::move(persistent))); auto sctx = ctx.create_subcontext(); - EXPECT_TRUE(sctx.insert(std::move(ephemeral))); + EXPECT_TRUE(sctx.insert_batch(std::move(ephemeral))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = sctx.eval(deadline); EXPECT_EQ(code, DDWAF_OK); @@ -1763,7 +1763,7 @@ TEST(TestEvaluationEngine, InputFilterWithSubcontextCondition) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - EXPECT_TRUE(ctx.insert(std::move(root))); + EXPECT_TRUE(ctx.insert_batch(std::move(root))); timer deadline{std::chrono::microseconds(LONG_TIME)}; auto [code, res] = ctx.eval(deadline); EXPECT_EQ(code, DDWAF_MATCH); @@ -1817,7 +1817,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRules) context ctx{rbuilder.build()}; auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 2); @@ -1837,7 +1837,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRules) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admino"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 2); @@ -1857,7 +1857,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRules) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 2); @@ -1925,7 +1925,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFilters) context ctx{rbuilder.build()}; auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 1); @@ -1946,7 +1946,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFilters) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admino"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 2); @@ -1967,7 +1967,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFilters) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(std::move(root)); + ctx.insert_and_apply(std::move(root)); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 2); @@ -2066,7 +2066,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFiltersMultipleObject context ctx{rbuilder.build()}; auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - ctx.insert(object_view{root}); + ctx.insert_and_apply(object_view{root}); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 3); @@ -2086,7 +2086,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFiltersMultipleObject context ctx{rbuilder.build()}; auto root = object_builder_da::map({{"usr.id", "admin"}}); - ctx.insert(object_view{root}); + ctx.insert_and_apply(object_view{root}); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 3); @@ -2107,7 +2107,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFiltersMultipleObject auto root = object_builder_da::map( {{"server.request.headers", object_builder_da::map({{"cookie", "mycookie"}})}}); - ctx.insert(object_view{root}); + ctx.insert_and_apply(object_view{root}); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 3); @@ -2128,7 +2128,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFiltersMultipleObject auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - ctx.insert(object_view{root}); + ctx.insert_and_apply(object_view{root}); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 3); @@ -2150,7 +2150,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFiltersMultipleObject auto root = object_builder_da::map( {{"server.request.headers", object_builder_da::map({{"cookie", "mycookie"}})}, {"usr.id", "admin"}}); - ctx.insert(object_view{root}); + ctx.insert_and_apply(object_view{root}); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 3); @@ -2172,7 +2172,7 @@ TEST(TestEvaluationEngine, InputFilterMultipleRulesMultipleFiltersMultipleObject auto root = object_builder_da::map( {{"server.request.headers", object_builder_da::map({{"cookie", "mycookie"}})}, {"usr.id", "admin"}, {"http.client_ip", "192.168.0.1"}}); - ctx.insert(object_view{root}); + ctx.insert_and_apply(object_view{root}); auto objects_to_exclude = ctx.eval_filters(deadline); EXPECT_EQ(objects_to_exclude.size(), 3); diff --git a/tests/unit/exclusion/input_filter_test.cpp b/tests/unit/exclusion/input_filter_test.cpp index 064c4ea11..50988ab1f 100644 --- a/tests/unit/exclusion/input_filter_test.cpp +++ b/tests/unit/exclusion/input_filter_test.cpp @@ -19,7 +19,7 @@ TEST(TestInputFilter, InputExclusionNoConditions) object_store store; auto root = object_builder_da::map({{"query", "value"}}); - store.insert(root); + store.insert_and_apply(root); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {}); @@ -45,7 +45,7 @@ TEST(TestInputFilter, ObjectExclusionNoConditions) auto child = root.emplace("query", object_builder_da::map()); child.emplace("params", "param"); - store.insert(root); + store.insert_and_apply(root); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); @@ -73,7 +73,7 @@ TEST(TestInputFilter, PersistentInputExclusionWithPersistentCondition) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip", {}); @@ -101,7 +101,7 @@ TEST(TestInputFilter, InputExclusionWithConditionAndTransformers) auto root = object_builder_da::map({{"usr.id", "ADMIN"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("usr.id"), "usr.id", {}); @@ -129,7 +129,7 @@ TEST(TestInputFilter, InputExclusionFailedCondition) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.2"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("http.client_ip"), "http.client_ip", {}); @@ -158,7 +158,7 @@ TEST(TestInputFilter, ObjectExclusionWithCondition) auto child = root.emplace("query", object_builder_da::map({{"params", "value"}})); object_store store; - store.insert(root); + store.insert_and_apply(root); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); @@ -188,7 +188,7 @@ TEST(TestInputFilter, ObjectExclusionFailedCondition) {"query", object_builder_da::map({{"params", "value"}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); auto obj_filter = std::make_shared(); obj_filter->insert(get_target_index("query"), "query", {"params"}); @@ -229,7 +229,7 @@ TEST(TestInputFilter, InputValidateCachedMatch) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline).has_value()); @@ -239,7 +239,7 @@ TEST(TestInputFilter, InputValidateCachedMatch) auto root = object_builder_da::map({{"usr.id", "admin"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; auto opt_spec = filter.match(store, cache, {}, deadline); @@ -277,19 +277,19 @@ TEST(TestInputFilter, InputValidateCachedSubcontextMatch) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; - ctx_store.insert(objects[1]); + ctx_store.insert_and_apply(objects[1]); ddwaf::timer deadline{2s}; ASSERT_FALSE(filter.match(ctx_store, ctx_cache, {}, deadline)); } { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(objects[0]); + sctx_store.insert_and_apply(objects[0]); input_filter::cache_type sctx_cache = ctx_cache; ddwaf::timer deadline{2s}; @@ -301,19 +301,19 @@ TEST(TestInputFilter, InputValidateCachedSubcontextMatch) } { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; - ctx_store.insert(objects[2]); + ctx_store.insert_and_apply(objects[2]); ddwaf::timer deadline{2s}; ASSERT_FALSE(filter.match(ctx_store, ctx_cache, {}, deadline)); } { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(objects[3]); + sctx_store.insert_and_apply(objects[3]); input_filter::cache_type sctx_cache = ctx_cache; ddwaf::timer deadline{2s}; @@ -348,7 +348,7 @@ TEST(TestInputFilter, InputMatchWithoutCache) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; input_filter::cache_type cache; @@ -359,7 +359,7 @@ TEST(TestInputFilter, InputMatchWithoutCache) auto root = object_builder_da::map({{"usr.id", "admin"}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; input_filter::cache_type cache; @@ -395,7 +395,7 @@ TEST(TestInputFilter, InputNoMatchWithoutCache) objects.emplace_back(object_builder_da::map({{"usr.id", "admin"}})); { - store.insert(objects[0]); + store.insert_and_apply(objects[0]); ddwaf::timer deadline{2s}; input_filter::cache_type cache; @@ -403,7 +403,7 @@ TEST(TestInputFilter, InputNoMatchWithoutCache) } { - store.insert(objects[1]); + store.insert_and_apply(objects[1]); auto client_ip_ptr = store.get_target("http.client_ip"); @@ -447,8 +447,8 @@ TEST(TestInputFilter, InputCachedMatchSecondRun) objects.emplace_back(object_builder_da::map({{"random", "random"}})); { - defer cleanup{[&]() { store.clear_last_batch(); }}; - store.insert(objects[0]); + defer cleanup{[&]() { store.flush_input_queue(); }}; + store.insert_and_apply(objects[0]); ddwaf::timer deadline{2s}; auto opt_spec = filter.match(store, cache, {}, deadline); @@ -459,8 +459,8 @@ TEST(TestInputFilter, InputCachedMatchSecondRun) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; - store.insert(objects[1]); + defer cleanup{[&]() { store.flush_input_queue(); }}; + store.insert_and_apply(objects[1]); ddwaf::timer deadline{2s}; ASSERT_FALSE(filter.match(store, cache, {}, deadline).has_value()); @@ -498,7 +498,7 @@ TEST(TestInputFilter, ObjectValidateCachedMatch) { object_store store; - store.insert(objects[0]); + store.insert_and_apply(objects[0]); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline).has_value()); @@ -506,7 +506,7 @@ TEST(TestInputFilter, ObjectValidateCachedMatch) { object_store store; - store.insert(objects[1]); + store.insert_and_apply(objects[1]); ddwaf::timer deadline{2s}; auto opt_spec = filter.match(store, cache, {}, deadline); @@ -540,7 +540,7 @@ TEST(TestInputFilter, ObjectMatchWithoutCache) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"query", object_builder_da::map({{"params", "value"}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; input_filter::cache_type cache; @@ -552,7 +552,7 @@ TEST(TestInputFilter, ObjectMatchWithoutCache) auto root = object_builder_da::map( {{"usr.id", "admin"}, {"query", object_builder_da::map({{"params", "value"}})}}); object_store store; - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; input_filter::cache_type cache; @@ -589,7 +589,7 @@ TEST(TestInputFilter, ObjectNoMatchWithoutCache) objects.emplace_back(object_builder_da::map({{"usr.id", "admin"}})); { - store.insert(objects[0]); + store.insert_and_apply(objects[0]); ddwaf::timer deadline{2s}; input_filter::cache_type cache; @@ -597,7 +597,7 @@ TEST(TestInputFilter, ObjectNoMatchWithoutCache) } { - store.insert(objects[1]); + store.insert_and_apply(objects[1]); ddwaf::timer deadline{2s}; input_filter::cache_type cache; @@ -638,8 +638,8 @@ TEST(TestInputFilter, ObjectCachedMatchSecondRun) objects.emplace_back(object_builder_da::map({{"random", "random"}})); { - defer cleanup{[&]() { store.clear_last_batch(); }}; - store.insert(objects[0]); + defer cleanup{[&]() { store.flush_input_queue(); }}; + store.insert_and_apply(objects[0]); ddwaf::timer deadline{2s}; auto opt_spec = filter.match(store, cache, {}, deadline); @@ -649,8 +649,8 @@ TEST(TestInputFilter, ObjectCachedMatchSecondRun) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; - store.insert(objects[1]); + defer cleanup{[&]() { store.flush_input_queue(); }}; + store.insert_and_apply(objects[1]); ddwaf::timer deadline{2s}; ASSERT_FALSE(filter.match(store, cache, {}, deadline).has_value()); @@ -685,7 +685,7 @@ TEST(TestInputFilter, MatchWithDynamicMatcher) object_store store; input_filter::cache_type cache; - store.insert(objects[0]); + store.insert_and_apply(objects[0]); ddwaf::timer deadline{2s}; auto opt_spec = filter.match(store, cache, {}, deadline); @@ -696,7 +696,7 @@ TEST(TestInputFilter, MatchWithDynamicMatcher) object_store store; input_filter::cache_type cache; - store.insert(objects[1]); + store.insert_and_apply(objects[1]); std::unordered_map> matchers; matchers["ip_data"] = diff --git a/tests/unit/exclusion/object_filter_test.cpp b/tests/unit/exclusion/object_filter_test.cpp index 331713697..3fdf6f2b4 100644 --- a/tests/unit/exclusion/object_filter_test.cpp +++ b/tests/unit/exclusion/object_filter_test.cpp @@ -23,7 +23,7 @@ TEST(TestObjectFilter, RootTarget) auto root = object_builder_da::map({ {"query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})}, }); - store.insert(root); + store.insert_and_apply(root); object_filter filter; filter.insert(query, "query", {}); @@ -58,7 +58,7 @@ TEST(TestObjectFilter, DuplicateTarget) {"query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})}, })); { - store.insert(objects[0]); + store.insert_and_apply(objects[0]); auto objects_filtered = filter.match(store, cache, deadline); @@ -67,7 +67,7 @@ TEST(TestObjectFilter, DuplicateTarget) } { - store.insert(objects[1]); + store.insert_and_apply(objects[1]); auto objects_filtered = filter.match(store, cache, deadline); @@ -91,7 +91,7 @@ TEST(TestObjectFilter, DuplicateCachedTarget) auto root = object_builder_da::map({ {"query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})}, }); - store.insert(root); + store.insert_and_apply(root); { auto objects_filtered = filter.match(store, cache, deadline); @@ -115,7 +115,7 @@ TEST(TestObjectFilter, SingleTarget) auto child = root.emplace( "query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})); - store.insert(root); + store.insert_and_apply(root); object_filter filter; filter.insert(query, "query", {"params"}); @@ -145,7 +145,7 @@ TEST(TestObjectFilter, DuplicateSingleTarget) auto child = root.emplace( "query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 1); @@ -157,7 +157,7 @@ TEST(TestObjectFilter, DuplicateSingleTarget) auto child = root.emplace( "query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 1); @@ -184,7 +184,7 @@ TEST(TestObjectFilter, MultipleTargets) auto object = sibling.emplace( "token", object_builder_da::map({{"value", "naskjdnakjsd"}, {"expiration", "yesterday"}})); - store.insert(root); + store.insert_and_apply(root); object_filter filter; filter.insert(query, "query", {"uri"}); @@ -225,7 +225,7 @@ TEST(TestObjectFilter, DuplicateMultipleTargets) auto object = sibling.emplace("token", object_builder_da::map({{"value", "naskjdnakjsd"}, {"expiration", "yesterday"}})); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); auto objects_filtered = filter.match(store, cache, deadline); @@ -246,7 +246,7 @@ TEST(TestObjectFilter, DuplicateMultipleTargets) auto object = sibling.emplace("token", object_builder_da::map({{"value", "naskjdnakjsd"}, {"expiration", "yesterday"}})); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); auto objects_filtered = filter.match(store, cache, deadline); @@ -270,7 +270,7 @@ TEST(TestObjectFilter, MissingTarget) {"token", object_builder_da::map({{"value", "naskjdnakjsd"}, {"expiration", "yesterday"}})}})}, }); - store.insert(root); + store.insert_and_apply(root); object_filter filter; filter.insert(status, "status", {"value"}); @@ -291,7 +291,7 @@ TEST(TestObjectFilter, SingleTargetCache) auto child = root.emplace( "query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})); - store.insert(root); + store.insert_and_apply(root); object_filter filter; filter.insert(query, "query", {"params"}); @@ -328,7 +328,7 @@ TEST(TestObjectFilter, MultipleTargetsCache) auto child = root.emplace( "query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 1); @@ -343,7 +343,7 @@ TEST(TestObjectFilter, MultipleTargetsCache) auto object = sibling.emplace("token", object_builder_da::map({{"value", "naskjdnakjsd"}, {"expiration", "yesterday"}})); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 1); @@ -371,7 +371,7 @@ TEST(TestObjectFilter, SingleGlobTarget) auto child = root.emplace( "query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 2); @@ -388,7 +388,7 @@ TEST(TestObjectFilter, SingleGlobTarget) object_builder_da::map({{"params", object_builder_da::map({{"value", "paramsvalue"}})}, {"uri", "uri_value"}})); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 2); @@ -401,7 +401,7 @@ TEST(TestObjectFilter, SingleGlobTarget) object_filter::cache_type cache; auto root = object_builder_da::map({{"query", owned_object{}}}); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 0); @@ -425,7 +425,7 @@ TEST(TestObjectFilter, GlobAndKeyTarget) auto child = root.emplace( "query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 2); @@ -442,7 +442,7 @@ TEST(TestObjectFilter, GlobAndKeyTarget) object_builder_da::map({{"params", object_builder_da::map({{"value", "paramsvalue"}})}, {"uri", "uri_value"}})); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 2); @@ -455,7 +455,7 @@ TEST(TestObjectFilter, GlobAndKeyTarget) object_filter::cache_type cache; auto root = object_builder_da::map({{"query", owned_object{}}}); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 0); @@ -482,7 +482,7 @@ TEST(TestObjectFilter, MultipleComponentsGlobAndKeyTargets) {{"params", object_builder_da::map({{"other", "paramsvalue"}})}})); auto grandnephew = child.emplace("uri", object_builder_da::map({{"other", "paramsvalue"}})); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 1); @@ -499,7 +499,7 @@ TEST(TestObjectFilter, MultipleComponentsGlobAndKeyTargets) child.emplace("params", object_builder_da::map({{"value", "paramsvalue"}})); auto grandnephew = child.emplace("uri", object_builder_da::map({{"value", "paramsvalue"}})); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 2); @@ -515,7 +515,7 @@ TEST(TestObjectFilter, MultipleComponentsGlobAndKeyTargets) {{"value", object_builder_da::map({{"whatever", "paramsvalue"}})}, {"other", object_builder_da::map({{"random", "paramsvalue"}})}})}}); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 0); @@ -528,7 +528,7 @@ TEST(TestObjectFilter, MultipleComponentsGlobAndKeyTargets) owned_object root = object_builder_da::map({{"query", object_builder_da::map({{"value", "value"}})}}); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 0); @@ -557,7 +557,7 @@ TEST(TestObjectFilter, MultipleGlobsTargets) auto greatgrandnephew = grandnephew.emplace("random", object_builder_da::map({{"other", "paramsvalue"}, {"somethingelse", "paramsvalue"}})); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 4); @@ -575,7 +575,7 @@ TEST(TestObjectFilter, MultipleGlobsTargets) object_builder_da::map({{"params", object_builder_da::map({{"something", "value"}})}, {"uri", object_builder_da::map({{"random", "value"}})}})}}); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 0); @@ -588,7 +588,7 @@ TEST(TestObjectFilter, MultipleGlobsTargets) auto root = object_builder_da::map( {{"query", object_builder_da::map({{"params", "value"}, {"uri", "value"}})}}); - store.insert(root); + store.insert_and_apply(root); auto objects_filtered = filter.match(store, cache, deadline); ASSERT_EQ(objects_filtered.size(), 0); @@ -624,7 +624,7 @@ TEST(TestObjectFilter, MultipleComponentsMultipleGlobAndKeyTargets) object_store store; object_filter::cache_type cache; auto root = yaml_to_object(object); - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; auto objects_filtered = filter.match(store, cache, deadline); @@ -651,7 +651,7 @@ TEST(TestObjectFilter, MultipleComponentsMultipleGlobAndKeyTargets) object_store store; object_filter::cache_type cache; auto root = yaml_to_object(object); - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; auto objects_filtered = filter.match(store, cache, deadline); @@ -674,7 +674,7 @@ TEST(TestObjectFilter, ArrayWithGlobTargets) object_builder_da::map({{"a", object_builder_da::array({object_builder_da::map({{"c", object_builder_da::map({{"d", "value"}})}})})}})}}); - store.insert(root); + store.insert_and_apply(root); ddwaf::timer deadline{2s}; auto objects_filtered = filter.match(store, cache, deadline); @@ -690,7 +690,7 @@ TEST(TestObjectFilter, Timeout) auto root = object_builder_da::map( {{"query", object_builder_da::map({{"params", "paramsvalue"}, {"uri", "uri_value"}})}}); - store.insert(root); + store.insert_and_apply(root); object_filter filter; filter.insert(query, "query", {}); diff --git a/tests/unit/exclusion/rule_filter_test.cpp b/tests/unit/exclusion/rule_filter_test.cpp index 5e71f1667..40171fbdd 100644 --- a/tests/unit/exclusion/rule_filter_test.cpp +++ b/tests/unit/exclusion/rule_filter_test.cpp @@ -35,7 +35,7 @@ TEST(TestRuleFilter, Match) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -67,7 +67,7 @@ TEST(TestRuleFilter, MatchWithDynamicMatcher) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -80,7 +80,7 @@ TEST(TestRuleFilter, MatchWithDynamicMatcher) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -110,7 +110,7 @@ TEST(TestRuleFilter, NoMatch) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -144,7 +144,7 @@ TEST(TestRuleFilter, ValidateCachedMatch) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)); @@ -154,7 +154,7 @@ TEST(TestRuleFilter, ValidateCachedMatch) auto root = object_builder_da::map({{"usr.id", "admin"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -190,9 +190,9 @@ TEST(TestRuleFilter, CachedMatchAndSubcontextMatch) // only the latest address. This ensures that the IP condition can't be // matched on the second run. { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; - store.insert(object_builder_da::map({{"http.client_ip", "192.168.0.1"}})); + store.insert_and_apply(object_builder_da::map({{"http.client_ip", "192.168.0.1"}})); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)); @@ -202,7 +202,7 @@ TEST(TestRuleFilter, CachedMatchAndSubcontextMatch) auto root = object_builder_da::map({{"usr.id", "admin"}}); auto sctx_store = object_store::from_upstream_store(store); - sctx_store.insert(std::move(root)); + sctx_store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; rule_filter::excluded_set default_set{.rules = {}, .mode = {}, .action = {}}; @@ -238,7 +238,7 @@ TEST(TestRuleFilter, MatchWithoutCache) ddwaf::rule_filter::cache_type cache; auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)); @@ -248,7 +248,7 @@ TEST(TestRuleFilter, MatchWithoutCache) ddwaf::rule_filter::cache_type cache; auto root = object_builder_da::map({{"usr.id", "admin"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)->rules.empty()); @@ -279,7 +279,7 @@ TEST(TestRuleFilter, NoMatchWithoutCache) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)); @@ -290,7 +290,7 @@ TEST(TestRuleFilter, NoMatchWithoutCache) auto root = object_builder_da::map({{"usr.id", "admin"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)); @@ -323,7 +323,7 @@ TEST(TestRuleFilter, FullCachedMatchSecondRun) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)->rules.empty()); @@ -333,7 +333,7 @@ TEST(TestRuleFilter, FullCachedMatchSecondRun) { auto root = object_builder_da::map({{"random", "random"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; EXPECT_FALSE(filter.match(store, cache, {}, deadline)); diff --git a/tests/unit/expression_test.cpp b/tests/unit/expression_test.cpp index e0ba15a80..8e6852f48 100644 --- a/tests/unit/expression_test.cpp +++ b/tests/unit/expression_test.cpp @@ -27,7 +27,7 @@ TEST(TestExpression, SimpleMatch) auto root = object_builder_da::map({{"server.request.query", "value"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -58,7 +58,7 @@ TEST(TestExpression, SimpleNegatedMatch) auto root = object_builder_da::map({{"server.request.query", "val"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -91,11 +91,11 @@ TEST(TestExpression, MultiInputMatchOnSecondEval) expression::cache_type cache; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "bad"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -103,11 +103,11 @@ TEST(TestExpression, MultiInputMatchOnSecondEval) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.body", "value"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -138,11 +138,11 @@ TEST(TestExpression, DuplicateInput) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "bad"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -150,11 +150,11 @@ TEST(TestExpression, DuplicateInput) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "value"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -174,11 +174,11 @@ TEST(TestExpression, MatchDuplicateInputNoCache) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "bad"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -187,11 +187,11 @@ TEST(TestExpression, MatchDuplicateInputNoCache) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "value"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -229,11 +229,11 @@ TEST(TestExpression, TwoConditionsSingleInputNoMatch) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "bad_value"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -241,11 +241,11 @@ TEST(TestExpression, TwoConditionsSingleInputNoMatch) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "value"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -271,7 +271,7 @@ TEST(TestExpression, TwoConditionsSingleInputMatch) auto root = object_builder_da::map({{"server.request.query", "value"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -300,7 +300,7 @@ TEST(TestExpression, TwoConditionsMultiInputSingleEvalMatch) auto root = object_builder_da::map( {{"server.request.query", "query"}, {"server.request.body", "body"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -326,11 +326,11 @@ TEST(TestExpression, TwoConditionsMultiInputMultiEvalMatch) expression::cache_type cache; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map({{"server.request.query", "query"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -338,12 +338,12 @@ TEST(TestExpression, TwoConditionsMultiInputMultiEvalMatch) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto root = object_builder_da::map( {{"server.request.query", "red-herring"}, {"server.request.body", "body"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -364,7 +364,7 @@ TEST(TestExpression, MatchWithKeyPath) {{"server.request.query", object_builder_da::map({{"key", "value"}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -393,7 +393,7 @@ TEST(TestExpression, MatchWithTransformer) auto root = object_builder_da::map({{"server.request.query", "VALUE"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -422,7 +422,7 @@ TEST(TestExpression, MatchWithMultipleTransformers) auto root = object_builder_da::map({{"server.request.query", " VALUE "}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -451,7 +451,7 @@ TEST(TestExpression, MatchOnKeys) {{"server.request.query", object_builder_da::map({{"value", "1729"}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -481,7 +481,7 @@ TEST(TestExpression, MatchOnKeysWithTransformer) {{"server.request.query", object_builder_da::map({{"VALUE", "1729"}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -510,7 +510,7 @@ TEST(TestExpression, ExcludeInput) auto root = object_builder_da::map({{"server.request.query", "value"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; std::unordered_set excluded_objects{store.get_target("server.request.query")}; @@ -532,7 +532,7 @@ TEST(TestExpression, ExcludeKeyPath) {{"server.request.query", object_builder_da::map({{"key", "value"}})}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; std::unordered_set excluded_objects{store.get_target("server.request.query")}; diff --git a/tests/unit/module_test.cpp b/tests/unit/module_test.cpp index baaa063e8..68b2ddb0a 100644 --- a/tests/unit/module_test.cpp +++ b/tests/unit/module_test.cpp @@ -53,7 +53,7 @@ TEST(TestModuleUngrouped, SingleRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -65,7 +65,7 @@ TEST(TestModuleUngrouped, SingleRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(root); + store.insert_and_apply(root); std::vector results; ddwaf::timer deadline = endless_timer(); mod.eval(results, store, cache, {}, {}, deadline); @@ -118,7 +118,7 @@ TEST(TestModuleUngrouped, MultipleMonitoringRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -132,7 +132,7 @@ TEST(TestModuleUngrouped, MultipleMonitoringRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(root); + store.insert_and_apply(root); std::vector results; ddwaf::timer deadline = endless_timer(); mod.eval(results, store, cache, {}, {}, deadline); @@ -186,7 +186,7 @@ TEST(TestModuleUngrouped, BlockingRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -243,7 +243,7 @@ TEST(TestModuleUngrouped, MonitoringRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -256,7 +256,7 @@ TEST(TestModuleUngrouped, MonitoringRuleMatch) // Check that we can still match the blocking rule { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.2"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -313,7 +313,7 @@ TEST(TestModuleUngrouped, BlockingRuleMatchBasePrecedence) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -373,7 +373,7 @@ TEST(TestModuleUngrouped, BlockingRuleMatchUserPrecedence) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -412,7 +412,7 @@ TEST(TestModuleUngrouped, NonExpiringModule) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline{0s}; @@ -449,7 +449,7 @@ TEST(TestModuleUngrouped, ExpiringModule) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline{0s}; @@ -487,7 +487,7 @@ TEST(TestModuleUngrouped, DisabledRules) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -539,7 +539,7 @@ TEST(TestModuleGrouped, MultipleGroupsMonitoringRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -595,7 +595,7 @@ TEST(TestModuleGrouped, MultipleGroupsBlockingRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -650,7 +650,7 @@ TEST(TestModuleGrouped, SingleGroupBlockingRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -704,7 +704,7 @@ TEST(TestModuleGrouped, SingleGroupMonitoringRuleMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -759,7 +759,7 @@ TEST(TestModuleGrouped, UserPrecedenceSingleGroupMonitoringUserMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -814,7 +814,7 @@ TEST(TestModuleGrouped, BasePrecedenceSingleGroupMonitoringBaseMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -870,7 +870,7 @@ TEST(TestModuleGrouped, UserPrecedenceSingleGroupBlockingBaseMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -926,7 +926,7 @@ TEST(TestModuleGrouped, UserPrecedenceSingleGroupBlockingUserMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -982,7 +982,7 @@ TEST(TestModuleGrouped, BasePrecedenceSingleGroupBlockingBaseMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1038,7 +1038,7 @@ TEST(TestModuleGrouped, BasePrecedenceSingleGroupBlockingUserMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1093,7 +1093,7 @@ TEST(TestModuleGrouped, UserPrecedenceMultipleGroupsMonitoringMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1149,7 +1149,7 @@ TEST(TestModuleGrouped, UserPrecedenceMultipleGroupsBlockingMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1204,7 +1204,7 @@ TEST(TestModuleGrouped, BasePrecedenceMultipleGroupsMonitoringMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1260,7 +1260,7 @@ TEST(TestModuleGrouped, BasePrecedenceMultipleGroupsBlockingMatch) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1404,7 +1404,7 @@ TEST(TestModuleGrouped, MultipleGroupsRulesAndMatches) object_store store; auto root = object_builder_da::map({{"http.client_ip", "192.168.0.2"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1507,7 +1507,7 @@ TEST(TestModuleGrouped, MultipleGroupsSingleMatchPerGroup) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1610,7 +1610,7 @@ TEST(TestModuleGrouped, MultipleGroupsOnlyBlockingMatch) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1651,7 +1651,7 @@ TEST(TestModuleGrouped, DisabledRules) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline = endless_timer(); @@ -1686,7 +1686,7 @@ TEST(TestModuleGrouped, NonExpiringModule) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline{0s}; @@ -1723,7 +1723,7 @@ TEST(TestModuleGrouped, ExpiringModule) { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::vector results; ddwaf::timer deadline{0s}; diff --git a/tests/unit/object_store_test.cpp b/tests/unit/object_store_test.cpp index ea29da7a6..47d755c32 100644 --- a/tests/unit/object_store_test.cpp +++ b/tests/unit/object_store_test.cpp @@ -20,7 +20,9 @@ TEST(TestObjectStore, InsertInvalidObject) auto url = get_target_index("url"); object_store store; - store.insert(owned_object{}); + // An empty owned_object is not a map, so insert_batch returns false and + // nothing is queued; no next_batch() call needed. + store.insert_batch(owned_object{}); EXPECT_TRUE(store.empty()); EXPECT_FALSE(store.has_new_targets()); @@ -37,7 +39,9 @@ TEST(TestObjectStore, InsertStringObject) object_store store; - store.insert(test::ddwaf_object_da::make_string("hello")); + // A string is not a map, so insert_batch returns false and nothing is + // queued; no next_batch() call needed. + store.insert_batch(test::ddwaf_object_da::make_string("hello")); EXPECT_TRUE(store.empty()); EXPECT_FALSE(store.has_new_targets()); @@ -56,7 +60,7 @@ TEST(TestObjectStore, InsertAndGetObject) root.emplace("query", test::ddwaf_object_da::make_string("hello")); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -73,13 +77,13 @@ TEST(TestObjectStore, InsertAndGetSubcontextObject) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; auto root = test::ddwaf_object_da::make_map(); root.emplace("query", test::ddwaf_object_da::make_string("hello")); auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(std::move(root)); + sctx_store.insert_and_apply(std::move(root)); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); @@ -105,7 +109,7 @@ TEST(TestObjectStore, InsertMultipleUniqueObjects) object_store ctx_store; { - ctx_store.insert(object_builder_da::map({{"query", "hello"}})); + ctx_store.insert_and_apply(object_builder_da::map({{"query", "hello"}})); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -117,7 +121,7 @@ TEST(TestObjectStore, InsertMultipleUniqueObjects) { auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(object_builder_da::map({{"url", "hello"}})); + sctx_store.insert_and_apply(object_builder_da::map({{"url", "hello"}})); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); @@ -128,7 +132,9 @@ TEST(TestObjectStore, InsertMultipleUniqueObjects) } { - ctx_store.insert(owned_object{}); + // An empty owned_object is not a map, so nothing is queued; latest_batch_ + // still contains query from the first insert above. + ctx_store.insert_batch(owned_object{}); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -138,7 +144,7 @@ TEST(TestObjectStore, InsertMultipleUniqueObjects) EXPECT_FALSE(ctx_store.get_target(url).has_value()); } - ctx_store.clear_last_batch(); + ctx_store.flush_input_queue(); EXPECT_FALSE(ctx_store.empty()); EXPECT_FALSE(ctx_store.has_new_targets()); @@ -155,12 +161,12 @@ TEST(TestObjectStore, InsertMultipleUniqueObjectBatches) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto first = test::ddwaf_object_da::make_map(); first.emplace("query", test::ddwaf_object_da::make_string("hello")); - store.insert(std::move(first)); + store.insert_and_apply(std::move(first)); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -171,12 +177,12 @@ TEST(TestObjectStore, InsertMultipleUniqueObjectBatches) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto second = test::ddwaf_object_da::make_map(); second.emplace("url", test::ddwaf_object_da::make_string("hello")); - store.insert(std::move(second)); + store.insert_and_apply(std::move(second)); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -187,10 +193,12 @@ TEST(TestObjectStore, InsertMultipleUniqueObjectBatches) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; + // An empty owned_object is not a map, so nothing is queued; latest_batch_ + // was already cleared by the previous flush_input_queue(). owned_object third = owned_object{}; - store.insert(std::move(third)); + store.insert_batch(std::move(third)); EXPECT_FALSE(store.empty()); EXPECT_FALSE(store.has_new_targets()); EXPECT_FALSE(store.is_new_target(query)); @@ -207,11 +215,11 @@ TEST(TestObjectStore, InsertMultipleOverlappingObjects) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; auto first = test::ddwaf_object_da::make_map(); first.emplace("query", test::ddwaf_object_da::make_string("hello")); - store.insert(std::move(first)); + store.insert_and_apply(std::move(first)); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -227,13 +235,13 @@ TEST(TestObjectStore, InsertMultipleOverlappingObjects) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; // Reinsert query auto second = test::ddwaf_object_da::make_map(); second.emplace("url", test::ddwaf_object_da::make_string("hello")); second.emplace("query", test::ddwaf_object_da::make_string("bye")); - store.insert(std::move(second)); + store.insert_and_apply(std::move(second)); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -256,11 +264,11 @@ TEST(TestObjectStore, InsertMultipleOverlappingObjects) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; // Reinsert url auto third = test::ddwaf_object_da::make_map(); third.emplace("url", test::ddwaf_object_da::make_string("bye")); - store.insert(std::move(third)); + store.insert_and_apply(std::move(third)); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -282,7 +290,7 @@ TEST(TestObjectStore, InsertSingleTargets) object_store ctx_store; - ctx_store.insert(query, "query", test::ddwaf_object_da::make_string("hello")); + ctx_store.insert_target(query, "query", test::ddwaf_object_da::make_string("hello")); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -293,7 +301,7 @@ TEST(TestObjectStore, InsertSingleTargets) { auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(url, "url", test::ddwaf_object_da::make_string("hello")); + sctx_store.insert_target(url, "url", test::ddwaf_object_da::make_string("hello")); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); @@ -303,7 +311,7 @@ TEST(TestObjectStore, InsertSingleTargets) EXPECT_TRUE(sctx_store.get_target(url).has_value()); } - ctx_store.clear_last_batch(); + ctx_store.flush_input_queue(); EXPECT_FALSE(ctx_store.empty()); EXPECT_FALSE(ctx_store.has_new_targets()); @@ -320,9 +328,9 @@ TEST(TestObjectStore, InsertSingleTargetBatches) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; - ctx_store.insert(query, "query", test::ddwaf_object_da::make_string("hello")); + ctx_store.insert_target(query, "query", test::ddwaf_object_da::make_string("hello")); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -333,10 +341,10 @@ TEST(TestObjectStore, InsertSingleTargetBatches) } { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; auto sctx_store = object_store::from_upstream_store(ctx_store); - sctx_store.insert(url, "url", test::ddwaf_object_da::make_string("hello")); + sctx_store.insert_target(url, "url", test::ddwaf_object_da::make_string("hello")); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); @@ -360,9 +368,10 @@ TEST(TestObjectStore, DuplicatePersistentTarget) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; - EXPECT_TRUE(store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE( + store.insert_target(query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -374,9 +383,9 @@ TEST(TestObjectStore, DuplicatePersistentTarget) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; - EXPECT_TRUE(store.insert(query, "query", test::ddwaf_object_da::make_string("bye"))); + EXPECT_TRUE(store.insert_target(query, "query", test::ddwaf_object_da::make_string("bye"))); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -401,9 +410,10 @@ TEST(TestObjectStore, DuplicateSubcontextTarget) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.flush_input_queue(); }}; { - EXPECT_TRUE(store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE( + store.insert_target(query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -415,7 +425,8 @@ TEST(TestObjectStore, DuplicateSubcontextTarget) } { - EXPECT_TRUE(store.insert(query, "query", test::ddwaf_object_da::make_string("bye"))); + EXPECT_TRUE( + store.insert_target(query, "query", test::ddwaf_object_da::make_string("bye"))); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -441,11 +452,11 @@ TEST(TestObjectStore, ReplaceSubcontextWithPersistent) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; { object_store sctx_store; - EXPECT_TRUE( - sctx_store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE(sctx_store.insert_target( + query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); @@ -458,7 +469,7 @@ TEST(TestObjectStore, ReplaceSubcontextWithPersistent) { EXPECT_TRUE( - ctx_store.insert(query, "query", test::ddwaf_object_da::make_string("bye"))); + ctx_store.insert_target(query, "query", test::ddwaf_object_da::make_string("bye"))); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -484,10 +495,10 @@ TEST(TestObjectStore, ReplacePersistentWithSubcontextSameBatch) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; { - EXPECT_TRUE( - ctx_store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE(ctx_store.insert_target( + query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -500,8 +511,8 @@ TEST(TestObjectStore, ReplacePersistentWithSubcontextSameBatch) { object_store sctx_store; - EXPECT_TRUE( - sctx_store.insert(query, "query", test::ddwaf_object_da::make_string("bye"))); + EXPECT_TRUE(sctx_store.insert_target( + query, "query", test::ddwaf_object_da::make_string("bye"))); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); @@ -527,9 +538,10 @@ TEST(TestObjectStore, ReplacePersistentWithSubcontextDifferentBatch) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.flush_input_queue(); }}; - EXPECT_TRUE(ctx_store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE( + ctx_store.insert_target(query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -542,7 +554,8 @@ TEST(TestObjectStore, ReplacePersistentWithSubcontextDifferentBatch) { auto sctx_store = object_store::from_upstream_store(ctx_store); - EXPECT_TRUE(sctx_store.insert(query, "query", test::ddwaf_object_da::make_string("bye"))); + EXPECT_TRUE( + sctx_store.insert_target(query, "query", test::ddwaf_object_da::make_string("bye"))); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); diff --git a/tests/unit/processor/processor_test.cpp b/tests/unit/processor/processor_test.cpp index 1ecd6bf39..7362974a7 100644 --- a/tests/unit/processor/processor_test.cpp +++ b/tests/unit/processor/processor_test.cpp @@ -50,7 +50,7 @@ TEST(TestProcessor, SingleMappingOutputNoEvalUnconditional) auto input_map = object_builder_da::map({{"input_address", "input_string"}}); object_store store; - store.insert(input_map); + store.insert_and_apply(input_map); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -91,7 +91,7 @@ TEST(TestProcessor, MultiMappingOutputNoEvalUnconditional) {{"input_address", "first_input_string"}, {"input_address.second", "second_input_string"}}); object_store store; - store.insert(input_map); + store.insert_and_apply(input_map); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -145,7 +145,7 @@ TEST(TestProcessor, SingleMappingOutputNoEvalConditionalTrue) object_builder_da::map({{"input_address", "input_string"}, {"enabled?", true}}); object_store store; - store.insert(input_map); + store.insert_and_apply(input_map); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -189,7 +189,7 @@ TEST(TestProcessor, SingleMappingOutputNoEvalConditionalCached) auto input_map = object_builder_da::map({{"enabled?", true}}); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -224,7 +224,7 @@ TEST(TestProcessor, SingleMappingOutputNoEvalConditionalCached) {"input_address", "input_string"}, }); - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); proc.eval(store, collector, cache, alloc, deadline); attributes = collector.get_available_attributes_and_reset(); @@ -245,7 +245,7 @@ TEST(TestProcessor, SingleMappingOutputNoEvalConditionalFalse) object_builder_da::map({{"input_address", "input_string"}, {"enabled?", false}}); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -285,7 +285,7 @@ TEST(TestProcessor, SingleMappingNoOutputEvalUnconditional) }); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -331,7 +331,7 @@ TEST(TestProcessor, SingleMappingNoOutputEvalConditionalTrue) object_builder_da::map({{"input_address", "input_string"}, {"enabled?", true}}); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -380,7 +380,7 @@ TEST(TestProcessor, SingleMappingNoOutputEvalConditionalFalse) object_builder_da::map({{"input_address", "input_string"}, {"enabled?", false}}); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -422,7 +422,7 @@ TEST(TestProcessor, MultiMappingNoOutputEvalUnconditional) {{"input_address", "first_input_string"}, {"input_address.second", "second_input_string"}}); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -479,7 +479,7 @@ TEST(TestProcessor, SingleMappingOutputEvalUnconditional) }); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -529,7 +529,7 @@ TEST(TestProcessor, OutputAlreadyAvailableInStore) {{"input_address", "input_string"}, {"output_address", owned_object::make_null()}}); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -562,7 +562,7 @@ TEST(TestProcessor, OutputAlreadyGenerated) }); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -595,7 +595,7 @@ TEST(TestProcessor, EvalAlreadyAvailableInStore) {{"input_address", "input_string"}, {"output_address", owned_object::make_null()}}); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), @@ -629,7 +629,7 @@ TEST(TestProcessor, OutputEvalWithoutattributesMap) }); object_store store; - store.insert(std::move(input_map)); + store.insert_and_apply(std::move(input_map)); std::vector mappings{ {.inputs = {{{{.index = get_target_index("input_address"), diff --git a/tests/unit/processor/structured_processor_test.cpp b/tests/unit/processor/structured_processor_test.cpp index c1408a326..1a081733d 100644 --- a/tests/unit/processor/structured_processor_test.cpp +++ b/tests/unit/processor/structured_processor_test.cpp @@ -53,7 +53,7 @@ TEST(TestStructuredProcessor, AllParametersAvailable) {{"unary_address", "unary_string"}, {"optional_address", "optional_string"}, {"variadic_address_1", 1U}, {"variadic_address_2", 1U}}); object_store store; - store.insert(input_map); + store.insert_and_apply(input_map); std::vector mappings{ {.inputs = {{{{.index = get_target_index("unary_address"), @@ -102,7 +102,7 @@ TEST(TestStructuredProcessor, OptionalParametersNotAvailable) {"variadic_address_1", 1U}, {"variadic_address_2", 1U}}); object_store store; - store.insert(input_map); + store.insert_and_apply(input_map); std::vector mappings{ {.inputs = {{{{.index = get_target_index("unary_address"), @@ -149,7 +149,7 @@ TEST(TestStructuredProcessor, RequiredParameterNotAvailable) {"variadic_address_1", 1U}, {"variadic_address_2", 1U}}); object_store store; - store.insert(input_map); + store.insert_and_apply(input_map); std::vector mappings{ {.inputs = {{{{.index = get_target_index("unary_address"), @@ -193,7 +193,7 @@ TEST(TestStructuredProcessor, NoVariadocParametersAvailable) }); object_store store; - store.insert(input_map); + store.insert_and_apply(input_map); std::vector mappings{ {.inputs = {{{{.index = get_target_index("unary_address"), diff --git a/tests/unit/rule_test.cpp b/tests/unit/rule_test.cpp index 3cffbf188..64670bce6 100644 --- a/tests/unit/rule_test.cpp +++ b/tests/unit/rule_test.cpp @@ -38,8 +38,8 @@ TEST(TestRule, Match) core_rule::cache_type cache; { - defer cleanup{[&]() { store.clear_last_batch(); }}; - store.insert(root.clone(memory::get_default_resource())); + defer cleanup{[&]() { store.flush_input_queue(); }}; + store.insert_and_apply(root.clone(memory::get_default_resource())); auto [verdict, result] = rule.match(store, cache, {}, {}, deadline); ASSERT_TRUE(result.has_value()); @@ -63,7 +63,7 @@ TEST(TestRule, Match) } { - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); auto [verdict, result] = rule.match(store, cache, {}, {}, deadline); EXPECT_FALSE(result.has_value()); @@ -86,7 +86,7 @@ TEST(TestRule, NoMatch) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; @@ -120,7 +120,7 @@ TEST(TestRule, ValidateCachedMatch) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; auto [verdict, result] = rule.match(store, cache, {}, {}, deadline); @@ -131,7 +131,7 @@ TEST(TestRule, ValidateCachedMatch) auto root = object_builder_da::map({{"usr.id", "admin"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; auto [verdict, result] = rule.match(store, cache, {}, {}, deadline); @@ -190,7 +190,7 @@ TEST(TestRule, MatchWithoutCache) object_store store; { auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; core_rule::cache_type cache; @@ -201,7 +201,7 @@ TEST(TestRule, MatchWithoutCache) { auto root = object_builder_da::map({{"usr.id", "admin"}}); - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; core_rule::cache_type cache; @@ -254,7 +254,7 @@ TEST(TestRule, NoMatchWithoutCache) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; core_rule::cache_type cache; @@ -266,7 +266,7 @@ TEST(TestRule, NoMatchWithoutCache) auto root = object_builder_da::map({{"usr.id", "admin"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; core_rule::cache_type cache; @@ -301,7 +301,7 @@ TEST(TestRule, FullCachedMatchSecondRun) object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; auto [verdict, result] = rule.match(store, cache, {}, {}, deadline); @@ -314,7 +314,7 @@ TEST(TestRule, FullCachedMatchSecondRun) object_builder_da::map({{"http.client_ip", "192.168.0.1"}, {"usr.id", "admin"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); ddwaf::timer deadline{2s}; auto [verdict, result] = rule.match(store, cache, {}, {}, deadline); @@ -336,7 +336,7 @@ TEST(TestRule, ExcludeObject) auto root = object_builder_da::map({{"http.client_ip", "192.168.0.1"}}); object_store store; - store.insert(std::move(root)); + store.insert_and_apply(std::move(root)); std::unordered_set excluded_set{store.get_target("http.client_ip")}; diff --git a/tests/unit/waf_test.cpp b/tests/unit/waf_test.cpp index 00420921a..f31417947 100644 --- a/tests/unit/waf_test.cpp +++ b/tests/unit/waf_test.cpp @@ -53,7 +53,7 @@ TEST(TestWaf, BasicContextRun) auto root = object_builder_da::map({{"value1", "rule1"}}); auto ctx = instance.create_context(memory::get_default_resource()); - EXPECT_TRUE(ctx.insert(std::move(root))); + EXPECT_TRUE(ctx.insert_batch(std::move(root))); ddwaf::timer deadline{2s}; auto [code, res] = ctx.eval(deadline); EXPECT_EQ(code, DDWAF_MATCH);