Browse Source

[4483] Integers in expressions implemented.

Tomek Mrugalski 8 years ago
parent
commit
89d9384eb1

+ 11 - 0
src/lib/eval/eval_context.cc

@@ -147,6 +147,17 @@ EvalContext::convertUint32(const std::string& number,
     return (static_cast<uint32_t>(n));
 }
 
+std::string
+EvalContext::fromUint32(const uint32_t integer) {
+    std::string tmp(4, 0);
+    tmp[0] = (integer >> 24) & 0xff;
+    tmp[1] = (integer >> 16) & 0xff;
+    tmp[2] = (integer >> 8) & 0xff;
+    tmp[3] = integer & 0xff;
+
+    return (tmp);
+}
+
 void
 EvalContext::fatal (const std::string& what)
 {

+ 19 - 7
src/lib/eval/eval_context.h

@@ -69,13 +69,13 @@ public:
     ///
     /// @param loc location within the parsed file when experienced a problem.
     /// @param what string explaining the nature of the error.
-    void error(const isc::eval::location& loc, const std::string& what);
+    static void error(const isc::eval::location& loc, const std::string& what);
 
     /// @brief Error handler
     ///
     /// This is a simplified error reporting tool for possible future
     /// cases when the EvalParser is not able to handle the packet.
-    void error(const std::string& what);
+    static void error(const std::string& what);
 
     /// @brief Fatal error handler
     ///
@@ -103,12 +103,14 @@ public:
 
     /// @brief Attempts to convert string to unsigned 32bit integer
     ///
+    /// For reverse conversion, see @ref fromUint32
+    ///
     /// @param number string to be converted
     /// @param loc the location of the token
     /// @return the integer value
     /// @throw EvalParseError if conversion fails or the value is out of range.
-    uint32_t convertUint32(const std::string& number,
-                           const isc::eval::location& loc);
+    static uint32_t convertUint32(const std::string& number,
+                                  const isc::eval::location& loc);
 
     /// @brief Attempts to convert string to unsigned 8bit integer
     ///
@@ -116,8 +118,8 @@ public:
     /// @param loc the location of the token
     /// @return the integer value
     /// @throw EvalParseError if conversion fails or the value is out of range.
-    uint8_t convertUint8(const std::string& number,
-                         const isc::eval::location& loc);
+    static uint8_t convertUint8(const std::string& number,
+                                const isc::eval::location& loc);
 
     /// @brief Nest level conversion
     ///
@@ -127,7 +129,17 @@ public:
     /// @throw calls the syntax error function if the value is not in
     ///        the range 0..31
     uint8_t convertNestLevelNumber(const std::string& nest_level,
-                                    const isc::eval::location& loc);
+                                   const isc::eval::location& loc);
+
+    /// @brief Converts integer to string representation
+    ///
+    /// The integer is coded as 4 bytes long string in network order, e.g.
+    /// 6 is represented as 00000006. For reverse conversion, see
+    /// @ref convertUint32.
+    ///
+    /// @param integer value to be converted
+    /// @return 4 bytes long string that encodes the value.
+    static std::string fromUint32(const uint32_t integer);
 
     /// @brief Returns the universe (v4 or v6)
     ///

+ 12 - 0
src/lib/eval/parser.yy

@@ -89,6 +89,7 @@ using namespace isc::eval;
 
 %type <uint16_t> option_code
 %type <uint32_t> enterprise_id
+%type <uint32_t> integer_expr
 %type <TokenOption::RepresentationType> option_repr_type
 %type <TokenRelay6Field::FieldType> relay6_field
 %type <uint8_t> nest_level
@@ -370,6 +371,17 @@ string_expr : STRING
                                                                TokenVendor::DATA, index));
                     ctx.expression.push_back(vendor_class);
                 }
+             | integer_expr
+                {
+                    TokenPtr integer(new TokenInteger($1));
+                    ctx.expression.push_back(integer);
+                }
+            ;
+
+integer_expr: INTEGER
+              {
+                  $$ = ctx.convertUint32($1, @1);
+              }
             ;
 
 option_code : INTEGER

+ 45 - 8
src/lib/eval/tests/context_unittest.cc

@@ -96,6 +96,14 @@ public:
         EXPECT_TRUE(eq);
     }
 
