]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3502] Checkpoint: syntax to do
authorFrancis Dupont <fdupont@isc.org>
Wed, 7 Aug 2024 15:27:47 +0000 (17:27 +0200)
committerFrancis Dupont <fdupont@isc.org>
Wed, 21 Aug 2024 13:12:38 +0000 (15:12 +0200)
src/lib/eval/eval_messages.cc
src/lib/eval/eval_messages.h
src/lib/eval/eval_messages.mes
src/lib/eval/tests/evaluate_unittest.cc
src/lib/eval/tests/token_unittest.cc
src/lib/eval/token.cc
src/lib/eval/token.h

index 7e7ad0e721cd2ca83e76c253c9f610bdf9b6cb56..72324a3f324e6a0f059cfb213199cd4dcf3c23ab 100644 (file)
@@ -29,6 +29,9 @@ extern const isc::log::MessageID EVAL_DEBUG_OR = "EVAL_DEBUG_OR";
 extern const isc::log::MessageID EVAL_DEBUG_PKT = "EVAL_DEBUG_PKT";
 extern const isc::log::MessageID EVAL_DEBUG_PKT4 = "EVAL_DEBUG_PKT4";
 extern const isc::log::MessageID EVAL_DEBUG_PKT6 = "EVAL_DEBUG_PKT6";
+extern const isc::log::MessageID EVAL_DEBUG_POP_AND_BRANCH_FALSE = "EVAL_DEBUG_POP_AND_BRANCH_FALSE";
+extern const isc::log::MessageID EVAL_DEBUG_POP_OR_BRANCH_FALSE = "EVAL_DEBUG_POP_OR_BRANCH_FALSE";
+extern const isc::log::MessageID EVAL_DEBUG_POP_OR_BRANCH_TRUE = "EVAL_DEBUG_POP_OR_BRANCH_TRUE";
 extern const isc::log::MessageID EVAL_DEBUG_RELAY6 = "EVAL_DEBUG_RELAY6";
 extern const isc::log::MessageID EVAL_DEBUG_RELAY6_RANGE = "EVAL_DEBUG_RELAY6_RANGE";
 extern const isc::log::MessageID EVAL_DEBUG_SPLIT = "EVAL_DEBUG_SPLIT";
