From: Tomek Mrugalski Date: Mon, 26 Oct 2015 19:11:22 +0000 (+0100) Subject: [4081] Initial Token implementation added. X-Git-Tag: trac4106_base~1^2~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a7923efa9107b579431a06d67212b0364160c4e0;p=thirdparty%2Fkea.git [4081] Initial Token implementation added. --- diff --git a/configure.ac b/configure.ac index 6207f9948a..934b360f21 100755 --- a/configure.ac +++ b/configure.ac @@ -1479,6 +1479,8 @@ AC_CONFIG_FILES([compatcheck/Makefile src/lib/util/threads/Makefile src/lib/util/threads/tests/Makefile src/lib/util/unittests/Makefile + src/lib/eval/Makefile + src/lib/eval/tests/Makefile tools/Makefile tools/path_replacer.sh ]) diff --git a/src/lib/eval/Makefile.am b/src/lib/eval/Makefile.am new file mode 100644 index 0000000000..e6893a129e --- /dev/null +++ b/src/lib/eval/Makefile.am @@ -0,0 +1,26 @@ +SUBDIRS = . tests + +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +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 += $(CRYPTO_LDFLAGS) + +EXTRA_DIST = eval.dox + +# Define rule to build logging source files from message file +expr_messages.h expr_messages.cc: s-messages + +s-messages: expr_messages.mes + $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/expr/expr_messages.mes + +CLEANFILES = expr_messages.h expr_messages.cc diff --git a/src/lib/eval/tests/Makefile.am b/src/lib/eval/tests/Makefile.am new file mode 100644 index 0000000000..4e8b1b0781 --- /dev/null +++ b/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) +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) diff --git a/src/lib/eval/tests/main.cc b/src/lib/eval/tests/main.cc new file mode 100644 index 0000000000..45044836fe --- /dev/null +++ b/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 + +#include + +int +main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + isc::log::initLogger(); + + int result = RUN_ALL_TESTS(); + + return (result); +} diff --git a/src/lib/eval/tests/token_unittest.cc b/src/lib/eval/tests/token_unittest.cc new file mode 100644 index 0000000000..670d637a6e --- /dev/null +++ b/src/lib/eval/tests/token_unittest.cc @@ -0,0 +1,171 @@ +// 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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 a packet and properly store the option's value. +TEST_F(TokenTest, optionString) { + 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 == 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()); +} + +}; diff --git a/src/lib/eval/token.cc b/src/lib/eval/token.cc new file mode 100644 index 0000000000..ed0bc33552 --- /dev/null +++ b/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 +#include + +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, 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"); +} diff --git a/src/lib/eval/token.h b/src/lib/eval/token.h new file mode 100644 index 0000000000..5f5038bd09 --- /dev/null +++ b/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 +#include +#include + +namespace isc { +namespace dhcp { + +class Token; + +/// @brief Pointer to a single Token +typedef boost::shared_ptr 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 Expression; + +/// Evaluated values are stored as a stack of strings +typedef std::stack 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 previous + /// evaluated values. Specific implementations may ignore the packet altogether + /// and just put its 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 value - 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(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 the value of specified +/// option. If the option is not found, an empty string ("") is returned. +class TokenOption : public Token { +public: + /// @brief Constructor that takes option code as 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 not used + /// @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 simple string comparison and sets 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