Browse Source

[master] Merge branch 'trac4081' (src/lib/eval added)

Tomek Mrugalski 9 years ago
parent
commit
28d818a26a

+ 2 - 0
configure.ac

@@ -1479,6 +1479,8 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/lib/util/threads/Makefile
                  src/lib/util/threads/Makefile
                  src/lib/util/threads/tests/Makefile
                  src/lib/util/threads/tests/Makefile
                  src/lib/util/unittests/Makefile
                  src/lib/util/unittests/Makefile
+                 src/lib/eval/Makefile
+                 src/lib/eval/tests/Makefile
                  tools/Makefile
                  tools/Makefile
                  tools/path_replacer.sh
                  tools/path_replacer.sh
 ])
 ])

+ 1 - 0
doc/Doxyfile

@@ -661,6 +661,7 @@ INPUT                  = ../src/bin/d2 \
                          ../src/lib/dhcpsrv \
                          ../src/lib/dhcpsrv \
                          ../src/lib/dhcpsrv/parsers \
                          ../src/lib/dhcpsrv/parsers \
                          ../src/lib/dns \
                          ../src/lib/dns \
+                         ../src/lib/eval \
                          ../src/lib/exceptions \
                          ../src/lib/exceptions \
                          ../src/lib/hooks \
                          ../src/lib/hooks \
                          ../src/lib/log \
                          ../src/lib/log \

+ 1 - 0
doc/devel/mainpage.dox

@@ -104,6 +104,7 @@
  *   - @subpage leaseReclamationRoutine
  *   - @subpage leaseReclamationRoutine
  * - @subpage libdhcp_ddns
  * - @subpage libdhcp_ddns
  * - @subpage dhcpDatabaseBackends
  * - @subpage dhcpDatabaseBackends
+ * - @subpage dhcpEval
  * - @subpage configBackend
  * - @subpage configBackend
  *   - @subpage configBackendMotivation
  *   - @subpage configBackendMotivation
  *   - @subpage configBackendJSONDesign
  *   - @subpage configBackendJSONDesign

+ 1 - 1
src/lib/Makefile.am

@@ -1,3 +1,3 @@
 # The following build order must be maintained.
 # The following build order must be maintained.
 SUBDIRS = exceptions util log hooks cryptolink dns cc asiolink dhcp config stats \
 SUBDIRS = exceptions util log hooks cryptolink dns cc asiolink dhcp config stats \
-          asiodns testutils dhcp_ddns dhcpsrv cfgrpt
+          asiodns testutils dhcp_ddns dhcpsrv cfgrpt eval

+ 7 - 0
src/lib/dhcp/option.cc

@@ -214,6 +214,13 @@ std::string Option::toText(int indent) {
 }
 }
 
 
 std::string
 std::string
+Option::toString() {
+    /// @todo: Implement actual conversion in derived classes.
+    return (toText(0));
+}
+
+
+std::string
 Option::headerToText(const int indent, const std::string& type_name) {
 Option::headerToText(const int indent, const std::string& type_name) {
     std::stringstream output;
     std::stringstream output;
     for (int i = 0; i < indent; i++)
     for (int i = 0; i < indent; i++)

+ 8 - 0
src/lib/dhcp/option.h

@@ -206,6 +206,14 @@ public:
     /// @return string with text representation.
     /// @return string with text representation.
     virtual std::string toText(int indent = 0);
     virtual std::string toText(int indent = 0);
 
 
+    /// @brief Returns string representation of the value
+    ///
+    /// This is terse repesentation used in cases where client classification
+    /// refers to a specific option.
+    ///
+    /// @return string that represents the value of the option.
+    virtual std::string toString();
+
     /// Returns option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
     /// Returns option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
     ///
     ///
     /// @return option type
     /// @return option type

+ 5 - 0
src/lib/dhcp/option_string.cc

@@ -92,5 +92,10 @@ OptionString::toText(int indent) {
     return (output.str());
     return (output.str());
 }
 }
 
 
+std::string
+OptionString::toString() {
+    return (getValue());
+}
+
 } // end of isc::dhcp namespace
 } // end of isc::dhcp namespace
 } // end of isc namespace
 } // end of isc namespace