@@ -86,6 +89,9 @@ const char* values[] = {
     "EVAL_DEBUG_PKT", "%1: Pushing PKT meta data %2 with value %3",
     "EVAL_DEBUG_PKT4", "%1: Pushing PKT4 field %2 with value %3",
     "EVAL_DEBUG_PKT6", "%1: Pushing PKT6 field %2 with value %3",
+    "EVAL_DEBUG_POP_AND_BRANCH_FALSE", "Value is false: branching to %1",
+    "EVAL_DEBUG_POP_OR_BRANCH_FALSE", "Value is false: keeping it and branching to %1",
+    "EVAL_DEBUG_POP_OR_BRANCH_TRUE", "Value is true: keeping it and branching to %1",
     "EVAL_DEBUG_RELAY6", "%1: Pushing PKT6 relay field %2 nest %3 with value %4",
     "EVAL_DEBUG_RELAY6_RANGE", "%1: Pushing PKT6 relay field %2 nest %3 with value %4",
     "EVAL_DEBUG_SPLIT", "%1: Popping field %2, delimiters %3, string %4, pushing result %5",
index f9b2ec5acfd16d5b66e05a89100eac1fd0ab02f8..4eb7f66513d22566b1afc286640de509f331a97f 100644 (file)
@@ -30,6 +30,9 @@ extern const isc::log::MessageID EVAL_DEBUG_OR;
 extern const isc::log::MessageID EVAL_DEBUG_PKT;
 extern const isc::log::MessageID EVAL_DEBUG_PKT4;
 extern const isc::log::MessageID EVAL_DEBUG_PKT6;
+extern const isc::log::MessageID EVAL_DEBUG_POP_AND_BRANCH_FALSE;
+extern const isc::log::MessageID EVAL_DEBUG_POP_OR_BRANCH_FALSE;
+extern const isc::log::MessageID EVAL_DEBUG_POP_OR_BRANCH_TRUE;
 extern const isc::log::MessageID EVAL_DEBUG_RELAY6;
 extern const isc::log::MessageID EVAL_DEBUG_RELAY6_RANGE;
 extern const isc::log::MessageID EVAL_DEBUG_SPLIT;
index ad81a3d229335f8ba7f7f7d9975689631d7abae3..4225e7f2b096c089cd798b8b1a195fbabcd55be2 100644 (file)
@@ -128,6 +128,24 @@ This debug message indicates that the first value is popped from
 the value stack, negated and then pushed onto the value stack.
 The string is displayed in text.
 
+# For use with TokenPopAndBranchFalse
+% EVAL_DEBUG_POP_AND_BRANCH_FALSE Value is false: branching to %1
+Logged at debug log level 55.
+This debug message indicates that a branch on false condition is performed
+to the displayed target.
+
+# For use with TokenPopOrBranchFalse
+% EVAL_DEBUG_POP_OR_BRANCH_FALSE Value is false: keeping it and branching to %1
+Logged at debug log level 55.
+This debug message indicates that a branch on false condition is performed
+to the displayed target.
+
+# For use with TokenPopOrBranchTrue
+% EVAL_DEBUG_POP_OR_BRANCH_TRUE Value is true: keeping it and branching to %1
+Logged at debug log level 55.
+This debug message indicates that a branch on true condition is performed
+to the displayed target.
+
 # For use with TokenOption based classes.  These include TokenOption,
 # TokenRelay4Option and TokenRelay6Option.
 
index ecf4db79e18c8a29ad075879640c5009299d22ad..293b53dc7e929e772ec8c09201940a1fc8fcb318 100644 (file)
@@ -579,4 +579,144 @@ TEST_F(ExpressionsTest, label) {
     EXPECT_EQ(2, values.size());
 }
 
-};
+// Tests the pop or branch when true / left or.
+TEST_F(ExpressionsTest, popOrBranchTrue) {
+    // The left or can be implemented as <left><P|BT-L><right><L>.
+    // Do the complete table.
+    TokenPtr branch;
+    ASSERT_NO_THROW(branch.reset(new TokenPopOrBranchTrue(123)));
+    TokenPtr label;
+    ASSERT_NO_THROW(label.reset(new TokenLabel(123)));
+    TokenPtr left;
+    TokenPtr right;
+    bool result_(false);
+
+    // False or false == false.
+    ASSERT_NO_THROW(left.reset(new TokenString("false")));
+    ASSERT_NO_THROW(right.reset(new TokenString("false")));
+    e_.push_back(left);
+    e_.push_back(branch);
+    e_.push_back(right);
+    e_.push_back(label);
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
+    EXPECT_FALSE(result_);
+    e_.clear();
+
+    // False or true == true.
+    ASSERT_NO_THROW(left.reset(new TokenString("false")));
+    ASSERT_NO_THROW(right.reset(new TokenString("true")));
+    e_.push_back(left);
+    e_.push_back(branch);
+    e_.push_back(right);
+    e_.push_back(label);
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
+    EXPECT_TRUE(result_);
+    e_.clear();
+
+    // True or any thing == true.
+    ASSERT_NO_THROW(left.reset(new TokenString("true")));
+    ASSERT_NO_THROW(right.reset(new TokenString("any thing")));
+    e_.push_back(left);
+    e_.push_back(branch);
+    e_.push_back(right);
+    e_.push_back(label);
+    EXPECT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
+    EXPECT_TRUE(result_);
+}
+
+// Tests the pop or branch when false / left and.
+TEST_F(ExpressionsTest, popOrBranchFalse) {
+    // The left and can be implemented as <left><P|BF-L><right><L>.
+    // Do the complete table.
+    TokenPtr branch;
+    ASSERT_NO_THROW(branch.reset(new TokenPopOrBranchFalse(123)));
+    TokenPtr label;
+    ASSERT_NO_THROW(label.reset(new TokenLabel(123)));
+    TokenPtr left;
+    TokenPtr right;
+    bool result_(false);
+
+    // True and true == true.
+    ASSERT_NO_THROW(left.reset(new TokenString("true")));
+    ASSERT_NO_THROW(right.reset(new TokenString("true")));
+    e_.push_back(left);
+    e_.push_back(branch);
+    e_.push_back(right);
+    e_.push_back(label);
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
+    EXPECT_TRUE(result_);
+    e_.clear();
+
+    // True and false == false.
+    ASSERT_NO_THROW(left.reset(new TokenString("true")));
+    ASSERT_NO_THROW(right.reset(new TokenString("false")));
+    e_.push_back(left);
+    e_.push_back(branch);
+    e_.push_back(right);
+    e_.push_back(label);
+    ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
+    EXPECT_FALSE(result_);
+    e_.clear();
+
+    // False and any thing == false.
+    ASSERT_NO_THROW(left.reset(new TokenString("false")));
+    ASSERT_NO_THROW(right.reset(new TokenString("any thing")));
+    e_.push_back(left);
+    e_.push_back(branch);
+    e_.push_back(right);
+    e_.push_back(label);
+    EXPECT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
+    EXPECT_FALSE(result_);
+}
+
+// Tests the pop and branch when false / lazy if.
+TEST_F(ExpressionsTest, popAndBranchFalse) {
+    // The lazy can be implemented as:
+    // <test><P&BF-L1><then><B-L2><L1><else><L2>>.
+    // Do the complete table.
+    TokenPtr brancht;
+    ASSERT_NO_THROW(brancht.reset(new TokenPopAndBranchFalse(123)));
+    TokenPtr branchu;
+    ASSERT_NO_THROW(branchu.reset(new TokenBranch(567)));
+    TokenPtr label1;
+    ASSERT_NO_THROW(label1.reset(new TokenLabel(123)));
+    TokenPtr label2;
+    ASSERT_NO_THROW(label2.reset(new TokenLabel(567)));
+    TokenPtr test;
+    TokenPtr foo;
+    ASSERT_NO_THROW(foo.reset(new TokenString("foo")));
+    TokenPtr bar;
+    ASSERT_NO_THROW(bar.reset(new TokenString("bar")));
+    TokenPtr extra;
+    ASSERT_NO_THROW(extra.reset(new TokenString("extra token")));
+    string result_("");
+
+    // if true then foo else bar == foo
+    ASSERT_NO_THROW(test.reset(new TokenString("true")));
+    e_.push_back(test);
+    e_.push_back(brancht);
+    e_.push_back(foo);
+    e_.push_back(branchu);
+    e_.push_back(label1);
+    e_.push_back(bar);
+    e_.push_back(extra);
+    e_.push_back(label2);
+    ASSERT_NO_THROW(result_ = evaluateString(e_, *pkt4_));
+    EXPECT_EQ("foo", result_);
+    e_.clear();
+
+    // if false then foo else bar == bar
+    ASSERT_NO_THROW(test.reset(new TokenString("false")));
+    e_.push_back(test);
+    e_.push_back(brancht);
+    e_.push_back(foo);
+    e_.push_back(extra);
+    e_.push_back(branchu);
+    e_.push_back(label1);
+    e_.push_back(bar);
+    e_.push_back(label2);
+    ASSERT_NO_THROW(result_ = evaluateString(e_, *pkt6_));
+    EXPECT_EQ("bar", result_);
+}
+
+}
index 3b71f6db89d108c729ee223d98337c9f2a9dcf50..8d2b55eb083be7f5b24b0e3ddd80a15350d3690d 100644 (file)
@@ -3916,4 +3916,104 @@ TEST_F(TokenTest, branch) {
     ASSERT_TRUE(values_.empty());
 }
 