+    void checkTokenInteger(const TokenPtr& token, uint32_t exp_value) {
+        ASSERT_TRUE(token);
+        boost::shared_ptr<TokenInteger> integer =
+            boost::dynamic_pointer_cast<TokenInteger>(token);
+        ASSERT_TRUE(integer);
+        EXPECT_EQ(exp_value, integer->getInteger());
+    }
+
     /// @brief checks if the given token is an option with the expected code
     /// and representation type
     /// @param token token to be checked
@@ -929,7 +937,7 @@ TEST_F(EvalContextTest, relay6OptionLimits) {
     checkError("relay6[32].option[123].text == 'foo'",
                "<string>:1.8-9: Nest level has invalid value in 32. "
                "Allowed range: 0..31");
-                     
+
     // next level must be a positive number
     checkError("relay6[-1].option[123].text == 'foo'",
                "<string>:1.8-9: Invalid value in -1. Allowed range: 0..255");
@@ -1200,10 +1208,17 @@ TEST_F(EvalContextTest, scanErrors) {
 TEST_F(EvalContextTest, scanParseErrors) {
     checkError("", "<string>:1.1: syntax error, unexpected end of file");
     checkError(" ", "<string>:1.2: syntax error, unexpected end of file");
-    checkError("0x", "<string>:1.1: syntax error, unexpected integer");
+    checkError("0x", "<string>:1.2: Invalid character: x");
     checkError("0abc",
-               "<string>:1.1: syntax error, unexpected integer");
-    checkError("10.0.1", "<string>:1.1-2: syntax error, unexpected integer");
+               "<string>:1.2: Invalid character: a");
+
+    // This one is a little bid odd. This is truncated address, so it's not
+    // recognized as address. Instead, first token (10) is recognized as
+    // integer. The only thing we can do with integers right now is test
+    // for equality, so the only possible next token is ==. There's a dot
+    // instead, so an error is reported.
+    checkError("10.0.1", "<string>:1.3: syntax error, unexpected ., expecting ==");
+
     checkError("10.256.0.1",
                "<string>:1.1-10: Failed to convert 10.256.0.1 to "
                "an IP address.");
@@ -1332,10 +1347,13 @@ TEST_F(EvalContextTest, typeErrors) {
     checkError("substring('foobar',0x32,1) == 'foo'",
                "<string>:1.20-23: syntax error, unexpected constant "
                "hexstring, expecting integer");
-    checkError("concat('foo',3) == 'foo3'",
-               "<string>:1.14: syntax error, unexpected integer");
-    checkError("concat(3,'foo') == '3foo'",
-               "<string>:1.8: syntax error, unexpected integer");
+
+    // With the #4483 addition, all integers are treated as 4 byte strings,
+    // so those checks no longer makes sense. Commeting it out.
+    // checkError("concat('foo',3) == 'foo3'",
+    //            "<string>:1.14: syntax error, unexpected integer");
+    // checkError("concat(3,'foo') == '3foo'",
+    //           "<string>:1.8: syntax error, unexpected integer");
     checkError("('foo' == 'bar') == 'false'",
                "<string>:1.18-19: syntax error, unexpected ==, "
                "expecting end of file");
@@ -1439,4 +1457,23 @@ TEST_F(EvalContextTest, vendorClass6DataIndex) {
     testVendorClass("vendor-class[4491].data[3] == 0x1234", Option::V6, 4491, 3);
 }
 
+// Checks if integer expressions can be parsed and checked for equality.
+TEST_F(EvalContextTest, integer1) {
+
+    EvalContext eval(Option::V6);
+
+    EXPECT_NO_THROW(parsed_ = eval.parseString("1 == 2"));
+    EXPECT_TRUE(parsed_);
+
+    ASSERT_EQ(3, eval.expression.size());
+
+    TokenPtr tmp = eval.expression.at(0);
+    ASSERT_TRUE(tmp);
+    checkTokenInteger(tmp, 1);
+    tmp = eval.expression.at(1);
+
+    ASSERT_TRUE(tmp);
+    checkTokenInteger(tmp, 2);
+}
+
 };

+ 37 - 9
src/lib/eval/tests/token_unittest.cc

@@ -7,6 +7,7 @@
 #include <config.h>
 #include <fstream>
 #include <eval/token.h>
+#include <eval/eval_context.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/dhcp4.h>
@@ -183,12 +184,14 @@ public:
     }
 
     /// @brief Convenience function. Removes token and values stacks.
-    void clearStack() {
+    /// @param token specifies if the convenience token should be removed or not
+    void clearStack(bool token = true) {
         while (!values_.empty()) {
             values_.pop();
         }
-
-        t_.reset();
+        if (token) {
+            t_.reset();
+        }
     }
 
     /// @brief Aux. function that stores integer values as 4 bytes string.
@@ -196,12 +199,7 @@ public:
     /// @param value integer value to be stored
     /// @return 4 bytes long string with encoded value.
     string encode(uint32_t value) {
-        string tmp(4,0);
-        tmp[0] = value >> 24;
-        tmp[1] = value >> 16;
-        tmp[2] = value >> 8;
-        tmp[3] = value;
-        return (tmp);
+        return EvalContext::fromUint32(value);
     }
 
     TokenPtr t_; ///< Just a convenience pointer
@@ -484,6 +482,26 @@ public:
 
         evaluate(u, expected);
     }
+
+    /// @brief Tests if TokenInteger evaluates to proper value
+    ///
+    /// @param value integer value passed to constructor
+    /// @param expected expected string representation on stack after evaluation
+    void testInteger(std::string expected, uint32_t value) {
+
+        clearStack();
+
+        ASSERT_NO_THROW(t_.reset(new TokenInteger(value)));
+
+        // The universe (v4 or v6) shouldn't have any impact on this,
+        // but let's check it anyway.
+        evaluate(Option::V4, expected);
+
+        clearStack(false);
+        evaluate(Option::V6, expected);
+
+        clearStack(true);
+    }
 };
 
 // This tests the toBool() conversions
