]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3463] Added parsing to BindingVariable
authorThomas Markwalder <tmark@isc.org>
Mon, 27 Jan 2025 14:34:31 +0000 (09:34 -0500)
committerThomas Markwalder <tmark@isc.org>
Tue, 18 Feb 2025 18:54:19 +0000 (18:54 +0000)
/src/hooks/dhcp/lease_cmds/binding_variables.*
    BindingVariable::parse() - added simple parsing
    function

/src/hooks/dhcp/lease_cmds/tests/binding_variables_unittest.cc
    TEST(BindingVariableTest, validParsing)
    TEST(BindingVariableTest, invalidParsing) - new tests

src/hooks/dhcp/lease_cmds/binding_variables.cc
src/hooks/dhcp/lease_cmds/binding_variables.h
src/hooks/dhcp/lease_cmds/tests/binding_variables_unittest.cc

index e594724ae951847311750a5caf38e1744424e46b..e3a39ee95adc6ece2202f36ea7bd8aab65054c58 100644 (file)
@@ -32,12 +32,12 @@ BindingVariable::BindingVariable(const std::string& name,
     /// @todo If we add socpes we may wish to allow higher order
     /// scopes to override lower scopes with empty expressions.
     if (expression_str_.empty()) {
-        isc_throw(BadValue, "BindingVariable - '" << name_ 
+        isc_throw(BadValue, "BindingVariable - '" << name_
                   << "' expression_str cannot be empty");
     }
 
     if (family_ != AF_INET && family_ != AF_INET6) {
-        isc_throw(BadValue, "BindingVariable - '" << name_ 
+        isc_throw(BadValue, "BindingVariable - '" << name_
                   << "', invalid family: " << family_);
     }
 
@@ -51,6 +51,42 @@ BindingVariable::BindingVariable(const std::string& name,
     }
 }
 
+const data::SimpleKeywords
+BindingVariable::CONFIG_KEYWORDS =
+{
+    {"name",        Element::string},
+    {"expression",  Element::string},
+    {"source",      Element::string}
+};
+
+BindingVariablePtr
+BindingVariable::parse(data::ConstElementPtr config, uint16_t family) {
+    // Note checkKeywords() will throw DhcpConfigError if there is a problem.
+    SimpleParser::checkKeywords(CONFIG_KEYWORDS, config);
+
+    // Parse members.
+    std::string name = SimpleParser::getString(config, "name");;
+    std::string expression = SimpleParser::getString(config, "expression");;
+    std::string source_str = SimpleParser::getString(config, "source");;
+
+    try {
+        Source source;
+        if (source_str == "query") {
+            source = QUERY;
+        } else if (source_str == "response") {
+            source = RESPONSE;
+        } else {
+            isc_throw(BadValue, "invalid source '" << source_str
+                      << "', must be either 'query' or 'response'");
+        }
+
+        // Attempt to create the variable.
+        return (BindingVariablePtr(new BindingVariable(name, expression, source, family)));
+    } catch (const std::exception& ex) {
+        isc_throw(DhcpConfigError, "invalid config: " << ex.what());
+    }
+}
+
 std::string
 BindingVariable::evaluate(PktPtr packet) const {
     try {
@@ -58,19 +94,19 @@ BindingVariable::evaluate(PktPtr packet) const {
     } catch (const std::exception& ex) {
         isc_throw(BadValue, "BindingVariable - " << name_ << ", error evaluating expression: ["
                   << expression_str_ << "] : " << ex.what());
-    } 
+    }
 }
 
 ElementPtr
 BindingVariable::toElement() const {
     ElementPtr map = Element::createMap();
     map->set("name", Element::create(name_));
-    map->set("expression_str", Element::create(expression_str_));
+    map->set("expression", Element::create(expression_str_));
     map->set("source", Element::create((source_ == QUERY ? "query" : "response")));
     return (map);
 }
 
-BindingVariableCache::BindingVariableCache() 
+BindingVariableCache::BindingVariableCache()
     : variables_(), mutex_(new std::mutex) {
 }
 
@@ -81,7 +117,7 @@ BindingVariableCache::cacheVariable(BindingVariablePtr variable) {
     return(retpair.second);
 }
 
-void 
+void
 BindingVariableCache::clear() {
     util::MultiThreadingLock lock(*mutex_);
     // Discard contents.
@@ -148,6 +184,6 @@ BindingVariableCache::getBySource(const BindingVariable::Source& source) {
 
     return (var_list);
 }
-    
+
 } // end of namespace lease_cmds
 } // end of namespace isc
index 12925c75bdf9d3e6821002fc94b1fe3b5d155f0b..d1c9055c3b0951af82fb0e33cfc0e9d062db88a6 100644 (file)
@@ -10,6 +10,7 @@
 #include <cc/base_stamped_element.h>
 #include <cc/cfg_to_element.h>
 #include <cc/data.h>
+#include <cc/simple_parser.h>
 #include <eval/evaluate.h>
 #include <eval/token.h>
 #include <dhcp/pkt.h>
 namespace isc {
 namespace lease_cmds {
 
+
+// Forward declaration for pointer.
+class BindingVariable;
+
+/// @brief Defines a shared pointer to a BindingVariable.
+typedef boost::shared_ptr<BindingVariable> BindingVariablePtr;
+
 /// @brief Embodies a named expression, whose output when
 /// evaluated can be stored in a lease's user-context.
 class BindingVariable : public isc::data::CfgToElement {
@@ -35,6 +43,9 @@ public:
         RESPONSE
     };
 
+    /// @brief List of valid configurable parameters for a BindingVariable.
+    static const data::SimpleKeywords CONFIG_KEYWORDS;
+
     /// @brief Constructor
     ///
     /// @param name name of the variable, must be unique. Used
@@ -57,6 +68,14 @@ public:
     /// @brief Destructor
     virtual ~BindingVariable() = default;
 
+    /// @brief Parses configuration elements into a BindingVarable instance.
+    ///
+    /// @param config Map Element containing parameters for a single binding variable.
+    /// @param family Protocol family of the variable, either AF_INET or AF_INET6.
+    /// @return Pointer to the newly created BindingVariable instacne.
+    /// @throw DhcpConfigError if configuration parameters are invalid.
+    static BindingVariablePtr parse(data::ConstElementPtr config, uint16_t family);
+
     /// @brief Evaluate the variable against the given packet.
     ///
     /// @param packet Pointer to the target packet.
@@ -126,9 +145,6 @@ private:
     dhcp::ExpressionPtr expression_;
 };
 
-/// @brief Defines a shared pointer to a BindingVariable.
-typedef boost::shared_ptr<BindingVariable> BindingVariablePtr;
-
 /// @brief Defines a list of BindingVariablePtr instances.
 typedef std::list<BindingVariablePtr> BindingVariableList;
 
index 479a8d4727439a8c570a532bcbbd6b975e11a734..697c4bb0e5f181c2a69f7123002abd0951e20af3 100644 (file)
@@ -18,6 +18,7 @@
 
 using namespace std;
 using namespace isc;
+using namespace isc::dhcp;
 using namespace isc::data;
 using namespace isc::test;
 
@@ -25,6 +26,11 @@ using namespace isc::lease_cmds;
 
 namespace {
 
+#define SCOPED_LINE(line) \
+    std::stringstream ss; \
+    ss << "Scenario at line: " << line; \
+    SCOPED_TRACE(ss.str());
+
 /// @brief Test BindingVariable valid construction scenarios.
 TEST(BindingVariableTest, validConstructor) {
     BindingVariablePtr bv;
@@ -56,6 +62,7 @@ TEST(BindingVariableTest, validConstructor) {
     };
 
     for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
         ASSERT_NO_THROW_LOG(bv.reset(new BindingVariable(scenario.name_,
                                                          scenario.expression_str_,
                                                          scenario.source_,
@@ -115,6 +122,7 @@ TEST(BindingVariableTest, invalidConstructor) {
     };
 
     for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
         ASSERT_THROW_MSG(bv.reset(new BindingVariable(scenario.name_,
                                                       scenario.expression_str_,
                                                       BindingVariable::QUERY,
@@ -137,11 +145,112 @@ TEST(BindingVariableTest, toElement) {
     ASSERT_NO_THROW_LOG(elem = bv->toElement());
     std::stringstream ss;
     elem->toJSON(ss);
-    std::string expected_json = "{ \"expression_str\": \"pkt4.mac\","
+    std::string expected_json = "{ \"expression\": \"pkt4.mac\","
                                 " \"name\": \"myvar\", \"source\": \"query\" }";
     EXPECT_EQ(ss.str(), expected_json);
 }
 
+/// @brief Checks BindingVariable::parse with valid scenarios.
+TEST(BindingVariableTest, validParsing) {
+    struct Scenario {
+        uint32_t line_;
+        std::string config_;
+        uint32_t family_;
+        std::string expected_json_;
+    };
+
+    std::list<Scenario> scenarios = {
+        {
+           __LINE__,
+           R"({ "name": "one", "expression" : "pkt4.mac", "source": "query"})",
+            AF_INET,
+           "{ \"expression\": \"pkt4.mac\", \"name\": \"one\", \"source\": \"query\" }"
+        },
+        {
+           __LINE__,
+           R"({ "name": "two", "expression" : "pkt4.mac", "source": "response"})",
+            AF_INET,
+           "{ \"expression\": \"pkt4.mac\", \"name\": \"two\", \"source\": \"response\" }"
+        },
+        {
+           __LINE__,
+           R"({ "name": "three", "expression" : "pkt6.transid", "source": "query"})",
+            AF_INET6,
+           "{ \"expression\": \"pkt6.transid\", \"name\": \"three\", \"source\": \"query\" }"
+        },
+        {
+           __LINE__,
+           R"({ "name": "four", "expression" : "pkt6.transid", "source": "response"})",
+            AF_INET6,
+           "{ \"expression\": \"pkt6.transid\", \"name\": \"four\", \"source\": \"response\" }"
+        }
+    };
+
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+        BindingVariablePtr var;
+        ElementPtr config;
+        ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+        ASSERT_NO_THROW_LOG(var = BindingVariable::parse(config, scenario.family_));
+        ASSERT_TRUE(var);
+        std::stringstream js;
+        var->toElement()->toJSON(js);
+        EXPECT_EQ(js.str(), scenario.expected_json_);
+    }
+}
+
+/// @brief Checks BindingVariable::parse with invalid scenarios.
+TEST(BindingVariableTest, invalidParsing) {
+    struct Scenario {
+        uint32_t line_;
+        std::string config_;
+        uint32_t family_;
+        std::string expected_error_;
+    };
+
+    std::list<Scenario> scenarios = {
+        {
+            __LINE__,
+            R"({ "name": "", "expression" : "pkt4.mac", "source": "query"})",
+            AF_INET,
+            "invalid config: BindingVariable - name cannot be empty"
+        },
+        {
+            __LINE__,
+            R"({ "name": "myvar", "expression" : "", "source": "response"})",
+            AF_INET,
+            "invalid config: BindingVariable - 'myvar' expression_str cannot be empty"
+        },
+        {
+            __LINE__,
+            R"({ "name": "myvar", "expression" : "pkt5.bogus", "source": "query"})",
+            AF_INET,
+            "invalid config: BindingVariable - 'myvar', error parsing expression:"
+            " 'pkt5.bogus' : <string>:1.4: syntax error, unexpected integer, expecting ."
+        },
+        {
+            __LINE__,
+            R"({ "name": "myvar", "expression" : "pkt4.mac", "source": "BOGUS"})",
+            AF_INET,
+            "invalid config: invalid source 'BOGUS', must be either 'query' or 'response'"
+        },
+        {
+            __LINE__,
+            R"({ "name": "myvar", "expression" : "pkt4.mac", "source": "query", "bogus" : "foo" })",
+            AF_INET,
+            "spurious 'bogus' parameter"
+        }
+    };
+
+    for (auto const& scenario : scenarios) {
+        SCOPED_LINE(scenario.line_);
+        ElementPtr config;
+        ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
+        ASSERT_THROW_MSG(BindingVariable::parse(config, scenario.family_),
+                         DhcpConfigError, scenario.expected_error_);
+    }
+}
+
 /// @brief Verifies basic operation of the cache including
 /// construction, all getters and cache clearing.
 TEST(BindingVariableCacheTest, basics) {