+// Verify TokenPopOrBranchTrue.
+TEST_F(TokenTest, popOrBranchTrue) {
+    // 0 is not a valid branch.
+    ASSERT_THROW(t_.reset(new TokenPopOrBranchTrue(0)), EvalParseError);
+
+    ASSERT_NO_THROW(t_.reset(new TokenPopOrBranchTrue(123)));
+    EXPECT_EQ(0, t_->getLabel());
+
+    // CASE 1: The stack is empty.
+    EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+    // CASE 2: The top value is not a boolen.
+    values_.push("foo");
+    EXPECT_THROW(t_->evaluate(*pkt6_, values_), EvalTypeError);
+
+    // CASE 3: The top value is true.
+    clearStack(false);
+    values_.push("true");
+    ASSERT_EQ(1, values_.size());
+    unsigned next(0);
+    ASSERT_NO_THROW(next = t_->evaluate(*pkt4_, values_));
+    EXPECT_EQ(123, next);
+    ASSERT_EQ(1, values_.size());
+    string result = values_.top();
+    EXPECT_EQ("true", result);
+
+    // CASE 4: The top value is false.
+    clearStack(false);
+    values_.push("false");
+    ASSERT_EQ(1, values_.size());
+    testEvaluate(t_, *pkt6_, values_);
+    EXPECT_TRUE(values_.empty());
+}
+
+// Verify TokenPopOrBranchFalse.
+TEST_F(TokenTest, popOrBranchFalse) {
+    // 0 is not a valid branch.
+    ASSERT_THROW(t_.reset(new TokenPopOrBranchFalse(0)), EvalParseError);
+
+    ASSERT_NO_THROW(t_.reset(new TokenPopOrBranchFalse(123)));
+    EXPECT_EQ(0, t_->getLabel());
+
+    // CASE 1: The stack is empty.
+    EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+    // CASE 2: The top value is not a boolen.
+    values_.push("foo");
+    EXPECT_THROW(t_->evaluate(*pkt6_, values_), EvalTypeError);
+
+    // CASE 3: The top value is true.
+    clearStack(false);
+    values_.push("true");
+    ASSERT_EQ(1, values_.size());
+    testEvaluate(t_, *pkt6_, values_);
+    EXPECT_TRUE(values_.empty());
+
+    // CASE 4: The top value is false.
+    clearStack(false);
+    values_.push("false");
+    ASSERT_EQ(1, values_.size());
+    unsigned next(0);
+    ASSERT_NO_THROW(next = t_->evaluate(*pkt4_, values_));
+    EXPECT_EQ(123, next);
+    ASSERT_EQ(1, values_.size());
+    string result = values_.top();
+    EXPECT_EQ("false", result);
+}
+
+// Verify TokenPopAndBranchFalse.
+TEST_F(TokenTest, popAndBranchFalse) {
+    // 0 is not a valid branch.
+    ASSERT_THROW(t_.reset(new TokenPopAndBranchFalse(0)), EvalParseError);
+
+    ASSERT_NO_THROW(t_.reset(new TokenPopAndBranchFalse(123)));
+    EXPECT_EQ(0, t_->getLabel());
+
+    // CASE 1: The stack is empty.
+    EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+    // CASE 2: The top value is not a boolen.
+    values_.push("foo");
+    EXPECT_THROW(t_->evaluate(*pkt6_, values_), EvalTypeError);
+
+    // CASE 3: The top value is true.
+    clearStack(false);
+    values_.push("true");
+    ASSERT_EQ(1, values_.size());
+    testEvaluate(t_, *pkt6_, values_);
+    EXPECT_TRUE(values_.empty());
+
+    // CASE 4: The top value is false.
+    clearStack(false);
+    values_.push("false");
+    ASSERT_EQ(1, values_.size());
+    unsigned next(0);
+    ASSERT_NO_THROW(next = t_->evaluate(*pkt4_, values_));
+    EXPECT_EQ(123, next);
+    EXPECT_TRUE(values_.empty());
+}
+
 }