@@ -2721,4 +2739,14 @@ TEST_F(TokenTest, vendorClass6DataIndex) {
     EXPECT_TRUE(checkFile());
 }
 
+// Checks if various values can be represented as integer tokens
+TEST_F(TokenTest, integer) {
+    testInteger(encode(0), 0);
+    testInteger(encode(6), 6);
+    testInteger(encode(255), 255);
+    testInteger(encode(256), 256);
+    testInteger(encode(1410), 1410);
+    testInteger(encode(4294967295), 4294967295);
+}
+
 };

+ 6 - 0
src/lib/eval/token.cc

@@ -6,6 +6,7 @@
 
 #include <eval/token.h>
 #include <eval/eval_log.h>
+#include <eval/eval_context.h>
 #include <util/encode/hex.h>
 #include <util/io_utilities.h>
 #include <asiolink/io_address.h>
@@ -879,3 +880,8 @@ void TokenVendorClass::evaluate(Pkt& pkt, ValueStack& values) {
         isc_throw(EvalTypeError, "Invalid field specified." << field_);
     }
 }
+
+TokenInteger::TokenInteger(const uint32_t value)
+    :TokenString(EvalContext::fromUint32(value)), int_value_(value) {
+
+}

+ 29 - 0
src/lib/eval/token.h

@@ -155,6 +155,35 @@ protected:
     std::string value_; ///< Constant value
 };
 
+/// @brief Token representing an unsigned 32 bits integer
+///
+/// For performance reasons, the constant integer value is converted to string
+/// just once (in constructor). Afterwards, this effectively works as constant
+/// 4 bytes long string. Hence this class is derived from TokenString and
+/// does not even need its own evaluate() method.
+class TokenInteger : public TokenString {
+public:
+    /// @brief Integer value set during construction.
+    ///
+    /// The value is converted to string and stored in value_ provided by the
+    /// base class.
+    ///
+    /// @param value integer value to be stored.
+    TokenInteger(const uint32_t value);
+
+    /// @brief Returns integer value
+    ///
+    /// Used in tests only.
+    ///
+    /// @return integer value
+    uint32_t getInteger() const {
+        return (int_value_);
+    }
+
+protected:
+    uint32_t int_value_; ///< value as integer (stored for testing only)
+};
+
 /// @brief Token representing an IP address as a constant string
 ///
 /// This token holds the value of an IP address as a constant string,