diff --git a/docs/upgrading/UPGRADING-v2.0.md b/docs/upgrading/UPGRADING-v2.0.md index ce1377bad..b013ffb4d 100644 --- a/docs/upgrading/UPGRADING-v2.0.md +++ b/docs/upgrading/UPGRADING-v2.0.md @@ -591,3 +591,57 @@ ddwaf_object_set_string( ddwaf_object_destroy(&root, alloc); ``` + +## 5. Batched Evaluation: `ddwaf_context_multieval` and `ddwaf_subcontext_multieval` + +v2.x introduces `ddwaf_context_multieval` and `ddwaf_subcontext_multieval`, which evaluate multiple input batches in sequence within a single call and return a single combined result. These functions have no v1.x equivalent. + +They behave like `ddwaf_context_eval` / `ddwaf_subcontext_eval`, except that `data` must be an *array of maps*: each element is treated as a separate input batch and evaluated in order. Addresses provided in earlier batches persist in the store and remain available when evaluating later batches, so a rule that requires several addresses can be satisfied by data spread across different batches within the same call. The result accumulates all events, actions and attributes generated across the whole sequence. + +The ownership and allocator semantics are identical to the single-batch functions: the data is stored by the context (or subcontext) and freed with the provided allocator on destruction, while the result must be destroyed with the output allocator given to `ddwaf_context_init`. Passing a non-array object returns `DDWAF_ERR_INVALID_OBJECT`. + +In addition to the usual keys, the result contains an `evaluated` field: an unsigned integer with the number of batches that were fully evaluated. In the normal case it equals the number of batches provided; it is lower when evaluation stops early, e.g. on a timeout or once a batch produces a blocking result. + +```c +ddwaf_allocator alloc = ddwaf_get_default_allocator(); +ddwaf_context context = ddwaf_context_init(handle, alloc); + +// An array of batches; each element is a map of addresses +ddwaf_object data; +ddwaf_object_set_array(&data, 2, alloc); + +// First batch +ddwaf_object *batch1 = ddwaf_object_insert(&data, alloc); +ddwaf_object_set_map(batch1, 1, alloc); +ddwaf_object_set_string( + ddwaf_object_insert_key(batch1, "value_a", 7, alloc), "hello", 5, alloc); + +// Second batch - value_a above is still available while this batch is evaluated, +// so a rule depending on both value_a and value_b can match here +ddwaf_object *batch2 = ddwaf_object_insert(&data, alloc); +ddwaf_object_set_map(batch2, 1, alloc); +ddwaf_object_set_string( + ddwaf_object_insert_key(batch2, "value_b", 7, alloc), "world", 5, alloc); + +ddwaf_object result; +DDWAF_RET_CODE code = ddwaf_context_multieval(context, &data, alloc, &result, timeout); +if (code == DDWAF_MATCH) { + // Handle matches accumulated across all batches... +} + +// Destroy result using the output allocator from ddwaf_context_init +ddwaf_object_destroy(&result, alloc); +``` + +The subcontext variant is equivalent and follows the same lifecycle as `ddwaf_subcontext_eval`, evaluating the batches against the data inherited from the parent context without persisting beyond the subcontext: + +```c +ddwaf_subcontext subctx = ddwaf_subcontext_init(context); + +ddwaf_object data = ...; // array of batch maps +ddwaf_object result; +ddwaf_subcontext_multieval(subctx, &data, alloc, &result, timeout); +ddwaf_object_destroy(&result, alloc); + +ddwaf_subcontext_destroy(subctx); +``` 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..937c1f024 100644 --- a/include/ddwaf.h +++ b/include/ddwaf.h @@ -317,6 +317,11 @@ 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 once the batch has been + * evaluated, or 0 if a timeout prevented that. 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 +354,89 @@ 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 input + * batches that were fully evaluated. In the normal + * case this equals the total number of batches + * provided; it is lower when evaluation stops early, + * e.g. on a timeout or once a batch produces a + * blocking result. Empty batches are evaluated like + * any other and 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 +483,11 @@ 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 once the batch has been + * evaluated, or 0 if a timeout prevented that. 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 +518,88 @@ 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 input + * batches that were fully evaluated. In the normal + * case this equals the total number of batches + * provided; it is lower when evaluation stops early, + * e.g. on a timeout or once a batch produces a + * blocking result. Empty batches are evaluated like + * any other and 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/schema/result.json b/schema/result.json index 52740d6fd..a6ce11eec 100644 --- a/schema/result.json +++ b/schema/result.json @@ -29,6 +29,11 @@ "type": "integer", "minimum": 0, "description": "The total runtime of the evaluation cycle in nanoseconds" + }, + "evaluated": { + "type": "integer", + "minimum": 0, + "description": "The number of input batches that were fully evaluated during the cycle; on timeout or error it is the index of the batch where the problem occurred" } }, "required": [ @@ -37,7 +42,8 @@ "actions", "attributes", "timeout", - "duration" + "duration", + "evaluated" ], "additionalProperties": false } 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/attribute_collector.cpp b/src/attribute_collector.cpp index b90aba8ad..48acdd5f7 100644 --- a/src/attribute_collector.cpp +++ b/src/attribute_collector.cpp @@ -35,6 +35,17 @@ bool attribute_collector::insert(std::string_view key, owned_object &&object) return true; } +bool attribute_collector::insert_or_assign(std::string_view key, owned_object &&object) +{ + if (pending_.contains(key)) { + // If the attribute was pending, it no longer is + pending_.erase(key); + } + + insert_helper(key, std::move(object)); + return true; +} + bool attribute_collector::collect(const object_store &store, target_index input_target, std::span> input_key_path, std::string_view attribute_key) @@ -88,14 +99,14 @@ attribute_collector::collection_state attribute_collector::collect_helper(const } if (resolved.is_scalar()) { - insert_helper(attribute_key, resolved.clone(attributes_.alloc())); + insert_helper(attribute_key, resolved.clone(alloc_)); return collection_state::success; } if (resolved.is_array() && !resolved.empty()) { auto candidate = resolved.at_value(0); if (candidate.is_scalar()) { - insert_helper(attribute_key, candidate.clone(attributes_.alloc())); + insert_helper(attribute_key, candidate.clone(alloc_)); return collection_state::success; } } @@ -104,9 +115,8 @@ attribute_collector::collection_state attribute_collector::collect_helper(const void attribute_collector::insert_helper(std::string_view key, owned_object &&object) { - attributes_.emplace(key, std::move(object)); + inserted_.insert_or_assign(key, std::move(object)); DDWAF_DEBUG("Collected attribute: {}", key); - inserted_.insert(key); } } // namespace ddwaf diff --git a/src/attribute_collector.hpp b/src/attribute_collector.hpp index 46b4dfb22..d5a4925f6 100644 --- a/src/attribute_collector.hpp +++ b/src/attribute_collector.hpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include "memory_resource.hpp" @@ -40,7 +39,7 @@ class attribute_collector { public: explicit attribute_collector( nonnull_ptr alloc = memory::get_default_resource()) - : attributes_(owned_object::make_map(alloc)) + : alloc_(alloc) {} attribute_collector(const attribute_collector &) = delete; attribute_collector &operator=(const attribute_collector &) = delete; @@ -80,11 +79,12 @@ class attribute_collector { (!std::is_integral_v>) && (!std::is_floating_point_v>) { - return insert(key, owned_object::make_string( - std::string_view(std::forward(value)), attributes_.alloc())); + return insert( + key, owned_object::make_string(std::string_view(std::forward(value)), alloc_)); } bool insert(std::string_view key, owned_object &&object); + bool insert_or_assign(std::string_view key, owned_object &&object); bool collect(const object_store &store, target_index input_target, std::span> input_key_path, @@ -97,12 +97,11 @@ class attribute_collector { // and the list of previously collected attributes. owned_object get_available_attributes_and_reset() { - auto output_object = std::move(attributes_); - // Reset attributes - attributes_ = owned_object::make_map(output_object.alloc()); + auto attributes = owned_object::make_map(inserted_.size(), alloc_); + for (auto &[key, value] : inserted_) { attributes.emplace(key, std::move(value)); } inserted_.clear(); - return output_object; + return attributes; } // Only used for testing @@ -110,7 +109,7 @@ class attribute_collector { static attribute_collector from_upstream_collector(const attribute_collector &upstream) { - attribute_collector collector{upstream.attributes_.alloc()}; + attribute_collector collector{upstream.alloc_}; collector.pending_ = upstream.pending_; return collector; } @@ -132,8 +131,8 @@ class attribute_collector { std::pair>>; std::unordered_map pending_; - std::unordered_set inserted_; - owned_object attributes_; + nonnull_ptr alloc_; + std::unordered_map inserted_; }; } // namespace ddwaf 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..928cc9b43 100644 --- a/src/evaluation_engine.cpp +++ b/src/evaluation_engine.cpp @@ -5,11 +5,15 @@ // Copyright 2025 Datadog, Inc. #include +#include #include #include +#include #include +#include #include +#include "attribute_collector.hpp" #include "clock.hpp" #include "evaluation_engine.hpp" #include "exception.hpp" @@ -42,63 +46,107 @@ void set_context_event_address(object_store &store) return; } - store.insert(event_addr_idx, event_addr, owned_object::make_boolean(true)); + store.insert_and_apply(event_addr_idx, event_addr, owned_object::make_boolean(true)); +} + +void collect_attributes(const object_store &store, const std::vector &results, + attribute_collector &collector) +{ + + // First collect any pending attributes from previous runs + collector.collect_pending(store); + + for (const auto &result : results) { + for (const auto &attr : result.attributes.get()) { + std::visit( + [&](const auto &value) { + using value_type = std::decay_t; + + if constexpr (std::is_same_v) { + collector.collect(store, value.index, value.key_path, attr.key); + } else { + collector.insert(attr.key, value); + } + }, + attr.value_or_target); + } + } } } // 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 &) {} - - 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_); + // 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]() { input_batches_.flush(store_); }); + + std::vector all_results; + std::size_t batches_evaluated = 0; + bool event_addr_added = false; + + // Each queued input batch is evaluated as if it were a separate eval call, + // draining the queue one batch at a time. + while (input_batches_.next_batch(store_)) { + std::vector results; + auto verdict = rule_verdict::none; + 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) { + verdict = eval_rules(policy, results, deadline); + if (!event_addr_added && !results.empty()) { + set_context_event_address(store_); + event_addr_added = true; + } } } + + eval_postprocessors(deadline); + ++batches_evaluated; + // NOLINTNEXTLINE(bugprone-empty-catch) + } catch (const ddwaf::timeout_exception &) {} + + collect_attributes(store_, results, collector_); + + if (!results.empty()) { + all_results.insert(all_results.end(), std::make_move_iterator(results.begin()), + std::make_move_iterator(results.end())); } - eval_postprocessors(deadline); - // NOLINTNEXTLINE(bugprone-empty-catch) - } catch (const ddwaf::timeout_exception &) {} + if (deadline.expired() || verdict == rule_verdict::block) { + break; + } + } + + 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 // generated during this call. // object::assign(result.attributes, collector_.collect_pending(store)); - serializer.serialize(store_, results, collector_, deadline, output); + serializer.serialize(all_results, collector_, deadline, output); return {!output.attributes.empty() || !output.actions.empty() || !output.events.empty(), std::move(result_object)}; } @@ -194,18 +242,20 @@ exclusion_policy &evaluation_engine::eval_filters(timer &deadline) return cache_.exclusions; } -void evaluation_engine::eval_rules( +rule_verdict evaluation_engine::eval_rules( const exclusion_policy &policy, std::vector &results, timer &deadline) { + auto verdict = rule_verdict::none; for (std::size_t i = 0; i < ruleset_->rule_modules.size(); ++i) { const auto &mod = ruleset_->rule_modules[i]; auto &cache = cache_.rule_modules[i]; - auto verdict = mod.eval(results, store_, cache, policy, *ruleset_->rule_matchers, deadline); + verdict = mod.eval(results, store_, cache, policy, *ruleset_->rule_matchers, deadline); if (verdict == rule_module::verdict_type::block) { break; } } + return verdict; } } // namespace ddwaf diff --git a/src/evaluation_engine.hpp b/src/evaluation_engine.hpp index 2a19efa1a..eb841b05a 100644 --- a/src/evaluation_engine.hpp +++ b/src/evaluation_engine.hpp @@ -18,6 +18,7 @@ #include "exclusion/common.hpp" #include "exclusion/input_filter.hpp" #include "exclusion/rule_filter.hpp" +#include "input_batch_queue.hpp" #include "log.hpp" #include "memory_resource.hpp" #include "module.hpp" @@ -48,23 +49,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 (!input_batches_.insert_batch(store_, 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 (!input_batches_.insert_batch(store_, data)) { DDWAF_WARN("Illegal WAF call: parameter structure invalid!"); return false; } return true; } + + bool insert_batches(owned_object data) noexcept + { + if (!input_batches_.insert_batches(store_, std::move(data))) { + DDWAF_WARN("Illegal WAF call: parameter structure invalid!"); + return false; + } + return true; + } + + bool insert_batches(array_view data) noexcept + { + if (!input_batches_.insert_batches(store_, data)) { + DDWAF_WARN("Illegal WAF call: parameter structure invalid!"); + return false; + } + return true; + } + + // Internals exposed for testing + bool next_batch() { return input_batches_.next_batch(store_); } + 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, @@ -86,7 +113,7 @@ class evaluation_engine { // This function below returns a reference to an internal object, // however using them this way helps with testing exclusion_policy &eval_filters(timer &deadline); - void eval_rules( + rule_verdict eval_rules( const exclusion_policy &policy, std::vector &results, timer &deadline); protected: @@ -135,6 +162,8 @@ class evaluation_engine { std::shared_ptr ruleset_; // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) object_store &store_; + // Queue of pending input batches fed into the store during evaluation + input_batch_queue input_batches_; attribute_collector collector_; // Caches diff --git a/src/input_batch_queue.hpp b/src/input_batch_queue.hpp new file mode 100644 index 000000000..8e9697a3e --- /dev/null +++ b/src/input_batch_queue.hpp @@ -0,0 +1,123 @@ +// 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 2025 Datadog, Inc. + +#pragma once + +#include "context_allocator.hpp" +#include "log.hpp" +#include "object.hpp" +#include "object_store.hpp" + +namespace ddwaf { + +// Manages the sequence of input batches fed into an object_store during an +// evaluation. Batches are enqueued (validated but not applied) and later +// consumed one at a time via next_batch(); each batch is evaluated as if it +// were a separate eval call. Any batch left unconsumed when evaluation finishes +// is flushed into the store as non-new data, mirroring the inputs being carried +// over to a subsequent eval call. +// +// The owning input objects are parked in the object_store rather than the queue +// so that views into them remain valid for the (potentially shared) lifetime of +// the store, while the queue itself only holds transient views. +class input_batch_queue { +public: + input_batch_queue() = default; + ~input_batch_queue() = default; + input_batch_queue(const input_batch_queue &) = delete; + input_batch_queue &operator=(const input_batch_queue &) = delete; + input_batch_queue(input_batch_queue &&) = default; + input_batch_queue &operator=(input_batch_queue &&) = default; + + // Enqueue a single batch of input addresses (a map). The owning object is + // parked in the store so that views into it remain valid. A batch with no + // addresses is accepted as a harmless no-op. Returns false if the object is + // not a map. + bool insert_batch(object_store &store, owned_object &&input) + { + const object_view view = store.insert(std::move(input)); + if (!view.is_map()) { + return false; + } + enqueue(view); + return true; + } + + bool insert_batch(object_store & /*store*/, map_view input) + { + enqueue(input); + return true; + } + + // 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; on failure the queue is left + // untouched. + bool insert_batches(object_store &store, owned_object &&input) + { + const object_view view = store.insert(std::move(input)); + if (!view.is_array()) { + return false; + } + return insert_batches(store, array_view{view}); + } + + bool insert_batches(object_store & /*store*/, array_view input) + { + // Validate every element before enqueueing anything so that a failure + // leaves the queue untouched. + for (auto element : input) { + if (!element.is_map()) { + return false; + } + } + + for (auto element : input) { enqueue(element); } + + return true; + } + + // Consume the next queued input batch, applying its addresses to the store + // and marking them as new. The previous batch's new-target set is reset + // first so each batch is evaluated as if it were a separate eval call. + // Returns false once the queue is drained. + bool next_batch(object_store &store) + { + store.clear_latest_batch(); + + if (queue_.empty()) { + return false; + } + + store.apply(queue_.front(), /*mark_new=*/true); + queue_.pop_front(); + return true; + } + + // Apply any remaining queued batches to the store *without* marking them as + // new (so they won't be evaluated) and reset the new-target set. Invoked + // once evaluation finishes, on any exit path including a timeout. + void flush(object_store &store) + { + if (!queue_.empty()) { + DDWAF_DEBUG("Flushing remaining queued objects"); + for (auto input : queue_) { store.apply(input, /*mark_new=*/false); } + queue_.clear(); + } + + store.clear_latest_batch(); + } + + [[nodiscard]] bool empty() const { return queue_.empty(); } + +private: + // Append a single input batch to the queue. + void enqueue(map_view input) { queue_.emplace_back(input); } + + memory::list queue_; +}; + +} // namespace ddwaf 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..8a4436530 100644 --- a/src/object_store.cpp +++ b/src/object_store.cpp @@ -5,7 +5,6 @@ // Copyright 2021 Datadog, Inc. #include #include -#include #include "log.hpp" #include "object.hpp" @@ -14,49 +13,27 @@ namespace ddwaf { -bool object_store::insert(owned_object &&input) +void object_store::apply(map_view batch, bool mark_new) { - const object_view view = input_objects_.emplace_back(std::move(input)); - if (!view.is_map()) { - return false; - } - - return insert(view); -} - -bool object_store::insert(map_view input) -{ - const auto size = input.size(); - if (size == 0) { - // Objects with no addresses are considered valid as they are harmless - return true; - } - + const auto size = batch.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); + auto [key_obj, value] = batch.at(i); if (key_obj.empty()) { continue; } auto key = key_obj.as(); - auto target = get_target_index(key); - insert_target_helper(target, key, value); + register_target(get_target_index(key), key, value, mark_new); } - - return true; } -bool object_store::insert(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) +void object_store::register_target( + target_index target, std::string_view key, object_view value, bool mark_new) { if (targets_.contains(target)) { DDWAF_DEBUG("Replacing target '{}' in object store", key); @@ -64,10 +41,10 @@ bool object_store::insert_target_helper(target_index target, std::string_view ke DDWAF_DEBUG("Inserting target '{}' into object store", key); } - targets_[target] = view; - latest_batch_.emplace(target); - - return true; + targets_[target] = value; + if (mark_new) { + latest_batch_.emplace(target); + } } } // namespace ddwaf diff --git a/src/object_store.hpp b/src/object_store.hpp index 15ea95235..379742a32 100644 --- a/src/object_store.hpp +++ b/src/object_store.hpp @@ -10,6 +10,7 @@ #include "object.hpp" #include "target_address.hpp" #include +#include namespace ddwaf { @@ -23,9 +24,51 @@ 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); + // Take ownership of an input object so that views into it remain valid for + // the lifetime of the store; returns a view over the stored object. This + // registers no targets. + object_view insert(owned_object &&input) + { + return input_objects_.emplace_back(std::move(input)); + } + + // Apply a batch of addresses (a map) to the store's targets. When mark_new + // is true the addresses are added to the new-target set and will be + // evaluated. The map must be backed by storage that outlives the store + // (e.g. an object owned via insert, or caller-owned input). + void apply(map_view batch, bool mark_new = true); + + // Own and apply a single batch as a self-contained unit: the previous + // new-target set is reset first, then the batch is applied as new. An empty + // batch only performs the reset. Returns false if the object is not a map. + bool insert_and_apply(owned_object &&input) + { + const object_view view = insert(std::move(input)); + if (!view.is_map()) { + return false; + } + return insert_and_apply(map_view{view}); + } + + bool insert_and_apply(map_view input) + { + clear_latest_batch(); + apply(input, /*mark_new=*/true); + return true; + } + + // Own and register a single derived target (e.g. produced by a processor), + // flagging it as new. Unlike insert_and_apply this does not reset the + // new-target set, since derived targets accumulate within the current batch. + bool insert_and_apply(target_index target, std::string_view key, owned_object &&input) + { + register_target(target, key, insert(std::move(input)), /*mark_new=*/true); + return true; + } + + // Reset the new-target set at a batch boundary so the next batch can + // identify newly-provided addresses; the targets themselves are retained. + void clear_latest_batch() { latest_batch_.clear(); } [[nodiscard]] object_view get_target(target_index target) const { @@ -49,7 +92,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 +105,12 @@ class object_store { } private: - bool insert_target_helper(target_index target, std::string_view key, object_view view); + // Register a single resolved target, optionally flagging it as new. Shared + // by apply and insert_and_apply. + void register_target( + target_index target, std::string_view key, object_view value, bool mark_new); memory::list input_objects_; - memory::unordered_set latest_batch_; memory::unordered_map targets_; }; diff --git a/src/processor/base.hpp b/src/processor/base.hpp index eda4e3c03..205fa9d4b 100644 --- a/src/processor/base.hpp +++ b/src/processor/base.hpp @@ -207,13 +207,15 @@ template class structured_processor : public base_processor { if (evaluate_) { // 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)); + collector.insert_or_assign(mapping.output.name, object.clone(alloc)); + store.insert_and_apply( + mapping.output.index, mapping.output.name, std::move(object)); } else { - collector.insert(mapping.output.name, std::move(object)); + collector.insert_or_assign(mapping.output.name, std::move(object)); } } else { - store.insert(mapping.output.index, mapping.output.name, std::move(object)); + store.insert_and_apply( + mapping.output.index, mapping.output.name, std::move(object)); } } } diff --git a/src/serializer.cpp b/src/serializer.cpp index bfd29ebdf..5e363e8a4 100644 --- a/src/serializer.cpp +++ b/src/serializer.cpp @@ -20,7 +20,6 @@ #include "memory_resource.hpp" #include "obfuscator.hpp" #include "object.hpp" -#include "object_store.hpp" #include "pointer.hpp" #include "rule.hpp" #include "serializer.hpp" @@ -306,31 +305,10 @@ void serialize_actions(const action_tracker &actions, borrowed_object action_map } } -void collect_attributes(const object_store &store, const std::vector &attributes, - attribute_collector &collector) -{ - for (const auto &attr : attributes) { - if (std::holds_alternative(attr.value_or_target)) { - auto input = std::get(attr.value_or_target); - collector.collect(store, input.index, input.key_path, attr.key); - } else if (std::holds_alternative(attr.value_or_target)) { - collector.insert(attr.key, std::get(attr.value_or_target)); - } else if (std::holds_alternative(attr.value_or_target)) { - collector.insert(attr.key, std::get(attr.value_or_target)); - } else if (std::holds_alternative(attr.value_or_target)) { - collector.insert(attr.key, std::get(attr.value_or_target)); - } else if (std::holds_alternative(attr.value_or_target)) { - collector.insert(attr.key, std::get(attr.value_or_target)); - } else if (std::holds_alternative(attr.value_or_target)) { - collector.insert(attr.key, std::get(attr.value_or_target)); - } - } -} - } // namespace -void result_serializer::serialize(const object_store &store, std::vector &results, - attribute_collector &collector, const timer &deadline, result_components output) +void result_serializer::serialize(std::vector &results, attribute_collector &collector, + const timer &deadline, result_components output) { action_tracker actions{.blocking_action = {}, .stack_id = {}, @@ -338,9 +316,6 @@ void result_serializer::serialize(const object_store &store, std::vector 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 +355,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..8b6fa0e94 100644 --- a/src/serializer.hpp +++ b/src/serializer.hpp @@ -12,7 +12,6 @@ #include "memory_resource.hpp" #include "obfuscator.hpp" #include "object.hpp" -#include "object_store.hpp" #include "pointer.hpp" #include "rule.hpp" #include @@ -28,6 +27,7 @@ struct result_components { borrowed_object timeout; borrowed_object attributes; borrowed_object keep; + borrowed_object evaluated; }; // NOLINTEND(cppcoreguidelines-avoid-const-or-ref-data-members) @@ -38,8 +38,8 @@ class result_serializer { : obfuscator_(obfuscator), actions_(actions), alloc_(alloc) {} - void serialize(const object_store &store, std::vector &results, - attribute_collector &collector, const timer &deadline, result_components output); + void serialize(std::vector &results, attribute_collector &collector, + const timer &deadline, result_components output); std::pair initialise_result_object(); 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..bdbe90248 --- /dev/null +++ b/tests/integration/interface/context/multieval/test.cpp @@ -0,0 +1,1031 @@ +// 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_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_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/integration/processors/extract_schema/test.cpp b/tests/integration/processors/extract_schema/test.cpp index d8e5ee627..c3bad3648 100644 --- a/tests/integration/processors/extract_schema/test.cpp +++ b/tests/integration/processors/extract_schema/test.cpp @@ -1037,9 +1037,10 @@ TEST(TestExtractSchemaIntegration, ProcessorSubcontextExpression) const auto *attributes = ddwaf_object_find(&out, STRL("attributes")); EXPECT_EQ(ddwaf_object_get_size(attributes), 2); - auto schema = test::object_to_json(*attributes); - EXPECT_STR( - schema, R"({"server.request.body.schema":[8],"server.request.query.schema":[8]})"); + // The attribute collector orders entries via an unordered_map, so the + // serialised key order is unspecified; compare structurally. + EXPECT_JSON( + *attributes, R"({"server.request.body.schema":[8],"server.request.query.schema":[8]})"); ddwaf_object_destroy(&out, alloc); ddwaf_subcontext_destroy(subctx); 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..f4b9896b6 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_and_apply(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_and_apply(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_and_apply(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_and_apply(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..ba6d79195 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.clear_latest_batch(); }}; - 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.clear_latest_batch(); }}; 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.clear_latest_batch(); }}; - 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.clear_latest_batch(); }}; 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.clear_latest_batch(); }}; + 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.clear_latest_batch(); }}; + 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.clear_latest_batch(); }}; + 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.clear_latest_batch(); }}; + 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..b92378a03 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.clear_latest_batch(); }}; - 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..532f29c52 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.clear_latest_batch(); }}; 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.clear_latest_batch(); }}; 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.clear_latest_batch(); }}; 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.clear_latest_batch(); }}; 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.clear_latest_batch(); }}; 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.clear_latest_batch(); }}; 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.clear_latest_batch(); }}; 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.clear_latest_batch(); }}; 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.clear_latest_batch(); }}; 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.clear_latest_batch(); }}; 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/input_batch_queue_test.cpp b/tests/unit/input_batch_queue_test.cpp new file mode 100644 index 000000000..f25b38c01 --- /dev/null +++ b/tests/unit/input_batch_queue_test.cpp @@ -0,0 +1,142 @@ +// 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 2025 Datadog, Inc. + +#include "input_batch_queue.hpp" +#include "object_store.hpp" + +#include "common/ddwaf_object_da.hpp" +#include "common/gtest_utils.hpp" + +using namespace ddwaf; +using namespace ddwaf::test; + +namespace { + +TEST(TestInputBatchQueue, EnqueueAndApplySingleBatch) +{ + auto query = get_target_index("query"); + + object_store store; + input_batch_queue queue; + + EXPECT_TRUE(queue.insert_batch(store, object_builder_da::map({{"query", "hello"}}))); + EXPECT_FALSE(queue.empty()); + + // Not applied until consumed + EXPECT_FALSE(store.is_new_target(query)); + EXPECT_FALSE(store.get_target(query).has_value()); + + EXPECT_TRUE(queue.next_batch(store)); + EXPECT_TRUE(queue.empty()); + EXPECT_TRUE(store.is_new_target(query)); + EXPECT_TRUE(store.get_target(query).has_value()); + + // Draining an empty queue resets the new-target set and returns false + EXPECT_FALSE(queue.next_batch(store)); + EXPECT_FALSE(store.has_new_targets()); + EXPECT_TRUE(store.get_target(query).has_value()); +} + +TEST(TestInputBatchQueue, InsertNonMapBatchReturnsFalse) +{ + object_store store; + input_batch_queue queue; + + EXPECT_FALSE(queue.insert_batch(store, test::ddwaf_object_da::make_string("hello"))); + EXPECT_FALSE(queue.insert_batch(store, owned_object{})); + EXPECT_TRUE(queue.empty()); +} + +TEST(TestInputBatchQueue, EmptyBatchIsNotSkipped) +{ + object_store store; + input_batch_queue queue; + + // A map with no addresses is valid but enqueues nothing + EXPECT_TRUE(queue.insert_batch(store, object_builder_da::map({}))); + EXPECT_FALSE(queue.empty()); +} + +TEST(TestInputBatchQueue, EnqueueAndApplyMultipleBatches) +{ + auto query = get_target_index("query"); + auto url = get_target_index("url"); + + object_store store; + input_batch_queue queue; + + EXPECT_TRUE(queue.insert_batches( + store, object_builder_da::array({object_builder_da::map({{"query", "hello"}}), + object_builder_da::map({{"url", "bye"}})}))); + + // First batch: query is new + EXPECT_TRUE(queue.next_batch(store)); + EXPECT_TRUE(store.is_new_target(query)); + EXPECT_FALSE(store.is_new_target(url)); + + // Second batch: only url is new, the previous new-target set is reset + EXPECT_TRUE(queue.next_batch(store)); + EXPECT_FALSE(store.is_new_target(query)); + EXPECT_TRUE(store.is_new_target(url)); + + // Both targets remain available in the store + EXPECT_TRUE(store.get_target(query).has_value()); + EXPECT_TRUE(store.get_target(url).has_value()); + + EXPECT_FALSE(queue.next_batch(store)); + EXPECT_TRUE(queue.empty()); +} + +TEST(TestInputBatchQueue, InsertBatchesNonArrayReturnsFalse) +{ + object_store store; + input_batch_queue queue; + + EXPECT_FALSE(queue.insert_batches(store, object_builder_da::map({{"query", "hello"}}))); + EXPECT_TRUE(queue.empty()); +} + +TEST(TestInputBatchQueue, InsertBatchesWithNonMapElementLeavesQueueUntouched) +{ + object_store store; + input_batch_queue queue; + + // The middle element is not a map: the whole call fails and nothing is queued + EXPECT_FALSE(queue.insert_batches( + store, object_builder_da::array({object_builder_da::map({{"query", "hello"}}), + test::ddwaf_object_da::make_string("not a map"), + object_builder_da::map({{"url", "bye"}})}))); + EXPECT_TRUE(queue.empty()); +} + +TEST(TestInputBatchQueue, FlushAppliesRemainingBatchesAsNonNew) +{ + auto query = get_target_index("query"); + auto url = get_target_index("url"); + + object_store store; + input_batch_queue queue; + + EXPECT_TRUE(queue.insert_batch(store, object_builder_da::map({{"query", "hello"}}))); + EXPECT_TRUE(queue.insert_batch(store, object_builder_da::map({{"url", "bye"}}))); + + // Consume only the first batch + EXPECT_TRUE(queue.next_batch(store)); + EXPECT_TRUE(store.is_new_target(query)); + + // Flushing applies the remaining batch as non-new and resets the new set + queue.flush(store); + EXPECT_TRUE(queue.empty()); + EXPECT_FALSE(store.has_new_targets()); + EXPECT_FALSE(store.is_new_target(query)); + EXPECT_FALSE(store.is_new_target(url)); + + // The remaining batch's data is still available in the store + EXPECT_TRUE(store.get_target(query).has_value()); + EXPECT_TRUE(store.get_target(url).has_value()); +} + +} // namespace 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..6c686376d 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 returns false and + // the store is left untouched. + store.insert_and_apply(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 returns false and the store is + // left untouched. + store.insert_and_apply(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.clear_latest_batch(); }}; 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,28 +121,30 @@ 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()); - EXPECT_TRUE(sctx_store.is_new_target(query)); + EXPECT_FALSE(sctx_store.is_new_target(query)); EXPECT_TRUE(sctx_store.is_new_target(url)); EXPECT_TRUE(sctx_store.get_target(query).has_value()); EXPECT_TRUE(sctx_store.get_target(url).has_value()); } { - ctx_store.insert(owned_object{}); + // An empty map resets the new-target set (clearing query from the first + // insert above) and is otherwise a no-op. + ctx_store.insert_and_apply(object_builder_da::map({})); EXPECT_FALSE(ctx_store.empty()); - EXPECT_TRUE(ctx_store.has_new_targets()); - EXPECT_TRUE(ctx_store.is_new_target(query)); + EXPECT_FALSE(ctx_store.has_new_targets()); + EXPECT_FALSE(ctx_store.is_new_target(query)); EXPECT_FALSE(ctx_store.is_new_target(url)); EXPECT_TRUE(ctx_store.get_target(query).has_value()); EXPECT_FALSE(ctx_store.get_target(url).has_value()); } - ctx_store.clear_last_batch(); + ctx_store.clear_latest_batch(); 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.clear_latest_batch(); }}; 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.clear_latest_batch(); }}; 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.clear_latest_batch(); }}; + // An empty owned_object is not a map, so insert is a no-op; + // latest_batch_ was already cleared by the previous clear_latest_batch(). owned_object third = owned_object{}; - store.insert(std::move(third)); + store.insert_and_apply(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.clear_latest_batch(); }}; 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.clear_latest_batch(); }}; // 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.clear_latest_batch(); }}; // 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_and_apply(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_and_apply(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.clear_latest_batch(); 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.clear_latest_batch(); }}; - ctx_store.insert(query, "query", test::ddwaf_object_da::make_string("hello")); + ctx_store.insert_and_apply(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.clear_latest_batch(); }}; 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_and_apply(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.clear_latest_batch(); }}; - EXPECT_TRUE(store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE( + store.insert_and_apply(query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -374,9 +383,10 @@ TEST(TestObjectStore, DuplicatePersistentTarget) } { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.clear_latest_batch(); }}; - EXPECT_TRUE(store.insert(query, "query", test::ddwaf_object_da::make_string("bye"))); + EXPECT_TRUE( + store.insert_and_apply(query, "query", test::ddwaf_object_da::make_string("bye"))); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -401,9 +411,10 @@ TEST(TestObjectStore, DuplicateSubcontextTarget) object_store store; { - defer cleanup{[&]() { store.clear_last_batch(); }}; + defer cleanup{[&]() { store.clear_latest_batch(); }}; { - EXPECT_TRUE(store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE(store.insert_and_apply( + query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -415,7 +426,8 @@ TEST(TestObjectStore, DuplicateSubcontextTarget) } { - EXPECT_TRUE(store.insert(query, "query", test::ddwaf_object_da::make_string("bye"))); + EXPECT_TRUE( + store.insert_and_apply(query, "query", test::ddwaf_object_da::make_string("bye"))); EXPECT_FALSE(store.empty()); EXPECT_TRUE(store.has_new_targets()); @@ -441,11 +453,11 @@ TEST(TestObjectStore, ReplaceSubcontextWithPersistent) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.clear_latest_batch(); }}; { object_store sctx_store; - EXPECT_TRUE( - sctx_store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE(sctx_store.insert_and_apply( + query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); @@ -457,8 +469,8 @@ TEST(TestObjectStore, ReplaceSubcontextWithPersistent) } { - EXPECT_TRUE( - ctx_store.insert(query, "query", test::ddwaf_object_da::make_string("bye"))); + EXPECT_TRUE(ctx_store.insert_and_apply( + query, "query", test::ddwaf_object_da::make_string("bye"))); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -484,10 +496,10 @@ TEST(TestObjectStore, ReplacePersistentWithSubcontextSameBatch) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.clear_latest_batch(); }}; { - EXPECT_TRUE( - ctx_store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE(ctx_store.insert_and_apply( + query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -500,8 +512,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_and_apply( + query, "query", test::ddwaf_object_da::make_string("bye"))); EXPECT_FALSE(sctx_store.empty()); EXPECT_TRUE(sctx_store.has_new_targets()); @@ -527,9 +539,10 @@ TEST(TestObjectStore, ReplacePersistentWithSubcontextDifferentBatch) object_store ctx_store; { - defer cleanup{[&]() { ctx_store.clear_last_batch(); }}; + defer cleanup{[&]() { ctx_store.clear_latest_batch(); }}; - EXPECT_TRUE(ctx_store.insert(query, "query", test::ddwaf_object_da::make_string("hello"))); + EXPECT_TRUE(ctx_store.insert_and_apply( + query, "query", test::ddwaf_object_da::make_string("hello"))); EXPECT_FALSE(ctx_store.empty()); EXPECT_TRUE(ctx_store.has_new_targets()); @@ -542,7 +555,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_and_apply(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..76357b6cd 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"), @@ -123,14 +123,12 @@ TEST(TestProcessor, MultiMappingOutputNoEvalUnconditional) auto attributes = collector.get_available_attributes_and_reset(); EXPECT_EQ(attributes.size(), 2); { - const auto [obtained_key, obtained_value] = object_view{attributes}.at(0); - EXPECT_STRV(obtained_key.as(), "output_address"); + auto obtained_value = object_view{attributes}.find("output_address"); EXPECT_STRV(obtained_value.as(), "first_output_string"); } { - const auto [obtained_key, obtained_value] = object_view{attributes}.at(1); - EXPECT_STRV(obtained_key.as(), "output_address.second"); + auto obtained_value = object_view{attributes}.find("output_address.second"); EXPECT_STRV(obtained_value.as(), "second_output_string"); } } @@ -145,7 +143,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 +187,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 +222,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 +243,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 +283,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 +329,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 +378,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 +420,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 +477,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 +527,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 +560,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 +593,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 +627,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..d25a62829 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.clear_latest_batch(); }}; + 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/serializer_test.cpp b/tests/unit/serializer_test.cpp index cd8b85b7f..a5ade9690 100644 --- a/tests/unit/serializer_test.cpp +++ b/tests/unit/serializer_test.cpp @@ -28,7 +28,7 @@ TEST(TestEventSerializer, SerializeNothing) ddwaf::timer deadline{2s}; auto [result_object, output] = serializer.initialise_result_object(); - serializer.serialize(store, results, collector, deadline, output); + serializer.serialize(results, collector, deadline, output); EXPECT_EVENTS(result_object, ); // This means no results EXPECT_ACTIONS(result_object, {}); @@ -60,7 +60,7 @@ TEST(TestEventSerializer, SerializeEmptyEvent) }; std::vector results{result}; - serializer.serialize(store, results, collector, deadline, output); + serializer.serialize(results, collector, deadline, output); EXPECT_EVENTS(result_object, {}); EXPECT_ACTIONS(result_object, {}); @@ -102,7 +102,7 @@ TEST(TestEventSerializer, SerializeSingleEventSingleMatch) ddwaf::timer deadline{2s}; auto [result_object, output] = serializer.initialise_result_object(); - serializer.serialize(store, results, collector, deadline, output); + serializer.serialize(results, collector, deadline, output); EXPECT_EVENTS(result_object, {.id = "xasd1022", .name = "random rule", .block_id = "*", @@ -179,7 +179,7 @@ TEST(TestEventSerializer, SerializeSingleEventMultipleMatches) ddwaf::timer deadline{2s}; auto [result_object, output] = serializer.initialise_result_object(); - serializer.serialize(store, results, collector, deadline, output); + serializer.serialize(results, collector, deadline, output); EXPECT_EVENTS(result_object, {.id = "xasd1022", .name = "random rule", @@ -311,7 +311,7 @@ TEST(TestEventSerializer, SerializeMultipleEvents) ddwaf::timer deadline{2s}; auto [result_object, output] = serializer.initialise_result_object(); - serializer.serialize(store, results, collector, deadline, output); + serializer.serialize(results, collector, deadline, output); EXPECT_EVENTS(result_object, {.id = "xasd1022", .name = "random rule", @@ -396,7 +396,7 @@ TEST(TestEventSerializer, SerializeEventNoActions) ddwaf::timer deadline{2s}; auto [result_object, output] = serializer.initialise_result_object(); - serializer.serialize(store, results, collector, deadline, output); + serializer.serialize(results, collector, deadline, output); EXPECT_EVENTS(result_object, {.id = "xasd1022", .name = "random rule", .tags = {{"type", "test"}, {"category", "none"}}, @@ -452,7 +452,7 @@ TEST(TestEventSerializer, SerializeAllTags) ddwaf::timer deadline{2s}; auto [result_object, output] = serializer.initialise_result_object(); - serializer.serialize(store, results, collector, deadline, output); + serializer.serialize(results, collector, deadline, output); EXPECT_EVENTS( result_object, {.id = "xasd1022", .name = "random rule", @@ -509,7 +509,7 @@ TEST(TestEventSerializer, NoMonitorActions) ddwaf::timer deadline{2s}; auto [result_object, output] = serializer.initialise_result_object(); - serializer.serialize(store, results, collector, deadline, output); + serializer.serialize(results, collector, deadline, output); EXPECT_EVENTS( result_object, {.id = "xasd1022", .name = "random rule", @@ -567,7 +567,7 @@ TEST(TestEventSerializer, UndefinedActions) ddwaf::timer deadline{2s}; auto [result_object, output] = serializer.initialise_result_object(); - serializer.serialize(store, results, collector, deadline, output); + serializer.serialize(results, collector, deadline, output); EXPECT_EVENTS( result_object, {.id = "xasd1022", .name = "random rule", @@ -625,7 +625,7 @@ TEST(TestEventSerializer, StackTraceAction) ddwaf::timer deadline{2s}; auto [result_object, output] = serializer.initialise_result_object(); - serializer.serialize(store, results, collector, deadline, output); + serializer.serialize(results, collector, deadline, output); EXPECT_EVENTS( result_object, {.id = "xasd1022", .name = "random rule", @@ -701,7 +701,7 @@ TEST(TestEventSerializer, SerializeMultiArgMatchWithKeyPaths) ddwaf::timer deadline{2s}; auto [result_object, output] = serializer.initialise_result_object(); - serializer.serialize(store, results, collector, deadline, output); + serializer.serialize(results, collector, deadline, output); EXPECT_EVENTS(result_object, {.id = "rule-multi", 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); diff --git a/tools/waf_runner.cpp b/tools/waf_runner.cpp index bd2e6ec81..0839f0849 100644 --- a/tools/waf_runner.cpp +++ b/tools/waf_runner.cpp @@ -111,7 +111,7 @@ int main(int argc, char *argv[]) auto data = input.as(); ddwaf_object ret; - ddwaf_context_eval(context, &data, alloc, &ret, std::numeric_limits::max()); + ddwaf_context_multieval(context, &data, alloc, &ret, std::numeric_limits::max()); YAML::Emitter out(std::cout); out.SetIndent(2);