index f6d2b6cbd9acf2ea46bfb2609a254dce29831583..3d0f719283cceabe3387b92aed96c06b88d97875 100644 (file)
@@ -1529,3 +1529,72 @@ TokenBranch::evaluate(Pkt&, ValueStack&) {
         .arg(target_);
     return (target_);
 }
+
+TokenPopOrBranchTrue::TokenPopOrBranchTrue(const unsigned target)
+    : TokenBranch(target) {
+}
+
+unsigned
+TokenPopOrBranchTrue::evaluate(Pkt&, ValueStack& values) {
+    if (values.size() == 0) {
+        isc_throw(EvalBadStack, "Incorrect empty stack.");
+    }
+
+    string op = values.top();
+    bool val = toBool(op);
+
+    if (!val) {
+        values.pop();
+        return (0);
+    }
+
+    LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_POP_OR_BRANCH_TRUE)
+        .arg(target_);
+    return (target_);
+}
+
+TokenPopOrBranchFalse::TokenPopOrBranchFalse(const unsigned target)
+    : TokenBranch(target) {
+}
+
+unsigned
+TokenPopOrBranchFalse::evaluate(Pkt&, ValueStack& values) {
+    if (values.size() == 0) {
+        isc_throw(EvalBadStack, "Incorrect empty stack.");
+    }
+
+    string op = values.top();
+    bool val = toBool(op);
+
+    if (val) {
+        values.pop();
+        return (0);
+    }
+
+    LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_POP_OR_BRANCH_FALSE)
+        .arg(target_);
+    return (target_);
+}
+
+TokenPopAndBranchFalse::TokenPopAndBranchFalse(const unsigned target)
+    : TokenBranch(target) {
+}
+
+unsigned
+TokenPopAndBranchFalse::evaluate(Pkt&, ValueStack& values) {
+    if (values.size() == 0) {
+        isc_throw(EvalBadStack, "Incorrect empty stack.");
+    }
+
+    string op = values.top();
+    values.pop();
+    bool val = toBool(op);
+
+    if (val) {
+        return (0);
+    }
+
+    LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_POP_AND_BRANCH_FALSE)
+        .arg(target_);
+    return (target_);
+}
index c7ca87512f4c2d2a1d518de6aefdcdf90fe2db84..14397f64f2f615c83813e43e4f00d9af3aab3841 100644 (file)
@@ -1006,6 +1006,8 @@ public:
 
 /// @brief Token that represents logical and operator
 ///
