mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-29 13:07:50 +00:00
[5363] Added ifelse operator
This commit is contained in:
parent
f0d1ab76a9
commit
373d2d91f4
@ -579,6 +579,7 @@
|
|||||||
<row><entry>Substring</entry><entry>substring('foobar',0,3)</entry><entry>Return the requested substring</entry></row>
|
<row><entry>Substring</entry><entry>substring('foobar',0,3)</entry><entry>Return the requested substring</entry></row>
|
||||||
<row><entry>Concat</entry><entry>concat('foo','bar')</entry><entry>Return the
|
<row><entry>Concat</entry><entry>concat('foo','bar')</entry><entry>Return the
|
||||||
concatenation of the strings</entry></row>
|
concatenation of the strings</entry></row>
|
||||||
|
<row><entry>Ifelse</entry><entry>ifelse('foo' == 'bar','us','them')</entry><entry>Return the branch value according to the condition</entry></row>
|
||||||
</tbody>
|
</tbody>
|
||||||
</tgroup>
|
</tgroup>
|
||||||
</table>
|
</table>
|
||||||
@ -624,6 +625,15 @@ concatenation of the strings</entry></row>
|
|||||||
concat('foo', 'bar') == 'foobar'
|
concat('foo', 'bar') == 'foobar'
|
||||||
</screen>
|
</screen>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<title>Ifelse</title>
|
||||||
|
The ifelse function "ifelse(cond, iftrue, ifelse)" returns the
|
||||||
|
"iftrue" or "ifelse" branch value following the boolean
|
||||||
|
condition "cond". For instance:
|
||||||
|
<screen>
|
||||||
|
ifelse(option[230].exists, option[230].hex, 'none')
|
||||||
|
</screen>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<note>
|
<note>
|
||||||
|
@ -132,13 +132,13 @@ instantiated with the appropriate value and put onto the expression vector.
|
|||||||
isc::eval::Token class and represents a certain expression primitive.
|
isc::eval::Token class and represents a certain expression primitive.
|
||||||
Currently supported tokens are:
|
Currently supported tokens are:
|
||||||
|
|
||||||
- isc::dhcp::TokenString -- represents a constant string, e.g. "MSFT";
|
- isc::dhcp::TokenString -- represents a constant string, e.g. "MSFT".
|
||||||
- isc::dhcp::TokenHexString -- represents a constant string, encoded as
|
- isc::dhcp::TokenHexString -- represents a constant string, encoded as
|
||||||
hex string, e.g. 0x666f6f which is actually "foo";
|
hex string, e.g. 0x666f6f which is actually "foo".
|
||||||
- isc::dhcp::TokenIpAddress -- represents a constant IP address, encoded as
|
- isc::dhcp::TokenIpAddress -- represents a constant IP address, encoded as
|
||||||
a 4 or 16 byte binary string, e.g., 10.0.0.1 is 0x10000001.
|
a 4 or 16 byte binary string, e.g., 10.0.0.1 is 0x10000001.
|
||||||
- isc::dhcp::TokenOption -- represents an option in a packet, e.g.
|
- isc::dhcp::TokenOption -- represents an option in a packet, e.g.
|
||||||
option[123].text;
|
option[123].text.
|
||||||
- isc::dhcp::TokenRelay4Option -- represents a sub-option inserted by the
|
- isc::dhcp::TokenRelay4Option -- represents a sub-option inserted by the
|
||||||
DHCPv4 relay, e.g. relay[123].text or relay[123].hex
|
DHCPv4 relay, e.g. relay[123].text or relay[123].hex
|
||||||
- isc::dhcp::TokenRelay6Option -- represents a sub-option inserted by
|
- isc::dhcp::TokenRelay6Option -- represents a sub-option inserted by
|
||||||
@ -149,10 +149,11 @@ instantiated with the appropriate value and put onto the expression vector.
|
|||||||
- isc::dhcp::TokenPkt6 -- represents a DHCPv6 packet field (message type
|
- isc::dhcp::TokenPkt6 -- represents a DHCPv6 packet field (message type
|
||||||
or transaction id).
|
or transaction id).
|
||||||
- isc::dhcp::TokenRelay6Field -- represents a DHCPv6 relay information field.
|
- isc::dhcp::TokenRelay6Field -- represents a DHCPv6 relay information field.
|
||||||
- isc::dhcp::TokenEqual -- represents the equal (==) operator;
|
- isc::dhcp::TokenEqual -- represents the equal (==) operator.
|
||||||
- isc::dhcp::TokenSubstring -- represents the substring(text, start, length) operator;
|
- isc::dhcp::TokenSubstring -- represents the substring(text, start, length) operator.
|
||||||
- isc::dhcp::TokenConcat -- represents the concat operator which
|
- isc::dhcp::TokenConcat -- represents the concat operator which
|
||||||
concatenate two other tokens.
|
concatenate two other tokens.
|
||||||
|
- isc::dhcp::TokenIfElse == represents the ifelse(cond, iftrue, ifelse) operator.
|
||||||
- isc::dhcp::TokenNot -- the logical not operator.
|
- isc::dhcp::TokenNot -- the logical not operator.
|
||||||
- isc::dhcp::TokenAnd -- the logical and (strict) operator.
|
- isc::dhcp::TokenAnd -- the logical and (strict) operator.
|
||||||
- isc::dhcp::TokenOr -- the logical or (strict) operator (strict means
|
- isc::dhcp::TokenOr -- the logical or (strict) operator (strict means
|
||||||
|
@ -34,6 +34,18 @@ the value stack. The strings are displayed in hex.
|
|||||||
This debug message indicates that the given binary string is being pushed
|
This debug message indicates that the given binary string is being pushed
|
||||||
onto the value stack. The string is displayed in hex.
|
onto the value stack. The string is displayed in hex.
|
||||||
|
|
||||||
|
# For use with TokenIfElse
|
||||||
|
|
||||||
|
% EVAL_DEBUG_IFELSE_FALSE Popping %1 (false) and %2, leaving %3
|
||||||
|
This debug message indicates that the condition is false so
|
||||||
|
the iftrue branch value is removed and the ifelse branch value
|
||||||
|
is left on the value stack.
|
||||||
|
|
||||||
|
% EVAL_DEBUG_IFELSE_TRUE Popping %1 (true) and %2, leaving %3
|
||||||
|
This debug message indicates that the condition is true so
|
||||||
|
the ifelse branch value is removed and the iftrue branch value
|
||||||
|
is left on the value stack.
|
||||||
|
|
||||||
# For use with TokenIpAddress
|
# For use with TokenIpAddress
|
||||||
|
|
||||||
% EVAL_DEBUG_IPADDRESS Pushing IPAddress %1
|
% EVAL_DEBUG_IPADDRESS Pushing IPAddress %1
|
||||||
|
@ -406,6 +406,14 @@ public:
|
|||||||
EXPECT_TRUE(conc);
|
EXPECT_TRUE(conc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief checks if the given token is an ifelse operator
|
||||||
|
void checkTokenIfElse(const TokenPtr& token) {
|
||||||
|
ASSERT_TRUE(token);
|
||||||
|
boost::shared_ptr<TokenIfElse> alt =
|
||||||
|
boost::dynamic_pointer_cast<TokenIfElse>(token);
|
||||||
|
EXPECT_TRUE(alt);
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief checks if the given expression raises the expected message
|
/// @brief checks if the given expression raises the expected message
|
||||||
/// when it is parsed.
|
/// when it is parsed.
|
||||||
void checkError(const string& expr, const string& msg) {
|
void checkError(const string& expr, const string& msg) {
|
||||||
@ -1209,6 +1217,26 @@ TEST_F(EvalContextTest, concat) {
|
|||||||
checkTokenConcat(tmp3);
|
checkTokenConcat(tmp3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test the parsing of an ifelse expression
|
||||||
|
TEST_F(EvalContextTest, ifElse) {
|
||||||
|
EvalContext eval(Option::V4);
|
||||||
|
|
||||||
|
EXPECT_NO_THROW(parsed_ =
|
||||||
|
eval.parseString("ifelse('foo' == 'bar', 'us', 'them') == 'you'"));
|
||||||
|
|
||||||
|
ASSERT_EQ(8, eval.expression.size());
|
||||||
|
|
||||||
|
TokenPtr tmp1 = eval.expression.at(2);
|
||||||
|
TokenPtr tmp2 = eval.expression.at(3);
|
||||||
|
TokenPtr tmp3 = eval.expression.at(4);
|
||||||
|
TokenPtr tmp4 = eval.expression.at(5);
|
||||||
|
|
||||||
|
checkTokenEq(tmp1);
|
||||||
|
checkTokenString(tmp2, "us");
|
||||||
|
checkTokenString(tmp3, "them");
|
||||||
|
checkTokenIfElse(tmp4);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Test some scanner error cases
|
// Test some scanner error cases
|
||||||
TEST_F(EvalContextTest, scanErrors) {
|
TEST_F(EvalContextTest, scanErrors) {
|
||||||
@ -1358,6 +1386,10 @@ TEST_F(EvalContextTest, parseErrors) {
|
|||||||
"<string>:1.16: syntax error, unexpected ), expecting \",\"");
|
"<string>:1.16: syntax error, unexpected ), expecting \",\"");
|
||||||
checkError("concat('foo','bar','') == 'foobar'",
|
checkError("concat('foo','bar','') == 'foobar'",
|
||||||
"<string>:1.19: syntax error, unexpected \",\", expecting )");
|
"<string>:1.19: syntax error, unexpected \",\", expecting )");
|
||||||
|
checkError("ifelse('foo'=='bar','foo')",
|
||||||
|
"<string>:1.26: syntax error, unexpected ), expecting \",\"");
|
||||||
|
checkError("ifelse('foo'=='bar','foo','bar','')",
|
||||||
|
"<string>:1.32: syntax error, unexpected \",\", expecting )");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests some type error cases
|
// Tests some type error cases
|
||||||
@ -1388,6 +1420,14 @@ TEST_F(EvalContextTest, typeErrors) {
|
|||||||
"<string>:1.8-10: syntax error, unexpected and, expecting ==");
|
"<string>:1.8-10: syntax error, unexpected and, expecting ==");
|
||||||
checkError("'true' or 'false'",
|
checkError("'true' or 'false'",
|
||||||
"<string>:1.8-9: syntax error, unexpected or, expecting ==");
|
"<string>:1.8-9: syntax error, unexpected or, expecting ==");
|
||||||
|
// Ifelse requires a boolean condition and string branches.
|
||||||
|
checkError("ifelse('foobar','foo','bar')",
|
||||||
|
"<string>:1.16: syntax error, unexpected \",\", expecting ==");
|
||||||
|
checkError("ifelse('foo'=='bar','foo'=='foo','bar')",
|
||||||
|
"<string>:1.26-27: syntax error, unexpected ==, "
|
||||||
|
"expecting \",\"");
|
||||||
|
checkError("ifelse('foo'=='bar','foo','bar'=='bar')",
|
||||||
|
"<string>:1.32-33: syntax error, unexpected ==, expecting )");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -490,6 +490,13 @@ TEST_F(ExpressionsTest, evaluateString) {
|
|||||||
EvalContext::PARSER_STRING);
|
EvalContext::PARSER_STRING);
|
||||||
testExpressionNegative<EvalParseError>("pkt6.msgtype == 1", Option::V6,
|
testExpressionNegative<EvalParseError>("pkt6.msgtype == 1", Option::V6,
|
||||||
EvalContext::PARSER_STRING);
|
EvalContext::PARSER_STRING);
|
||||||
|
|
||||||
|
// Check that ifelse works as expecting (it was added explicitely for
|
||||||
|
// the string evaluation).
|
||||||
|
testExpressionString(Option::V4,
|
||||||
|
"ifelse(option[100].exists,'foo','bar')", "foo");
|
||||||
|
testExpressionString(Option::V4,
|
||||||
|
"ifelse(option[200].exists,'foo','bar')", "bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -1954,6 +1954,45 @@ TEST_F(TokenTest, concat) {
|
|||||||
EXPECT_TRUE(checkFile());
|
EXPECT_TRUE(checkFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This test checks if a token representing an ifelse is able
|
||||||
|
// to select the branch following the condition.
|
||||||
|
TEST_F(TokenTest, ifElse) {
|
||||||
|
ASSERT_NO_THROW(t_.reset(new TokenIfElse()));
|
||||||
|
|
||||||
|
// Ifelse requires three values on the stack, try
|
||||||
|
// with 0, 1 and 2 all should throw an exception
|
||||||
|
EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
|
||||||
|
|
||||||
|
values_.push("bar");
|
||||||
|
EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
|
||||||
|
|
||||||
|
values_.push("foo");
|
||||||
|
EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
|
||||||
|
|
||||||
|
// The condition must be a boolean
|
||||||
|
values_.push("bar");
|
||||||
|
EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
|
||||||
|
|
||||||
|
// Check if what it returns
|
||||||
|
clearStack();
|
||||||
|
ASSERT_NO_THROW(t_.reset(new TokenIfElse()));
|
||||||
|
values_.push("true");
|
||||||
|
values_.push("foo");
|
||||||
|
values_.push("bar");
|
||||||
|
EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
|
||||||
|
ASSERT_EQ(1, values_.size());
|
||||||
|
EXPECT_EQ("foo", values_.top());
|
||||||
|
|
||||||
|
clearStack();
|
||||||
|
ASSERT_NO_THROW(t_.reset(new TokenIfElse()));
|
||||||
|
values_.push("false");
|
||||||
|
values_.push("foo");
|
||||||
|
values_.push("bar");
|
||||||
|
EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
|
||||||
|
ASSERT_EQ(1, values_.size());
|
||||||
|
EXPECT_EQ("bar", values_.top());
|
||||||
|
}
|
||||||
|
|
||||||
// This test checks if a token representing a not is able to
|
// This test checks if a token representing a not is able to
|
||||||
// negate a boolean value (with incorrectly built stack).
|
// negate a boolean value (with incorrectly built stack).
|
||||||
TEST_F(TokenTest, operatorNotInvalid) {
|
TEST_F(TokenTest, operatorNotInvalid) {
|
||||||
|
@ -591,6 +591,42 @@ TokenConcat::evaluate(Pkt& /*pkt*/, ValueStack& values) {
|
|||||||
.arg(toHex(values.top()));
|
.arg(toHex(values.top()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TokenIfElse::evaluate(Pkt& /*pkt*/, ValueStack& values) {
|
||||||
|
|
||||||
|
if (values.size() < 3) {
|
||||||
|
isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
|
||||||
|
"3 values for ifelse, got " << values.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
string iffalse = values.top();
|
||||||
|
values.pop();
|
||||||
|
string iftrue = values.top();
|
||||||
|
values.pop();
|
||||||
|
string cond = values.top();
|
||||||
|
values.pop();
|
||||||
|
bool val = toBool(cond);
|
||||||
|
|
||||||
|
if (val) {
|
||||||
|
values.push(iftrue);
|
||||||
|
} else {
|
||||||
|
values.push(iffalse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log what we popped and pushed
|
||||||
|
if (val) {
|
||||||
|
LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_IFELSE_TRUE)
|
||||||
|
.arg('\'' + cond + '\'')
|
||||||
|
.arg(toHex(iffalse))
|
||||||
|
.arg(toHex(iftrue));
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_IFELSE_FALSE)
|
||||||
|
.arg('\'' +cond + '\'')
|
||||||
|
.arg(toHex(iftrue))
|
||||||
|
.arg(toHex(iffalse));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
TokenNot::evaluate(Pkt& /*pkt*/, ValueStack& values) {
|
TokenNot::evaluate(Pkt& /*pkt*/, ValueStack& values) {
|
||||||
|
|
||||||
|
@ -694,6 +694,37 @@ public:
|
|||||||
void evaluate(Pkt& pkt, ValueStack& values);
|
void evaluate(Pkt& pkt, ValueStack& values);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// @brief Token that represents an alternative
|
||||||
|
///
|
||||||
|
/// For example in the sub-expression "ifelse(cond, iftrue, iffalse)"
|
||||||
|
/// the boolean "cond" expression is evaluated, if it is true then
|
||||||
|
/// the "iftrue" value is returned else the "iffalse" value is returned.
|
||||||
|
/// Please note that "iftrue" and "iffalse" must be plain string (vs. boolean)
|
||||||
|
/// expressions and they are always evaluated. If you want a similar
|
||||||
|
/// operator on boolean expressions it can be built from "and", "or" and
|
||||||
|
/// "not" boolean operators.
|
||||||
|
class TokenIfElse : public Token {
|
||||||
|
public:
|
||||||
|
/// @brief Constructor (does nothing)
|
||||||
|
TokenIfElse() { }
|
||||||
|
|
||||||
|
/// @brief Alternative.
|
||||||
|
///
|
||||||
|
/// Evaluation does not use packet information, but rather consumes the
|
||||||
|
/// last three results. It does a simple string comparison on the
|
||||||
|
/// condition (third value on the stack) which is required to be
|
||||||
|
/// either "true" or "false", and leaves the second and first
|
||||||
|
/// value if the condition is "true" or "false".
|
||||||
|
///
|
||||||
|
/// @throw EvalBadStack if there are less than 3 values on stack
|
||||||
|
/// @throw EvalTypeError if the third value (the condition) is not
|
||||||
|
/// either "true" or "false"
|
||||||
|
///
|
||||||
|
/// @param pkt (unused)
|
||||||
|
/// @param values - stack of values (two items are removed)
|
||||||
|
void evaluate(Pkt& pkt, ValueStack& values);
|
||||||
|
};
|
||||||
|
|
||||||
/// @brief Token that represents logical negation operator
|
/// @brief Token that represents logical negation operator
|
||||||
///
|
///
|
||||||
/// For example in the expression "not(option[vendor-class].text == 'MSF')"
|
/// For example in the expression "not(option[vendor-class].text == 'MSF')"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user