+ 6 - 0
src/lib/dhcp/option_string.h

@@ -110,6 +110,12 @@ public:
     ///
     ///
     /// @return Option information in the textual format.
     /// @return Option information in the textual format.
     virtual std::string toText(int indent = 0);
     virtual std::string toText(int indent = 0);
+
+    /// @brief Returns actual value of the option in string format.
+    ///
+    /// This method is used in client classification.
+    /// @return Content of the option.
+    virtual std::string toString();
 };
 };
 
 
 /// Pointer to the OptionString object.
 /// Pointer to the OptionString object.

+ 2 - 0
src/lib/eval/.gitignore

@@ -0,0 +1,2 @@
+eval_messages.cc
+eval_messages.h

+ 42 - 0
src/lib/eval/Makefile.am

@@ -0,0 +1,42 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+lib_LTLIBRARIES = libkea-eval.la
+libkea_eval_la_SOURCES  =
+libkea_eval_la_SOURCES += token.cc token.h
+
+libkea_eval_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_eval_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_eval_la_LIBADD   = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_eval_la_LIBADD  += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libkea_eval_la_LDFLAGS  = -no-undefined -version-info 3:0:0
+libkea_eval_la_LDFLAGS += $(LOG4CPLUS_LIBS) $(CRYPTO_LDFLAGS)
+
+EXTRA_DIST  = eval.dox
+EXTRA_DIST += eval_messages.mes
+
+# Define rule to build logging source files from message file
+eval_messages.h eval_messages.cc: s-messages
+
+s-messages: eval_messages.mes
+	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/eval/eval_messages.mes
+
+# Tell Automake that the eval_messages.{cc,h} source files are created in the
+# build process, so it must create these before doing anything else. Although
+# they are a dependency of the library (so will be created from the
+# message file anyway), there is no guarantee as to exactly _when_ in the build
+# they will be created.  As the .h file is included in other sources file (so
+# must be present when they are compiled), the safest option is to create it
+# first.
+BUILT_SOURCES = eval_messages.h eval_messages.cc
+
+CLEANFILES = eval_messages.h eval_messages.cc

+ 20 - 0
src/lib/eval/eval.dox

@@ -0,0 +1,20 @@
+// Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/**
+  @page dhcpEval Expression evaluation (client classification)
+
+  @todo: Document how the expression evaluation is implemented.
+
+ */

+ 20 - 0
src/lib/eval/eval_messages.mes

@@ -0,0 +1,20 @@
+# Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::dhcp
+
+% EVAL_RESULT Expression %1 evaluated to %2
+This debug message indicates that the expression has been evaluated
+to said value. This message is mostly useful during debugging of the
+client classification expressions.

+ 47 - 0
src/lib/eval/tests/Makefile.am

@@ -0,0 +1,47 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = \
+	$(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+
+TESTS += libeval_unittests
+
+libeval_unittests_SOURCES  = token_unittest.cc main.cc
+libeval_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+libeval_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libeval_unittests_LDFLAGS  = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+libeval_unittests_LDADD    = $(top_builddir)/src/lib/eval/libkea-eval.la
+libeval_unittests_LDADD   += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libeval_unittests_LDADD   += $(top_builddir)/src/lib/log/libkea-log.la
+libeval_unittests_LDADD   += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libeval_unittests_LDADD   += $(CRYPTO_LIBS) $(LOG4CPLUS_LIBS)
+libeval_unittests_LDADD   += $(BOOST_LIBS) $(GTEST_LDADD)
+
+if USE_CLANGPP
+# This is to workaround unused variables tcout and tcerr in
+# log4cplus's streams.h and unused parameters from some of the
+# Boost headers.
+libeval_unittests_CXXFLAGS += -Wno-unused-variable -Wno-unused-parameter
+endif
+
+endif
+
+noinst_PROGRAMS = $(TESTS)

+ 27 - 0
src/lib/eval/tests/main.cc

@@ -0,0 +1,27 @@
+// Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/logger_support.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    isc::log::initLogger();
+
+    int result = RUN_ALL_TESTS();
+
+    return (result);
+}