+/// @note Strict version i.e. evaluating right branch even left is false
+///
 /// For example "option[10].exists and option[11].exists"
 class TokenAnd : public Token {
 public:
@@ -1031,6 +1033,8 @@ public:
 
 /// @brief Token that represents logical or operator
 ///
+/// @note Strict version i.e. evaluating right branch even left is right
+///
 /// For example "option[10].exists or option[11].exists"
 class TokenOr : public Token {
 public:
@@ -1408,9 +1412,10 @@ protected:
     unsigned label_;
 };
 
-/// @brief Token unconditional branch.
+/// @brief Token that represents unconditional branch.
 ///
 /// Unconditionally branch to a forward target.
+/// Also the base class of branch tokens.
 class TokenBranch : public Token {
 public:
     /// @brief Constructor
@@ -1436,6 +1441,77 @@ protected:
     unsigned target_;
 };
 
+/// @brief Token that represents pop or branch if true.
+///
+/// Branch to a forward target when the top value is true else pop it.
+/// Can be used to implement the left "or" boolean operator.
+class TokenPopOrBranchTrue : public TokenBranch {
+public:
+    /// @brief Constructor
+    ///
+    /// @param target the label to branch to
+    /// @throw EvalParseError when target is 0
+    TokenPopOrBranchTrue(const unsigned target);
+
+    /// @brief Looks at the top of stack which must be "false" or "true".
+    /// On "false" pops it and returns 0, on "true" return the branch target.
+    ///
+    /// @param pkt (unused)
+    /// @param values - stack of values (use/pop boolean top of stack)
+    /// @throw EvalBadStack if there are less than 1 value on stack
+    /// @throw EvalTypeError if the top value on the stack is not either
+    ///        "true" or "false"
+    virtual unsigned evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents pop or branch if false.
+///
+/// Branch to a forward target when the top value is false else pop it.
+/// Can be used to implement the left "and" boolean operator.
+class TokenPopOrBranchFalse : public TokenBranch {
+public:
+    /// @brief Constructor
+    ///
+    /// @param target the label to branch to
+    /// @throw EvalParseError when target is 0
+    TokenPopOrBranchFalse(const unsigned target);
+
+    /// @brief Looks at the top of stack which must be "false" or "true".
+    /// On "true" pops it and returns 0, on "false" return the branch target.
+    ///
+    /// @param pkt (unused)
+    /// @param values - stack of values (use/pop boolean top of stack)
+    /// @throw EvalBadStack if there are less than 1 value on stack
+    /// @throw EvalTypeError if the top value on the stack is not either
+    ///        "true" or "false"
+    virtual unsigned evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents pop and branch if false.
+///
+/// Pop the op value, branch to a forward target when false.
+/// Can be used to implement the lazy "if" boolean operator i.e.
+/// evaluating either "then" or "else" branches (vs. strict the version
+/// which evaluates both).
+class TokenPopAndBranchFalse : public TokenBranch {
+public:
+    /// @brief Constructor
+    ///
+    /// @param target the label to branch to
+    /// @throw EvalParseError when target is 0
+    TokenPopAndBranchFalse(const unsigned target);
+
+    /// @brief Pops the top of stack which must be "false" or "true".
+    /// On "true" returns 0, on "false" return the branch target.
+    ///
+    /// @param pkt (unused)
+    /// @param values - stack of values (pop the boolean top value)
+    /// @throw EvalBadStack if there are less than 1 value on stack
+    /// @throw EvalTypeError if the top value on the stack is not either
+    ///        "true" or "false"
+    virtual unsigned evaluate(Pkt& pkt, ValueStack& values);
+};
+
 } // end of isc::dhcp namespace
 } // end of isc namespace
 #endif