+ 199 - 0
src/lib/eval/tests/token_unittest.cc

@@ -0,0 +1,199 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <eval/token.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option_string.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test fixture for testing Tokens.
+///
+/// This class provides several convenience objects to be used during testing
+/// of the Token family of classes.
+class TokenTest : public ::testing::Test {
+public:
+
+    /// @brief Initializes Pkt4,Pkt6 and options that can be useful for
+    ///        evaluation tests.
+    TokenTest() {
+        pkt4_.reset(new Pkt4(DHCPDISCOVER, 12345));
+        pkt6_.reset(new Pkt6(DHCPV6_SOLICIT, 12345));
+
+        // Add options with easily identifiable strings in them
+        option_str4_.reset(new OptionString(Option::V4, 100, "hundred4"));
+        option_str6_.reset(new OptionString(Option::V6, 100, "hundred6"));
+
+        pkt4_->addOption(option_str4_);
+        pkt6_->addOption(option_str6_);
+    }
+
+    TokenPtr t_; ///< Just a convenience pointer
+
+    ValueStack values_; ///< evaluated values will be stored here
+
+    Pkt4Ptr pkt4_; ///< A stub DHCPv4 packet
+    Pkt6Ptr pkt6_; ///< A stub DHCPv6 packet
+
+    OptionPtr option_str4_; ///< A string option for DHCPv4
+    OptionPtr option_str6_; ///< A string option for DHCPv6
+
+    /// @todo: Add more option types here
+};
+
+// This simple test checks that a TokenString, representing a constant string,
+// can be used in Pkt4 evaluation. (The actual packet is not used)
+TEST_F(TokenTest, string4) {
+
+    // Store constant string "foo" in the TokenString object.
+    ASSERT_NO_THROW(t_.reset(new TokenString("foo")));
+
+    // Make sure that the token can be evaluated without exceptions.
+    ASSERT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+    // Check that the evaluation put its value on the values stack.
+    ASSERT_EQ(1, values_.size());
+    EXPECT_EQ("foo", values_.top());
+}
+
+// This simple test checks that a TokenString, representing a constant string,
+// can be used in Pkt6 evaluation. (The actual packet is not used)
+TEST_F(TokenTest, string6) {
+
+    // Store constant string "foo" in the TokenString object.
+    ASSERT_NO_THROW(t_.reset(new TokenString("foo")));
+
+    // Make sure that the token can be evaluated without exceptions.
+    ASSERT_NO_THROW(t_->evaluate(*pkt6_, values_));
+
+    // Check that the evaluation put its value on the values stack.
+    ASSERT_EQ(1, values_.size());
+    EXPECT_EQ("foo", values_.top());
+}
+
+// This test checks if a token representing an option value is able to extract
+// the option from an IPv4 packet and properly store the option's value.
+TEST_F(TokenTest, optionString4) {
+    TokenPtr found;
+    TokenPtr not_found;
+
+    // The packets we use have option 100 with a string in them.
+    ASSERT_NO_THROW(found.reset(new TokenOption(100)));
+    ASSERT_NO_THROW(not_found.reset(new TokenOption(101)));
+
+    // This should evaluate to the content of the option 100 (i.e. "hundred4")
+    ASSERT_NO_THROW(found->evaluate(*pkt4_, values_));
+
+    // This should evaluate to "" as there is no option 101.
+    ASSERT_NO_THROW(not_found->evaluate(*pkt4_, values_));
+
+    // There should be 2 values evaluated.
+    ASSERT_EQ(2, values_.size());
+
+    // This is a stack, so the pop order is inversed. We should get the empty
+    // string first.
+    EXPECT_EQ("", values_.top());
+    values_.pop();
+
+    // Then the content of the option 100.
+    EXPECT_EQ("hundred4", values_.top());
+}
+
+// This test checks if a token representing an option value is able to extract
+// the option from an IPv6 packet and properly store the option's value.
+TEST_F(TokenTest, optionString6) {
+    TokenPtr found;
+    TokenPtr not_found;
+
+    // The packets we use have option 100 with a string in them.
+    ASSERT_NO_THROW(found.reset(new TokenOption(100)));
+    ASSERT_NO_THROW(not_found.reset(new TokenOption(101)));
+
+    // This should evaluate to the content of the option 100 (i.e. "hundred6")
+    ASSERT_NO_THROW(found->evaluate(*pkt6_, values_));
+
+    // This should evaluate to "" as there is no option 101.
+    ASSERT_NO_THROW(not_found->evaluate(*pkt6_, values_));
+
+    // There should be 2 values evaluated.
+    ASSERT_EQ(2, values_.size());
+
+    // This is a stack, so the pop order is inversed. We should get the empty
+    // string first.
+    EXPECT_EQ("", values_.top());
+    values_.pop();
+
+    // Then the content of the option 100.
+    EXPECT_EQ("hundred6", values_.top());
+}
+
+// This test checks if a token representing an == operator is able to
+// compare two values (with incorrectly built stack).
+TEST_F(TokenTest, optionEqualInvalid) {
+
+    ASSERT_NO_THROW(t_.reset(new TokenEqual()));
+
+    // CASE 1: There's not enough values on the stack. == is an operator that
+    // takes two parameters. There are 0 on the stack.
+    EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+    // CASE 2: One value is still not enough.
+    values_.push("foo");
+    EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+}
+
+// This test checks if a token representing an == operator is able to
+// compare two different values.
+TEST_F(TokenTest, optionEqualFalse) {
+
+    ASSERT_NO_THROW(t_.reset(new TokenEqual()));
+
+    values_.push("foo");
+    values_.push("bar");
+    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+    // After evaluation there should be a single value that represents
+    // result of "foo" == "bar" comparision.
+    ASSERT_EQ(1, values_.size());
+    EXPECT_EQ("false", values_.top());
+}
+
+// This test checks if a token representing an == operator is able to
+// compare two identical values.
+TEST_F(TokenTest, optionEqualTrue) {
+
+    ASSERT_NO_THROW(t_.reset(new TokenEqual()));
+
+    values_.push("foo");
+    values_.push("foo");
+    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+    // After evaluation there should be a single value that represents
+    // result of "foo" == "foo" comparision.
+    ASSERT_EQ(1, values_.size());
+    EXPECT_EQ("true", values_.top());
+}
+
+};

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

@@ -0,0 +1,55 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <eval/token.h>
+#include <string>
+
+using namespace isc::dhcp;
+using namespace std;
+
+void
+TokenString::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
+    // Literals only push, nothing to pop
+    values.push(value_);
+}
+
+void
+TokenOption::evaluate(const Pkt& pkt, ValueStack& values) {
+    OptionPtr opt = pkt.getOption(option_code_);
+    if (opt) {
+        values.push(opt->toString());
+    } else {
+        // Option not found, push empty string
+        values.push("");
+    }
+}
+
+void
+TokenEqual::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
+
+    if (values.size() < 2) {
+        isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
+                  "2 values for == operator, got " << values.size());
+    }
+
+    string op1 = values.top();
+    values.pop();
+    string op2 = values.top();
+    values.pop(); // Dammit, std::stack interface is awkward.
+
+    if (op1 == op2)
+        values.push("true");
+    else
+        values.push("false");
+}

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

@@ -0,0 +1,164 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TOKEN_H
+#define TOKEN_H
+
+#include <exceptions/exceptions.h>
+#include <dhcp/pkt.h>
+#include <stack>
+
+namespace isc {
+namespace dhcp {
+
+class Token;
+
+/// @brief Pointer to a single Token
+typedef boost::shared_ptr<Token> TokenPtr;
+
+/// This is a structure that holds an expression converted to RPN
+///
+/// For example expression: option[123] == 'foo' will be converted to:
+/// [0] = option[123] (TokenOption object)
+/// [1] = 'foo' (TokenString object)
+/// [2] = == operator (TokenEqual object)
+typedef std::vector<TokenPtr> Expression;
+
+/// Evaluated values are stored as a stack of strings
+typedef std::stack<std::string> ValueStack;
+
+/// @brief EvalStackError is thrown when more or less parameters are on the
+///        stack than expected.
+class EvalBadStack : public Exception {
+public:
+    EvalBadStack(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Base class for all tokens
+///
+/// It provides an interface for all tokens and storage for string representation
+/// (all tokens evaluate to string).
+///
+/// This class represents a single token. Examples of a token are:
+/// - "foo" (a constant string)
+/// - option[123] (a token that extracts value of option 123)
+/// - == (an operator that compares two other tokens)
+/// - substring(a,b,c) (an operator that takes three arguments: a string,
+///   first and last character)
+class Token {
+public:
+
+    /// @brief This is a generic method for evaluating a packet.
+    ///
+    /// We need to pass the packet being evaluated and possibly previously
+    /// evaluated values. Specific implementations may ignore the packet altogether
+    /// and just put their own value on the stack (constant tokens), look at the
+    /// packet and put some data extracted from it on the stack (option tokens),
+    /// or pop arguments from the stack and put back the result (operators).
+    ///
+    /// The parameters passed will be:
+    ///
+    /// @param pkt - packet being classified
+    /// @param values - stack of values with previously evaluated tokens
+    virtual void evaluate(const Pkt& pkt, ValueStack& values) = 0;
+
+    /// @brief Virtual destructor
+    virtual ~Token() {}
+};
+
+/// @brief Token representing a constant string
+///
+/// This token holds value of a constant string, e.g. it represents
+/// "MSFT" in expression option[vendor-class] == "MSFT"
+class TokenString : public Token {
+public:
+    /// Value is set during token construction.
+    ///
+    /// @param str constant string to be represented.
+    TokenString(const std::string& str)
+        :value_(str){
+    }
+
+    /// @brief Token evaluation (puts value of the constant string on the stack)
+    ///
+    /// @param pkt (ignored)
+    /// @param values (represented string will be pushed here)
+    void evaluate(const Pkt& pkt, ValueStack& values);
+
+protected:
+    std::string value_; ///< Constant value
+};
+
+/// @brief Token that represents a value of an option
+///
+/// This represents a reference to a given option, e.g. in the expression
+/// option[vendor-class] == "MSFT", it represents option[vendor-class]
+///
+/// During the evaluation it tries to extract the value of the specified
+/// option. If the option is not found, an empty string ("") is returned.
+class TokenOption : public Token {
+public:
+    /// @brief Constructor that takes an option code as a parameter
+    /// @param option_code code of the option
+    ///
+    /// Note: There is no constructor that takes option_name, as it would
+    /// introduce complex dependency of the libkea-eval on libdhcpsrv.
+    ///
+    /// @param option_code code of the option to be represented.
+    TokenOption(uint16_t option_code)
+        :option_code_(option_code) {}
+
+    /// @brief Evaluates the values of the option
+    ///
+    /// This token represents a value of the option, so this method attempts
+    /// to extract the option from the packet and put its value on the stack.
+    /// If the option is not there, an empty string ("") is put on the stack.
+    ///
+    /// @param pkt specified option will be extracted from this packet (if present)
+    /// @param values value of the option will be pushed here (or "")
+    void evaluate(const Pkt& pkt, ValueStack& values);
+
+private:
+    uint16_t option_code_; ///< code of the option to be extracted
+};
+
+/// @brief Token that represents equality operator (compares two other tokens)
+///
+/// For example in the expression option[vendor-class] == "MSFT" this token
+/// represents the equal (==) sign.
+class TokenEqual : public Token {
+public:
+    /// @brief Constructor (does nothing)
+    TokenEqual() {}
+
+    /// @brief Compare two values.
+    ///
+    /// Evaluation does not use packet information, but rather consumes the last
+    /// two parameters. It does a simple string comparison and sets the value to
+    /// either "true" or "false". It requires at least two parameters to be
+    /// present on stack.
+    ///
+    /// @throw EvalBadStack if there's less than 2 values on stack
+    ///
+    /// @brief pkt (unused)
+    /// @brief values - stack of values (2 arguments will be poped, 1 result
+    ///        will be pushed)
+    void evaluate(const Pkt& pkt, ValueStack& values);
+};
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif