Browse Source

[4271] Vendor and Vendor-class tokens implemented.

Tomek Mrugalski 8 years ago
parent
commit
ae2d315369

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

@@ -85,6 +85,11 @@ parser: lexer.cc location.hh position.hh stack.hh parser.cc parser.h
 	@echo "Flex/bison files regenerated"
 
 # --- Flex/Bison stuff below --------------------------------------------------
+# When debugging grammar issues, it's useful to add -v to bison parameters.
+# bison will generate parser.output file that explains the whole grammar.
+# It can be used to manually follow what's going on in the parser.
+# This is especially useful if yydebug_ is set to 1 as that variable
+# will cause parser to print out its internal state.
 location.hh position.hh stack.hh parser.cc parser.h: parser.yy
 	$(YACC) --defines=parser.h -o parser.cc parser.yy
 

+ 38 - 7
src/lib/eval/eval_context.cc

@@ -13,6 +13,7 @@
 #include <exceptions/exceptions.h>
 #include <boost/lexical_cast.hpp>
 #include <fstream>
+#include <limits>
 
 EvalContext::EvalContext(const Option::Universe& option_universe)
   : trace_scanning_(false), trace_parsing_(false),
@@ -97,14 +98,9 @@ uint8_t
 EvalContext::convertNestLevelNumber(const std::string& nest_level,
                                     const isc::eval::location& loc)
 {
-    int n = 0;
-    try {
-        n  = boost::lexical_cast<int>(nest_level);
-    } catch (const boost::bad_lexical_cast &) {
-        error(loc, "Nest level has invalid value in " + nest_level);
-    }
+    uint8_t n = convertUint8(nest_level, loc);
     if (option_universe_ == Option::V6) {
-        if (n < 0 || n >= HOP_COUNT_LIMIT) {
+        if (n >= HOP_COUNT_LIMIT) {
             error(loc, "Nest level has invalid value in "
                       + nest_level + ". Allowed range: 0..31");
 	}
@@ -112,9 +108,44 @@ EvalContext::convertNestLevelNumber(const std::string& nest_level,
         error(loc, "Nest level invalid for DHCPv4 packets");
     }
 
+    return (n);
+}
+
+uint8_t
+EvalContext::convertUint8(const std::string& number,
+                          const isc::eval::location& loc)
+{
+    int n = 0;
+    try {
+        n  = boost::lexical_cast<int>(number);
+    } catch (const boost::bad_lexical_cast &) {
+        error(loc, "Invalid integer value in " + number);
+    }
+    if (n < 0 || n >= std::numeric_limits<uint8_t>::max()) {
+        error(loc, "Invalid value in "
+              + number + ". Allowed range: 0..255");
+    }
+
     return (static_cast<uint8_t>(n));
 }
 
+uint32_t
+EvalContext::convertUint32(const std::string& number,
+                          const isc::eval::location& loc)
+{
+    uint64_t n = 0;
+    try {
+        n  = boost::lexical_cast<uint64_t>(number);
+    } catch (const boost::bad_lexical_cast &) {
+        error(loc, "Invalid value in " + number);
+    }
+    if (n >= std::numeric_limits<uint32_t>::max()) {
+        error(loc, "Invalid value in "
+              + number + ". Allowed range: 0..4294967295");
+    }
+
+    return (static_cast<uint32_t>(n));
+}
 
 void
 EvalContext::fatal (const std::string& what)

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

@@ -96,11 +96,29 @@ public:
     ///
     /// @param option_name the option name
     /// @param loc the location of the token
-    /// @result the option code
+    /// @return the option code
     /// @throw calls the syntax error function if the name cannot be resolved
     uint16_t convertOptionName(const std::string& option_name,
                                const isc::eval::location& loc);
 
+    /// @brief Attempts to convert string to unsinged 32bit integer
+    ///
+    /// @param number string to be converted
+    /// @param loc the location of the token
+    /// @return the option code
+    /// @throw EvalParseError if conversion fails or the value is out of range.
+    uint32_t convertUint32(const std::string& number,
+                           const isc::eval::location& loc);
+
+    /// @brief Attempts to convert string to unsinged 8bit integer
+    ///
+    /// @param number string to be converted
+    /// @param loc the location of the token
+    /// @return the option code
+    /// @throw EvalParseError if conversion fails or the value is out of range.
+    uint8_t convertUint8(const std::string& number,
+                         const isc::eval::location& loc);
+
     /// @brief Nest level conversion
     ///
     /// @param nest_level a string representing the integer nesting level

+ 5 - 0
src/lib/eval/lexer.ll

@@ -169,10 +169,15 @@ addr6 [0-9a-fA-F]*\:[0-9a-fA-F]*\:[0-9a-fA-F:.]*
 "["         return isc::eval::EvalParser::make_LBRACKET(loc);
 "]"         return isc::eval::EvalParser::make_RBRACKET(loc);
 ","         return isc::eval::EvalParser::make_COMA(loc);
+"*"         return isc::eval::EvalParser::make_ANY(loc);
 
 "pkt6"      return isc::eval::EvalParser::make_PKT6(loc);
 "msgtype"   return isc::eval::EvalParser::make_MSGTYPE(loc);
 "transid"   return isc::eval::EvalParser::make_TRANSID(loc);
+"vendor"    return isc::eval::EvalParser::make_VENDOR(loc);
+"vendor-class" return isc::eval::EvalParser::make_VENDOR_CLASS(loc);
+"data"      return isc::eval::EvalParser::make_DATA(loc);
+"enterprise" return isc::eval::EvalParser::make_ENTERPRISE(loc);
 
 .          driver.error (loc, "Invalid character: " + std::string(yytext));
 <<EOF>>    return isc::eval::EvalParser::make_END(loc);

+ 98 - 3
src/lib/eval/parser.yy

@@ -69,6 +69,11 @@ using namespace isc::eval;
   PKT6 "pkt6"
   MSGTYPE "msgtype"
   TRANSID "transid"
+  VENDOR_CLASS "vendor-class"
+  VENDOR "vendor"
+  ANY "*"
+  DATA "data"
+  ENTERPRISE "enterprise"
 ;
 
 %token <std::string> STRING "constant string"
@@ -78,6 +83,7 @@ using namespace isc::eval;
 %token <std::string> IP_ADDRESS "ip address"
 
 %type <uint16_t> option_code
+%type <uint32_t> enterprise_id
 %type <TokenOption::RepresentationType> option_repr_type
 %type <TokenRelay6Field::FieldType> relay6_field
 %type <uint8_t> nest_level
@@ -160,7 +166,35 @@ bool_expr : "(" bool_expr ")"
                         error(@1, "relay6 can only be used in DHCPv6.");
                     }
                 }
-          ;
+| VENDOR_CLASS "[" enterprise_id "]" "." EXISTS
+{
+    // Expression: vendor-class[1234].exists
+    //
+    // This token will find option 124 (DHCPv4) or 16 (DHCPv6), and will check
+    // if enterprise-id equals specified value.
+    TokenPtr exist(new TokenVendorClass(ctx.getUniverse(), $3, TokenOption::EXISTS));
+    ctx.expression.push_back(exist);
+}
+| VENDOR "[" enterprise_id "]" "." EXISTS
+{
+    // Expression: vendor[1234].exists
+    //
+    // This token will find option 125 (DHCPv4) or 17 (DHCPv6), and will check
+    // if enterprise-id equals specified value.
+    TokenPtr exist(new TokenVendor(ctx.getUniverse(), $3, TokenOption::EXISTS));
+    ctx.expression.push_back(exist);
+
+}
+| VENDOR "[" enterprise_id "]" "." OPTION "[" option_code "]" "." EXISTS
+{
+    // Expression vendor[1234].option[123].exists
+    //
+    // This token will check if specified vendor option exists, has specified
+    // enterprise-id and if has specified suboption.
+    TokenPtr exist(new TokenVendor(ctx.getUniverse(), $3, TokenOption::EXISTS, $8));
+    ctx.expression.push_back(exist);
+}
+;
 
 string_expr : STRING
                   {
@@ -253,7 +287,59 @@ string_expr : STRING
                       TokenPtr conc(new TokenConcat());
                       ctx.expression.push_back(conc);
                   }
-            ;
+| VENDOR "." ENTERPRISE
+{
+    // expression: vendor.enterprise
+    //
+    // This token will return enterprise-id number of received vendor option.
+    TokenPtr vendor(new TokenVendor(ctx.getUniverse(), 0, TokenVendor::ENTERPRISE_ID));
+    ctx.expression.push_back(vendor);
+}
+| VENDOR_CLASS "." ENTERPRISE
+{
+    // expression: vendor-class.enterprise
+    //
+    // This token will return enterprise-id number of received vendor class option.
+    TokenPtr vendor(new TokenVendorClass(ctx.getUniverse(), 0,
+                                         TokenVendor::ENTERPRISE_ID));
+    ctx.expression.push_back(vendor);
+}
+| VENDOR "[" enterprise_id "]" "." OPTION "[" option_code "]" "." option_repr_type
+{
+    // expression: vendor[1234].option[56].exists
+    // expression: vendor[1234].option[56].hex
+    //
+    // This token will search for vendor option with specified enterprise-id.
+    // If found, will search for specified suboption and finally will return
+    // if it exists ('exists') or its content ('hex')
+    TokenPtr opt(new TokenVendor(ctx.getUniverse(), $3, $11, $8));
+    ctx.expression.push_back(opt);
+}
+| VENDOR_CLASS "[" enterprise_id "]" "." DATA
+{
+    // expression: vendor-class[1234].data
+    //
+    // Vendor class option does not have suboptions, but chunks of data (typically 1,
+    // but the option structure allows multiple of them). If chunk offset is not
+    // specified, we assume the first (0th) is requested.
+    TokenPtr vendor_class(new TokenVendorClass(ctx.getUniverse(), $3,
+                                               TokenVendor::DATA, 0));
+    ctx.expression.push_back(vendor_class);
+}
+| VENDOR_CLASS "[" enterprise_id "]" "." DATA "[" INTEGER "]"
+{
+    // expression: vendor-class[1234].data[5]
+    //
+    // Vendor class option does not have suboptions, but chunks of data (typically 1,
+    // but the option structure allows multiple of them). This syntax specifies
+    // which data chunk (tuple) we want.
+    uint8_t index = ctx.convertUint8($8, @8);
+    TokenPtr vendor_class(new TokenVendorClass(ctx.getUniverse(), $3,
+                                               TokenVendor::DATA, index));
+    ctx.expression.push_back(vendor_class);
+}
+
+;
 
 option_code : INTEGER
                  {
@@ -275,6 +361,15 @@ option_repr_type : TEXT
                       }
                  ;
 
+enterprise_id : INTEGER
+{
+    $$ = ctx.convertUint32($1, @1);
+}
+| "*"
+{
+    $$ = 0;
+}
+
 pkt4_field : CHADDR
                 {
                     $$ = TokenPkt4::CHADDR;
@@ -330,7 +425,7 @@ relay6_field : PEERADDR { $$ = TokenRelay6Field::PEERADDR; }
 
 nest_level : INTEGER
                  {
-		 $$ = ctx.convertNestLevelNumber($1, @1);
+                 $$ = ctx.convertNestLevelNumber($1, @1);
                  }
                  // Eventually we may add strings to handle different
                  // ways of choosing from which relay we want to extract

+ 273 - 1
src/lib/eval/tests/context_unittest.cc

@@ -372,7 +372,188 @@ public:
         checkTokenPkt6(eval.expression.at(0), exp_type);
     }
 
-    Option::Universe universe_;
+
+    /// @brief Checks if the given token is TokenVendor and has expected characteristics
+    /// @param token token to be checked
+    /// @param exp_vendor_id expected vendor-id (aka enterprise number)
+    /// @param exp_repr expected representation (either 'exists' or 'hex')
+    /// @param exp_option_code expected option code (ignored if 0)
+    void checkTokenVendor(const TokenPtr& token, uint32_t exp_vendor_id,
+                          uint16_t exp_option_code,
+                          TokenOption::RepresentationType exp_repr) {
+        ASSERT_TRUE(token);
+
+        boost::shared_ptr<TokenVendor> vendor =
+            boost::dynamic_pointer_cast<TokenVendor>(token);
+
+        ASSERT_TRUE(vendor);
+
+        EXPECT_EQ(exp_vendor_id, vendor->getVendorId());
+        EXPECT_EQ(exp_repr, vendor->getRepresentation());
+        EXPECT_EQ(exp_option_code, vendor->getCode());
+    }
+
+    /// @brief Tests if specified token vendor expression can be parsed
+    ///
+    /// This test assumes the first token will be token vendor. Any additional
+    /// tokens are ignored. Tests experssions:
+    /// vendor[1234].option[234].hex
+    /// vendor[1234].option[234].exists
+    ///
+    /// @param expr expression to be parsed
+    /// @param u universe (V4 or V6)
+    /// @param vendor_id expected vendor-id (aka enterprise number)
+    /// @param option_code expected option code (ignored if 0)
+    /// @param expected_repr expected representation (either 'exists' or 'hex')
+    void testVendor(std::string expr, Option::Universe u, uint32_t vendor_id,
+                    uint16_t option_code, TokenOption::RepresentationType expected_repr) {
+        EvalContext eval(u);
+
+        EXPECT_NO_THROW(parsed_ = eval.parseString(expr));
+        EXPECT_TRUE(parsed_);
+
+        // We need at least one token, we will evaluate the first one.
+        ASSERT_FALSE(eval.expression.empty());
+
+        checkTokenVendor(eval.expression.at(0), vendor_id, option_code, expected_repr);
+    }
+
+    /// @brief Checks if token is really a TokenVendor, that the vendor_id was
+    /// stored properly and that it has expected representation
+    ///
+    /// This test is able to handle expressions similar to:
+    /// vendor[4491].option[1].hex
+    /// vendor[4491].option[1].exists
+    /// vendor[4491].exists
+    /// vendor[*].exists
+    ///
+    /// @param expr expression to be parsed
+    /// @param u universe (V4 or V6)
+    /// @param vendor_id expected vendor-id (aka enterprise number)
+    /// @param expected_repr expected representation (either 'exists' or 'hex')
+    void testVendor(std::string expr, Option::Universe u, uint32_t vendor_id,
+                    TokenOption::RepresentationType expected_repr) {
+        testVendor(expr, u, vendor_id, 0, expected_repr);
+    }
+
+    /// @brief Tests if the expression parses into token vendor that returns enterprise-id
+    ///
+    /// This test is able to handle expressions similar to:
+    /// vendor.enterprise
+    ///
+    /// @param expr expression to be parsed
+    /// @param u universe (V4 or V6)
+    void testVendorEnterprise(std::string expr, Option::Universe u) {
+        EvalContext eval(u);
+
+        EXPECT_NO_THROW(parsed_ = eval.parseString(expr));
+        EXPECT_TRUE(parsed_);
+
+        ASSERT_FALSE(eval.expression.empty());
+
+        boost::shared_ptr<TokenVendor> vendor =
+            boost::dynamic_pointer_cast<TokenVendor>(eval.expression.at(0));
+
+        ASSERT_TRUE(vendor);
+        EXPECT_EQ(TokenVendor::ENTERPRISE_ID, vendor->getField());
+    }
+
+    /// @brief This test checks if vendor-class token is correct
+    ///
+    /// This test checks if EXISTS representation is set correctly.
+    /// It covers cases like:
+    /// - vendor-class[4491].exist
+    /// - vendor-class[*].exist
+    ///
+    /// @param expr expression to be parsed
+    /// @param u universe (V4 or V6)
+    /// @param vendor_id expected vendor-id (aka enterprise number)
+    void testVendorClass(std::string expr, Option::Universe u, uint32_t vendor_id) {
+        EvalContext eval(u);
+
+        EXPECT_NO_THROW(parsed_ = eval.parseString(expr));
+        EXPECT_TRUE(parsed_);
+
+        ASSERT_EQ(1, eval.expression.size());
+        checkTokenVendorClass(eval.expression.at(0), vendor_id, 0, TokenOption::EXISTS,
+                              TokenVendor::EXISTS);
+    }
+
+    /// @brief Tests if specified token vendor class expression can be parsed
+    ///
+    /// This test assumes the first token will be token vendor. Any additional
+    /// tokens are ignored. Tests experssions:
+    /// - vendor-class[4491].exists
+    /// - vendor-class[*].exists
+    /// - vendor-class[4491].data
+    /// - vendor-class[4491].data[3]
+    ///
+    /// @param expr expression to be parsed
+    /// @param u universe (V4 or V6)
+    /// @param vendor_id expected vendor-id (aka enterprise number)
+    /// @param index expected data index
+    void testVendorClass(std::string expr, Option::Universe u, uint32_t vendor_id,
+                         uint16_t index) {
+        EvalContext eval(u);
+
+        EXPECT_NO_THROW(parsed_ = eval.parseString(expr));
+        EXPECT_TRUE(parsed_);
+
+        // Make sure there's at least one token
+        ASSERT_FALSE(eval.expression.empty());
+
+        // The first token should be TokenVendorClass, let's take a closer look.
+        checkTokenVendorClass(eval.expression.at(0), vendor_id, index,
+                              TokenOption::HEXADECIMAL, TokenVendor::DATA);
+
+    }
+
+    /// @brief Tests if the expression parses into vendor class token that
+    ///        returns enterprise-id.
+    ///
+    /// This test is able to handle expressions similar to:
+    /// - vendor-class.enterprise
+    ///
+    /// @param expr expression to be parsed
+    /// @param u universe (V4 or V6)
+    void testVendorClassEnterprise(std::string expr, Option::Universe u) {
+        EvalContext eval(u);
+
+        EXPECT_NO_THROW(parsed_ = eval.parseString(expr));
+        EXPECT_TRUE(parsed_);
+
+        // Make sure there's at least one token
+        ASSERT_FALSE(eval.expression.empty());
+
+        // The first token should be TokenVendorClass, let's take a closer look.
+        checkTokenVendorClass(eval.expression.at(0), 0, 0, TokenOption::HEXADECIMAL,
+                              TokenVendor::ENTERPRISE_ID);
+    }
+
+    /// @brief Checks if the given token is TokenVendorClass and has expected characteristics
+    ///
+    /// @param token token to be checked
+    /// @param vendor_id expected vendor-id (aka enterprise number)
+    /// @param index expected index (used for data field only)
+    /// @param repr expected representation (either 'exists' or 'hex')
+    /// @param field expected field (none, enterprise or data)
+    void checkTokenVendorClass(const TokenPtr& token, uint32_t vendor_id,
+                               uint16_t index, TokenOption::RepresentationType repr,
+                               TokenVendor::FieldType field) {
+        ASSERT_TRUE(token);
+
+        boost::shared_ptr<TokenVendorClass> vendor =
+            boost::dynamic_pointer_cast<TokenVendorClass>(token);
+
+        ASSERT_TRUE(vendor);
+
+        EXPECT_EQ(vendor_id, vendor->getVendorId());
+        EXPECT_EQ(index, vendor->getDataIndex());
+        EXPECT_EQ(repr, vendor->getRepresentation());
+        EXPECT_EQ(field, vendor->getField());
+    }
+
+    Option::Universe universe_; ///< Universe (V4 or V6)
     bool parsed_; ///< Parsing status
 };
 
@@ -1025,4 +1206,95 @@ TEST_F(EvalContextTest, typeErrors) {
                "<string>:1.8-9: syntax error, unexpected or, expecting ==");
 }
 
+
+TEST_F(EvalContextTest, vendor4SpecificVendorExists) {
+    testVendor("vendor[4491].exists", Option::V4, 4491, TokenOption::EXISTS);
+}
+
+TEST_F(EvalContextTest, vendor6SpecificVendorExists) {
+    testVendor("vendor[4491].exists", Option::V6, 4491, TokenOption::EXISTS);
+}
+
+TEST_F(EvalContextTest, vendor4AnyVendorExists) {
+    testVendor("vendor[*].exists", Option::V4, 0, TokenOption::EXISTS);
+}
+
+TEST_F(EvalContextTest, vendor6AnyVendorExists) {
+    testVendor("vendor[*].exists", Option::V6, 0, TokenOption::EXISTS);
+}
+
+TEST_F(EvalContextTest, vendor4enterprise) {
+    testVendorEnterprise("vendor.enterprise == 0x1234", Option::V4);
+}
+
+TEST_F(EvalContextTest, vendor6enterprise) {
+    testVendorEnterprise("vendor.enterprise == 0x1234", Option::V6);
+}
+
+TEST_F(EvalContextTest, vendor4SuboptionExists) {
+    testVendor("vendor[4491].option[1].exists", Option::V4, 4491, 1, TokenOption::EXISTS);
+}
+
+TEST_F(EvalContextTest, vendor6SuboptionExists) {
+    testVendor("vendor[4491].option[1].exists", Option::V6, 4491, 1, TokenOption::EXISTS);
+}
+
+TEST_F(EvalContextTest, vendor4SuboptionHex) {
+    testVendor("vendor[4491].option[1].hex == 0x1234", Option::V4, 4491, 1,
+               TokenOption::HEXADECIMAL);
+}
+
+TEST_F(EvalContextTest, vendor6SuboptionHex) {
+    testVendor("vendor[4491].option[1].hex == 0x1234", Option::V6, 4491, 1,
+               TokenOption::HEXADECIMAL);
+}
+
+TEST_F(EvalContextTest, vendorClass4SpecificVendorExists) {
+    testVendorClass("vendor-class[4491].exists", Option::V4, 4491);
+}
+
+TEST_F(EvalContextTest, vendorClass6SpecificVendorExists) {
+    testVendorClass("vendor-class[4491].exists", Option::V6, 4491);
+}
+
+TEST_F(EvalContextTest, vendorClass4AnyVendorExists) {
+    testVendorClass("vendor-class[*].exists", Option::V4, 0);
+}
+
+TEST_F(EvalContextTest, vendorClass6AnyVendorExists) {
+    testVendorClass("vendor-class[*].exists", Option::V6, 0);
+}
+
+TEST_F(EvalContextTest, vendorClass4enterprise) {
+    testVendorClassEnterprise("vendor-class.enterprise == 0x1234", Option::V4);
+}
+
+TEST_F(EvalContextTest, vendorClass6enterprise) {
+    testVendorClassEnterprise("vendor-class.enterprise == 0x1234", Option::V6);
+}
+
+TEST_F(EvalContextTest, vendorClass4SpecificVendorData) {
+    testVendorClass("vendor-class[4491].data == 0x1234", Option::V4, 4491, 0);
+}
+
+TEST_F(EvalContextTest, vendorClass6SpecificVendorData) {
+    testVendorClass("vendor-class[4491].data == 0x1234", Option::V6, 4491, 0);
+}
+
+TEST_F(EvalContextTest, vendorClass4AnyVendorData) {
+    testVendorClass("vendor-class[*].data == 0x1234", Option::V4, 0, 0);
+}
+
+TEST_F(EvalContextTest, vendorClass6AnyVendorData) {
+    testVendorClass("vendor-class[*].data == 0x1234", Option::V6, 0, 0);
+}
+
+TEST_F(EvalContextTest, vendorClass4DataIndex) {
+    testVendorClass("vendor-class[4491].data[3] == 0x1234", Option::V4, 4491, 3);
+}
+
+TEST_F(EvalContextTest, vendorClass6DataIndex) {
+    testVendorClass("vendor-class[4491].data[3] == 0x1234", Option::V6, 4491, 3);
+}
+
 };

+ 645 - 1
src/lib/eval/tests/token_unittest.cc

@@ -12,6 +12,8 @@
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_vendor_class.h>
 #include <log/logger_manager.h>
 #include <log/logger_name.h>
 #include <log/logger_support.h>
@@ -185,6 +187,19 @@ public:
         t_.reset();
     }
 
+    /// @brief Aux. function that stores integer values as 4 bytes string.
+    ///
+    /// @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);
+    }
+
     TokenPtr t_; ///< Just a convenience pointer
 
     ValueStack values_; ///< evaluated values will be stored here
@@ -195,6 +210,8 @@ public:
     OptionPtr option_str4_; ///< A string option for DHCPv4
     OptionPtr option_str6_; ///< A string option for DHCPv6
 
+    OptionVendorPtr vendor_; ///< Vendor option used during tests
+    OptionVendorClassPtr vendor_class_; ///< Vendor class option used during tests
 
     /// @brief Verify that the substring eval works properly
     ///
@@ -236,8 +253,232 @@ public:
         }
     }
 
-    /// @todo: Add more option types here
+    /// @brief Creates vendor-option with specified value and adds it to packet
+    ///
+    /// This method creates specified vendor option, removes any existing
+    /// vendor options and adds the new one to v4 or v6 packet.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param vendor_id specifies enterprise-id value.
+    void setVendorOption(Option::Universe u, uint32_t vendor_id) {
+        vendor_.reset(new OptionVendor(u, vendor_id));
+        switch (u) {
+        case Option::V4:
+            pkt4_->delOption(DHO_VIVSO_SUBOPTIONS);
+            pkt4_->addOption(vendor_);
+            break;
+        case Option::V6:
+            pkt6_->delOption(D6O_VENDOR_OPTS);
+            pkt6_->addOption(vendor_);
+            break;
+        }
+    }
+
+    /// @brief Creates vendor-class option with specified values and adds it to packet
+    ///
+    /// This method creates specified vendor-class option, removes any existing
+    /// vendor class options and adds the new one to v4 or v6 packet.
+    /// It also creates data tuples with greek alphabet names.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param vendor_id specifies enterprise-id value.
+    /// @param tuples_size number of data tuples to create.
+    void setVendorClassOption(Option::Universe u, uint32_t vendor_id,
+                              size_t tuples_size = 0) {
+        // Create the option first.
+        vendor_class_.reset(new OptionVendorClass(u, vendor_id));
+
+        // Now let's add specified number of data tuples
+        OpaqueDataTuple::LengthFieldType len = (u == Option::V4?OpaqueDataTuple::LENGTH_1_BYTE:
+                                                OpaqueDataTuple::LENGTH_2_BYTES);
+        const char * content[] = { "alpha", "beta", "delta", "gamma", "epsilon",
+                                 "zeta", "eta", "theta", "iota", "kappa" };
+        ASSERT_TRUE(tuples_size < sizeof(content));
+        for (int i = 0; i < tuples_size; i++) {
+            OpaqueDataTuple tuple(len);
+            tuple.assign(string(content[i]));
+            if (u == Option::V4 && i == 0) {
+                // vendor-clas for v4 has a pecurilar quirk. The first tuple is being
+                // added, even if there's no data at all.
+                vendor_class_->setTuple(0, tuple);
+            } else {
+                vendor_class_->addTuple(tuple);
+            }
+        }
+
+        switch (u) {
+        case Option::V4:
+            pkt4_->delOption(DHO_VIVCO_SUBOPTIONS);
+            pkt4_->addOption(vendor_class_);
+            break;
+        case Option::V6:
+            pkt6_->delOption(D6O_VENDOR_CLASS);
+            pkt6_->addOption(vendor_class_);
+            break;
+        }
+    }
+
+    /// @brief Auxiliary function that evaluates tokens and checks result
+    ///
+    /// Depending on the universe, either pkt4_ or pkt6_ are supposed to have
+    /// all the necessary values and options set. The result is checked
+    /// on the values_ stack.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param expected_result text representation of the expected outcome
+    void evaluate(Option::Universe u, std::string expected_result) {
+        switch (u) {
+        case Option::V4:
+            EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+            break;
+        case Option::V6:
+            EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_));
+            break;
+        default:
+            ADD_FAILURE() << "Invalid universe specified.";
+        }
+        ASSERT_EQ(1, values_.size());
+        EXPECT_EQ(expected_result, values_.top());
+    }
+
+    /// @brief Tests if vendor token behaves properly.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param token_vendor_id enterprise-id used in the token
+    /// @param option_vendor_id enterprise-id used in option (0 means don't
+    ///        create the option)
+    /// @param expected_result text representation of the expected outcome
+    void testVendorExists(Option::Universe u, uint32_t token_vendor_id,
+                          uint32_t option_vendor_id, std::string expected_result) {
+        // Let's clear any old values, so we can run multiple cases in each test
+        clearStack();
+
+        // Create the token
+        ASSERT_NO_THROW(t_.reset(new TokenVendor(u, token_vendor_id,
+                                                 TokenOption::EXISTS)));
+
+        // If specified option is non-zero, create it.
+        if (option_vendor_id) {
+            setVendorOption(u, option_vendor_id);
+        }
+
+        evaluate(u, expected_result);
+    }
+
+    /// @brief Tests if vendor token properly returns enterprise-id.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param option_vendor_id enterprise-id used in option (0 means don't
+    ///        create the option)
+    /// @param expected_result text representation of the expected outcome
+    void testVendorEnterprise(Option::Universe u, uint32_t option_vendor_id,
+                              std::string expected_result) {
+        // Let's clear any old values, so we can run multiple cases in each test
+        clearStack();
+
+        ASSERT_NO_THROW(t_.reset(new TokenVendor(u, 0, TokenVendor::ENTERPRISE_ID)));
+        if (option_vendor_id) {
+            setVendorOption(u, option_vendor_id);
+        }
+
+        evaluate(u, expected_result);
+    }
 
+    /// @brief Tests if vendor class token properly returns enterprise-id.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param option_vendor_id enterprise-id used in option (0 means don't
+    ///        create the option)
+    /// @param expected_result text representation of the expected outcome
+    void testVendorClassEnterprise(Option::Universe u, uint32_t option_vendor_id,
+                                   std::string expected_result) {
+        // Let's clear any old values, so we can run multiple cases in each test
+        clearStack();
+
+        ASSERT_NO_THROW(t_.reset(new TokenVendorClass(u, 0, TokenVendor::ENTERPRISE_ID)));
+        if (option_vendor_id) {
+            setVendorClassOption(u, option_vendor_id);
+        }
+
+        evaluate(u, expected_result);
+    }
+
+    /// @brief Tests if vendor class token can report existence properly.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param token_vendor_id enterprise-id used in the token
+    /// @param option_vendor_id enterprise-id used in option (0 means don't
+    ///        create the option)
+    /// @param expected_result text representation of the expected outcome
+    void testVendorClassExists(Option::Universe u, uint32_t token_vendor_id,
+                               uint32_t option_vendor_id, std::string expected_result) {
+        // Let's clear any old values, so we can run multiple cases in each test
+        clearStack();
+
+        ASSERT_NO_THROW(t_.reset(new TokenVendorClass(u, token_vendor_id,
+                                                      TokenOption::EXISTS)));
+
+        if (option_vendor_id) {
+            setVendorClassOption(u, option_vendor_id);
+        }
+
+        evaluate(u, expected_result);
+    }
+
+    /// @brief Tests if vendor class token can handle sub-options properly.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param token_vendor_id enterprise-id used in the token
+    /// @param token_option_code option code in the token
+    /// @param option_vendor_id enterprise-id used in option (0 means don't
+    ///        create the option)
+    /// @param option_code sub-option code (0 means don't create suboption)
+    /// @param expected_result text representation of the expected outcome
+    void testVendorSuboption(Option::Universe u,
+                             uint32_t token_vendor_id, uint16_t token_option_code,
+                             uint32_t option_vendor_id, uint16_t option_code,
+                             TokenOption::RepresentationType repr, std::string expected) {
+        // Let's clear any old values, so we can run multiple cases in each test
+        clearStack();
+
+        ASSERT_NO_THROW(t_.reset(new TokenVendor(u, token_vendor_id, repr,
+                                                 token_option_code)));
+        if (option_vendor_id) {
+            setVendorOption(u, option_vendor_id);
+            if (option_code) {
+                ASSERT_TRUE(vendor_);
+                OptionPtr subopt(new OptionString(u, option_code, "alpha"));
+                vendor_->addOption(subopt);
+            }
+        }
+
+        evaluate(u, expected);
+    }
+
+    /// @brief Tests if vendor class token can handle data chunks properly.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param token_vendor_id enterprise-id used in the token
+    /// @param token_index data index used in the token
+    /// @param option_vendor_id enterprise-id used in option (0 means don't
+    ///        create the option)
+    /// @param data_tuples number of data tuples in the option
+    /// @param expected_result text representation of the expected outcome
+    void testVendorClassData(Option::Universe u,
+                             uint32_t token_vendor_id, uint16_t token_index,
+                             uint32_t option_vendor_id, uint16_t data_tuples,
+                             std::string expected) {
+        // Let's clear any old values, so we can run multiple cases in each test
+        clearStack();
+
+        ASSERT_NO_THROW(t_.reset(new TokenVendorClass(u, token_vendor_id,
+                                                      TokenVendor::DATA, token_index)));
+        if (option_vendor_id) {
+            setVendorClassOption(u, option_vendor_id, data_tuples);
+        }
+
+        evaluate(u, expected);
+    }
 };
 
 // This tests the toBool() conversions
@@ -1671,4 +1912,407 @@ TEST_F(TokenTest, pkt6Fields) {
 
     EXPECT_TRUE(checkFile());
 }
+
+// This test verifies if expression vendor[4491].exist works properly in DHCPv4.
+TEST_F(TokenTest, vendor4SpecificVendorExists) {
+    // Case 1: no option, should evaluate to false
+    testVendorExists(Option::V4, 4491, 0, "false");
+
+    // Case 2: option present, but uses different enterprise-id, should fail
+    testVendorExists(Option::V4, 4491, 1234, "false");
+
+    // Case 3: option present and has matchin enterprise-id, should suceed
+    testVendorExists(Option::V4, 4491, 4491, "true");
+}
+
+// This test verifies if expression vendor[4491].exist works properly in DHCPv6.
+TEST_F(TokenTest, vendor6SpecificVendorExists) {
+    // Case 1: no option, should evaluate to false
+    testVendorExists(Option::V6, 4491, 0, "false");
+
+    // Case 2: option present, but uses different enterprise-id, should fail
+    testVendorExists(Option::V6, 4491, 1234, "false");
+
+    // Case 3: option present and has matchin enterprise-id, should suceed
+    testVendorExists(Option::V6, 4491, 4491, "true");
+}
+
+/// Test if expression vendor[*].exists works properly for DHCPv4.
+TEST_F(TokenTest, vendor4AnyVendorExists) {
+    // Case 1: no option, should evaluate to false
+    testVendorExists(Option::V4, 0, 0, "false");
+
+    // Case 2: option present with vendor-id 1234, should succeed
+    testVendorExists(Option::V4, 0, 1234, "true");
+
+    // Case 3: option present with vendor-id 4491, should succeed
+    testVendorExists(Option::V4, 0, 4491, "true");
+}
+
+// Test if expression vendor[*].exists works properly for DHCPv6.
+TEST_F(TokenTest, vendor6AnyVendorExists) {
+    // Case 1: no option, should evaluate to false
+    testVendorExists(Option::V6, 0, 0, "false");
+
+    // Case 2: option present with vendor-id 1234, should succeed
+    testVendorExists(Option::V6, 0, 1234, "true");
+
+    // Case 3: option present with vendor-id 4491, should succeed
+    testVendorExists(Option::V6, 0, 4491, "true");
+}
+
+// Test if expression vendor[*].enterprise works properly for DHCPv4.
+TEST_F(TokenTest, vendor4enterprise) {
+    // Case 1: No option present, should return empty string
+    testVendorEnterprise(Option::V4, 0, "");
+
+    // Case 2: Option with vendor-id 1234, should return "1234"
+    testVendorEnterprise(Option::V4, 1234, encode(1234));
+
+    // Case 3: Option with vendor-id set to maximum value, should still
+    // be able to handle it
+    testVendorEnterprise(Option::V4, 4294967295, encode(4294967295));
+}
+
+// Test if expression vendor[*].enterprise works properly for DHCPv6.
+TEST_F(TokenTest, vendor6enterprise) {
+    // Case 1: No option present, should return empty string
+    testVendorEnterprise(Option::V6, 0, "");
+
+    // Case 2: Option with vendor-id 1234, should return "1234"
+    testVendorEnterprise(Option::V6, 1234, encode(1234));
+
+    // Case 3: Option with vendor-id set to maximum value, should still
+    // be able to handle it
+    testVendorEnterprise(Option::V6, 4294967295, encode(4294967295));
+}
+
+// This one tests "vendor[4491].option[1].exists" expression. There are so many
+// wonderful ways in which this could fail: the option could not be there,
+// it could have different enterprise-id, may not have suboption 1. Or may
+// have the suboption with valid type, but enterprise may be different.
+TEST_F(TokenTest, vendor4SuboptionExists) {
+    // Case 1: expression vendor[4491].option[1].exists, no option present
+    testVendorSuboption(Option::V4, 4491, 1, 0, 0, TokenOption::EXISTS, "false");
+
+    // Case 2: expression vendor[4491].option[1].exists, option with vendor-id = 1234,
+    // no suboptions, expected result "false"
+    testVendorSuboption(Option::V4, 4491, 1, 1234, 0, TokenOption::EXISTS, "false");
+
+    // Case 3: expression vendor[4491].option[1].exists, option with vendor-id = 1234,
+    // suboption 1, expected result "false"
+    testVendorSuboption(Option::V4, 4491, 1, 1234, 1, TokenOption::EXISTS, "false");
+
+    // Case 4: expression vendor[4491].option[1].exists, option with vendor-id = 4491,
+    // suboption 2, expected result "false"
+    testVendorSuboption(Option::V4, 4491, 1, 4491, 2, TokenOption::EXISTS, "false");
+
+    // Case 5: expression vendor[4491].option[1].exists, option with vendor-id = 4491,
+    // suboption 1, expected result "true"
+    testVendorSuboption(Option::V4, 4491, 1, 4491, 1, TokenOption::EXISTS, "true");
+}
+
+// This is similar to the previous one, but tests vendor[4491].option[1].exists
+// for DHCPv6.
+TEST_F(TokenTest, vendor6SuboptionExists) {
+    // Case 1: expression vendor[4491].option[1].exists, no option present
+    testVendorSuboption(Option::V6, 4491, 1, 0, 0, TokenOption::EXISTS, "false");
+
+    // Case 2: expression vendor[4491].option[1].exists, option with vendor-id = 1234,
+    // no suboptions, expected result "false"
+    testVendorSuboption(Option::V6, 4491, 1, 1234, 0, TokenOption::EXISTS, "false");
+
+    // Case 3: expression vendor[4491].option[1].exists, option with vendor-id = 1234,
+    // suboption 1, expected result "false"
+    testVendorSuboption(Option::V6, 4491, 1, 1234, 1, TokenOption::EXISTS, "false");
+
+    // Case 4: expression vendor[4491].option[1].exists, option with vendor-id = 4491,
+    // suboption 2, expected result "false"
+    testVendorSuboption(Option::V6, 4491, 1, 4491, 2, TokenOption::EXISTS, "false");
+
+    // Case 5: expression vendor[4491].option[1].exists, option with vendor-id = 4491,
+    // suboption 1, expected result "true"
+    testVendorSuboption(Option::V6, 4491, 1, 4491, 1, TokenOption::EXISTS, "true");
+}
+
+// This test verifies if vendor[4491].option[1].hex expression properly returns
+// value of said sub-option or empty string if desired option is not present.
+// This test is for DHCPv4.
+TEST_F(TokenTest, vendor4SuboptionHex) {
+    // Case 1: no option present, should return empty string
+    testVendorSuboption(Option::V4, 4491, 1, 0, 0, TokenOption::HEXADECIMAL, "");
+
+    // Case 2: option with vendor-id = 1234, no suboptions, expected result ""
+    testVendorSuboption(Option::V4, 4491, 1, 1234, 0, TokenOption::HEXADECIMAL, "");
+
+    // Case 3: option with vendor-id = 1234, suboption 1, expected result ""
+    testVendorSuboption(Option::V4, 4491, 1, 1234, 1, TokenOption::HEXADECIMAL, "");
+
+    // Case 4: option with vendor-id = 4491, suboption 2, expected result ""
+    testVendorSuboption(Option::V4, 4491, 1, 4491, 2, TokenOption::HEXADECIMAL, "");
+
+    // Case 5: option with vendor-id = 4491, suboption 1, expected result content
+    // of the option
+    testVendorSuboption(Option::V4, 4491, 1, 4491, 1, TokenOption::HEXADECIMAL, "alpha");
+}
+
+// This test verifies if vendor[4491].option[1].hex expression properly returns
+// value of said sub-option or empty string if desired option is not present.
+// This test is for DHCPv4.
+TEST_F(TokenTest, vendor6SuboptionHex) {
+    // Case 1: no option present, should return empty string
+    testVendorSuboption(Option::V6, 4491, 1, 0, 0, TokenOption::HEXADECIMAL, "");
+
+    // Case 2: option with vendor-id = 1234, no suboptions, expected result ""
+    testVendorSuboption(Option::V6, 4491, 1, 1234, 0, TokenOption::HEXADECIMAL, "");
+
+    // Case 3: option with vendor-id = 1234, suboption 1, expected result ""
+    testVendorSuboption(Option::V6, 4491, 1, 1234, 1, TokenOption::HEXADECIMAL, "");
+
+    // Case 4: option with vendor-id = 4491, suboption 2, expected result ""
+    testVendorSuboption(Option::V6, 4491, 1, 4491, 2, TokenOption::HEXADECIMAL, "");
+
+    // Case 5: option with vendor-id = 4491, suboption 1, expected result content
+    // of the option
+    testVendorSuboption(Option::V6, 4491, 1, 4491, 1, TokenOption::HEXADECIMAL, "alpha");
+}
+
+// This test verifies that "vendor-class[4491].exists" expression can be used
+// in DHCPv4.
+TEST_F(TokenTest, vendorClass4SpecificVendorExists) {
+    // Case 1: no option present, should fail
+    testVendorClassExists(Option::V4, 4491, 0, "false");
+
+    // Case 2: option exists, but has different vendor-id (1234), should fail
+    testVendorClassExists(Option::V4, 4491, 1234, "false");
+
+    // Case 3: option exists and has matching vendor-id, should succeed
+    testVendorClassExists(Option::V4, 4491, 4491, "true");
+}
+
+// This test verifies that "vendor-class[4491].exists" expression can be used
+// in DHCPv6.
+TEST_F(TokenTest, vendorClass6SpecificVendorExists) {
+    // Case 1: no option present, should fail
+    testVendorClassExists(Option::V6, 4491, 0, "false");
+
+    // Case 2: option exists, but has different vendor-id (1234), should fail
+    testVendorClassExists(Option::V6, 4491, 1234, "false");
+
+    // Case 3: option exists and has matching vendor-id, should succeed
+    testVendorClassExists(Option::V6, 4491, 4491, "true");
+}
+
+// This test verifies that "vendor-class[*].exists" can be used in DHCPv4
+// and it matches a vendor class option with any vendor-id.
+TEST_F(TokenTest, vendorClass4AnyVendorExists) {
+    // Case 1: no option present, should fail
+    testVendorClassExists(Option::V4, 0, 0, "false");
+
+    // Case 2: option exists, should succeed, regardless of the vendor-id
+    testVendorClassExists(Option::V4, 0, 1234, "true");
+
+    // Case 3: option exists, should succeed, regardless of the vendor-id
+    testVendorClassExists(Option::V4, 0, 4491, "true");
+}
+
+// This test verifies that "vendor-class[*].exists" can be used in DHCPv6
+// and it matches a vendor class option with any vendor-id.
+TEST_F(TokenTest, vendorClass6AnyVendorExists) {
+    // Case 1: no option present, should fail
+    testVendorClassExists(Option::V6, 0, 0, "false");
+
+    // Case 2: option exists, should succeed, regardless of the vendor-id
+    testVendorClassExists(Option::V6, 0, 1234, "true");
+
+    // Case 3: option exists, should succeed, regardless of the vendor-id
+    testVendorClassExists(Option::V6, 0, 4491, "true");
+}
+
+// Test if expression "vendor-class.enterprise" works properly for DHCPv4.
+TEST_F(TokenTest, vendorClass4enterprise) {
+    // Case 1: No option present, should return empty string
+    testVendorClassEnterprise(Option::V4, 0, "");
+
+    // Case 2: Option with vendor-id 1234, should return "1234"
+    testVendorClassEnterprise(Option::V4, 1234, encode(1234));
+
+    // Case 3: Option with vendor-id set to maximum value, should still
+    // be able to handle it
+    testVendorClassEnterprise(Option::V4, 4294967295, encode(4294967295));
+}
+
+// Test if expression "vendor-class.enterprise" works properly for DHCPv6.
+TEST_F(TokenTest, vendorClass6enterprise) {
+    // Case 1: No option present, should return empty string
+    testVendorClassEnterprise(Option::V6, 0, "");
+
+    // Case 2: Option with vendor-id 1234, should return "1234"
+    testVendorClassEnterprise(Option::V6, 1234, encode(1234));
+
+    // Case 3: Option with vendor-id set to maximum value, should still
+    // be able to handle it.
+    testVendorClassEnterprise(Option::V6, 4294967295, encode(4294967295));
+}
+
+// Test that expression "vendor-class[4491].data" is able to retrieve content
+// of the first tuple of the vendor-class option in DHCPv4.
+TEST_F(TokenTest, vendorClass4SpecificVendorData) {
+    // Case 1: Expression looks for vendor-id 4491, data[0], there is no
+    // vendor-class option at all, expected result is empty string.
+    testVendorClassData(Option::V4, 4491, 0, 0, 0, "");
+
+    // Case 2: Expression looks for vendor-id 4491, data[0], there is
+    // vendor-class with vendor-id 1234 and no data, expected result is empty string.
+    testVendorClassData(Option::V4, 4491, 0, 1234, 0, "");
+
+    // Case 3: Expression looks for vendor-id 4491, data[0], there is
+    // vendor-class with vendor-id 4491 and no data, expected result is empty string
+    testVendorClassData(Option::V4, 4491, 0, 4491, 0, "");
+
+    // Case 4: Expression looks for vendor-id 4491, data[0], there is
+    // vendor-class with vendor-id 1234 and 1 data tuple, expected result is empty string
+    testVendorClassData(Option::V4, 4491, 0, 1234, 1, "");
+
+    // Case 5: Expression looks for vendor-id 4491, data[0], there is
+    // vendor-class with vendor-id 4491 and 1 data tuple, expected result is
+    // content of that data ("alpha")
+    testVendorClassData(Option::V4, 4491, 0, 4491, 1, "alpha");
+}
+
+// Test that expression "vendor-class[4491].data" is able to retrieve content
+// of the first tuple of the vendor-class option in DHCPv6.
+TEST_F(TokenTest, vendorClass6SpecificVendorData) {
+    // Case 1: Expression looks for vendor-id 4491, data[0], there is no
+    // vendor-class option at all, expected result is empty string.
+    testVendorClassData(Option::V6, 4491, 0, 0, 0, "");
+
+    // Case 2: Expression looks for vendor-id 4491, data[0], there is
+    // vendor-class with vendor-id 1234 and no data, expected result is empty string.
+    testVendorClassData(Option::V6, 4491, 0, 1234, 0, "");
+
+    // Case 3: Expression looks for vendor-id 4491, data[0], there is
+    // vendor-class with vendor-id 4491 and no data, expected result is empty string
+    testVendorClassData(Option::V6, 4491, 0, 4491, 0, "");
+
+    // Case 4: Expression looks for vendor-id 4491, data[0], there is
+    // vendor-class with vendor-id 1234 and 1 data tuple, expected result is empty string
+    testVendorClassData(Option::V6, 4491, 0, 1234, 1, "");
+
+    // Case 5: Expression looks for vendor-id 4491, data[0], there is
+    // vendor-class with vendor-id 4491 and 1 data tuple, expected result is
+    // content of that data ("alpha")
+    testVendorClassData(Option::V6, 4491, 0, 4491, 1, "alpha");
+}
+
+// Test that expression "vendor-class[*].data" is able to retrieve content
+// of the first tuple of the vendor-class option in DHCPv4.
+TEST_F(TokenTest, vendorClass4AnyVendorData) {
+    // Case 1: Expression looks for any vendor-id (0), data[0], there is no
+    // vendor-class option at all, expected result is empty string.
+    testVendorClassData(Option::V4, 0, 0, 0, 0, "");
+
+    // Case 2: Expression looks for any vendor-id (0), data[0], there is
+    // vendor-class with vendor-id 1234 and no data, expected result is empty string.
+    testVendorClassData(Option::V4, 0, 0, 1234, 0, "");
+
+    // Case 3: Expression looks for any vendor-id (0), data[0], there is
+    // vendor-class with vendor-id 4491 and no data, expected result is empty string
+    testVendorClassData(Option::V4, 0, 0, 4491, 0, "");
+
+    // Case 4: Expression looks for any vendor-id (0), data[0], there is
+    // vendor-class with vendor-id 1234 and 1 data tuple, expected result is empty string
+    testVendorClassData(Option::V4, 0, 0, 1234, 1, "alpha");
+
+    // Case 5: Expression looks for any vendor-id (0), data[0], there is
+    // vendor-class with vendor-id 4491 and 1 data tuple, expected result is
+    // content of that data ("alpha")
+    testVendorClassData(Option::V4, 0, 0, 4491, 1, "alpha");
+}
+
+// Test that expression "vendor-class[*].data" is able to retrieve content
+// of the first tuple of the vendor-class option in DHCPv6.
+TEST_F(TokenTest, vendorClass6AnyVendorData) {
+    // Case 1: Expression looks for any vendor-id (0), data[0], there is no
+    // vendor-class option at all, expected result is empty string.
+    testVendorClassData(Option::V6, 0, 0, 0, 0, "");
+
+    // Case 2: Expression looks for any vendor-id (0), data[0], there is
+    // vendor-class with vendor-id 1234 and no data, expected result is empty string.
+    testVendorClassData(Option::V6, 0, 0, 1234, 0, "");
+
+    // Case 3: Expression looks for any vendor-id (0), data[0], there is
+    // vendor-class with vendor-id 4491 and no data, expected result is empty string
+    testVendorClassData(Option::V6, 0, 0, 4491, 0, "");
+
+    // Case 4: Expression looks for any vendor-id (0), data[0], there is
+    // vendor-class with vendor-id 1234 and 1 data tuple, expected result is empty string
+    testVendorClassData(Option::V6, 0, 0, 1234, 1, "alpha");
+
+    // Case 5: Expression looks for any vendor-id (0), data[0], there is
+    // vendor-class with vendor-id 4491 and 1 data tuple, expected result is
+    // content of that data ("alpha")
+    testVendorClassData(Option::V6, 0, 0, 4491, 1, "alpha");
+}
+
+// This test verifies if expression vendor-class[4491].data[3] is able to access
+// the tuple specified by index. This is a DHCPv4 test.
+TEST_F(TokenTest, vendorClass4DataIndex) {
+    // Case 1: Expression looks for vendor-id 4491, data[3], there is no
+    // vendor-class option at all, expected result is empty string.
+    testVendorClassData(Option::V4, 4491, 3, 0, 0, "");
+
+    // Case 2: Expression looks for vendor-id 4491, data[3], there is
+    // vendor-class with vendor-id 1234 and no data, expected result is empty string.
+    testVendorClassData(Option::V4, 4491, 3, 1234, 0, "");
+
+    // Case 3: Expression looks for vendor-id 4491, data[3], there is
+    // vendor-class with vendor-id 4491 and no data, expected result is empty string
+    testVendorClassData(Option::V4, 4491, 3, 4491, 0, "");
+
+    // Case 4: Expression looks for vendor-id 4491, data[3], there is
+    // vendor-class with vendor-id 1234 and 2 data tuples, expected result is empty string.
+    testVendorClassData(Option::V4, 4491, 3, 1234, 1, "");
+
+    // Case 5: Expression looks for vendor-id 4491, data[3], there is
+    // vendor-class with vendor-id 4491, but has only 2 data tuples, expected
+    // result is empty string.
+    testVendorClassData(Option::V4, 4491, 3, 1234, 1, "");
+
+    // Case 6: Expression looks for vendor-id 4491, data[3], there is
+    // vendor-class with vendor-id 4491 and 5 data tuples, expected result is
+    // content of that tuple ("gamma")
+    testVendorClassData(Option::V4, 4491, 3, 4491, 5, "gamma");
+}
+
+// This test verifies if expression vendor-class[4491].data[3] is able to access
+// the tuple specified by index. This is a DHCPv6 test.
+TEST_F(TokenTest, vendorClass6DataIndex) {
+    // Case 1: Expression looks for vendor-id 4491, data[3], there is no
+    // vendor-class option at all, expected result is empty string.
+    testVendorClassData(Option::V6, 4491, 3, 0, 0, "");
+
+    // Case 2: Expression looks for vendor-id 4491, data[3], there is
+    // vendor-class with vendor-id 1234 and no data, expected result is empty string.
+    testVendorClassData(Option::V6, 4491, 3, 1234, 0, "");
+
+    // Case 3: Expression looks for vendor-id 4491, data[3], there is
+    // vendor-class with vendor-id 4491 and no data, expected result is empty string
+    testVendorClassData(Option::V6, 4491, 3, 4491, 0, "");
+
+    // Case 4: Expression looks for vendor-id 4491, data[3], there is
+    // vendor-class with vendor-id 1234 and 2 data tuples, expected result is empty string.
+    testVendorClassData(Option::V6, 4491, 3, 1234, 1, "");
+
+    // Case 5: Expression looks for vendor-id 4491, data[3], there is
+    // vendor-class with vendor-id 4491, but has only 2 data tuples, expected
+    // result is empty string.
+    testVendorClassData(Option::V6, 4491, 3, 1234, 1, "");
+
+    // Case 6: Expression looks for vendor-id 4491, data[3], there is
+    // vendor-class with vendor-id 4491 and 5 data tuples, expected result is
+    // content of that tuple ("gamma")
+    testVendorClassData(Option::V6, 4491, 3, 4491, 5, "gamma");
+}
+
 };

+ 200 - 2
src/lib/eval/token.cc

@@ -11,6 +11,10 @@
 #include <dhcp/pkt4.h>
 #include <boost/lexical_cast.hpp>
 #include <dhcp/pkt6.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_vendor_class.h>
 #include <cstring>
 #include <string>
 
@@ -63,7 +67,7 @@ TokenHexString::evaluate(Pkt& /*pkt*/, ValueStack& values) {
     // Log what we pushed
     LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_HEXSTRING)
         .arg("0x" + util::encode::encodeHex(std::vector<uint8_t>(value_.begin(),
-								 value_.end())));
+                                                                 value_.end())));
 }
 
 TokenIpAddress::TokenIpAddress(const string& addr) : value_("") {
@@ -89,7 +93,7 @@ TokenIpAddress::evaluate(Pkt& /*pkt*/, ValueStack& values) {
     // Log what we pushed
     LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_IPADDRESS)
         .arg("0x" + util::encode::encodeHex(std::vector<uint8_t>(value_.begin(),
-								 value_.end())));
+                                                                 value_.end())));
 }
 
 OptionPtr
@@ -136,6 +140,15 @@ TokenOption::evaluate(Pkt& pkt, ValueStack& values) {
     }
 }
 
+void
+TokenOption::pushFailure(ValueStack& values) {
+    if (representation_type_ == EXISTS) {
+        values.push("false");
+    } else {
+        values.push("");
+    }
+}
+
 TokenRelay4Option::TokenRelay4Option(const uint16_t option_code,
                                      const RepresentationType& rep_type)
     :TokenOption(option_code, rep_type) {
@@ -599,3 +612,188 @@ TokenPkt6::evaluate(Pkt& pkt, ValueStack& values) {
         .arg("0x" + util::encode::encodeHex(std::vector<uint8_t>(value.begin(),
                                                                  value.end())));
 }
+
+TokenVendor::TokenVendor(Option::Universe u, uint32_t vendor_id, RepresentationType repr,
+                         uint16_t option_code)
+    :TokenOption(option_code, repr), universe_(u), vendor_id_(vendor_id),
+     field_(option_code ? SUBOPTION : EXISTS)
+{
+}
+
+TokenVendor::TokenVendor(Option::Universe u, uint32_t vendor_id, FieldType field)
+    :TokenOption(0, TokenOption::HEXADECIMAL), universe_(u), vendor_id_(vendor_id),
+     field_(field)
+{
+    if (field_ == EXISTS) {
+        representation_type_ = TokenOption::EXISTS;
+    }
+}
+
+uint32_t TokenVendor::getVendorId() const {
+    return (vendor_id_);
+}
+
+TokenVendor::FieldType TokenVendor::getField() const {
+    return (field_);
+}
+
+void TokenVendor::evaluate(Pkt& pkt, ValueStack& values) {
+
+    // Get the option first.
+    uint16_t code = 0;
+    switch (universe_) {
+    case Option::V4:
+        code = DHO_VIVSO_SUBOPTIONS;
+        break;
+    case Option::V6:
+        code = D6O_VENDOR_OPTS;
+        break;
+    }
+
+    OptionPtr opt = pkt.getOption(code);
+    OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+    if (!vendor) {
+        // There's no vendor option, give up.
+        pushFailure(values);
+        return;
+    }
+
+    if (vendor_id_ && (vendor_id_ != vendor->getVendorId())) {
+        // There is vendor option, but it has other vendor-id value
+        // than we're looking for. (0 means accept any vendor-id)
+        pushFailure(values);
+        return;
+    }
+
+    switch (field_) {
+    case ENTERPRISE_ID:
+    {
+        // Extract enterprise-id
+        string txt(sizeof(uint32_t), 0);
+        uint32_t value = htonl(vendor->getVendorId());
+        memcpy(&txt[0], &value, sizeof(uint32_t));
+        values.push(txt);
+        return;
+    }
+    case SUBOPTION:
+        /// This is vendor[X].option[Y].exists, let's try to
+        /// extract the option
+        TokenOption::evaluate(pkt, values);
+        return;
+    case EXISTS:
+        // We already passed all the checks: the option is there and has specified
+        // enterprise-id.
+        values.push("true");
+        return;
+    case DATA:
+        // This is for vendor-class option, we can skip it here.
+        isc_throw(EvalTypeError, "Field None is not valid for vendor-class");
+        return;
+    }
+}
+
+OptionPtr TokenVendor::getOption(Pkt& pkt) {
+   uint16_t code = 0;
+    switch (universe_) {
+    case Option::V4:
+        code = DHO_VIVSO_SUBOPTIONS;
+        break;
+    case Option::V6:
+        code = D6O_VENDOR_OPTS;
+        break;
+    }
+
+    OptionPtr opt = pkt.getOption(code);
+    if (!opt) {
+        // If vendor option is not found, return NULL
+        return (opt);
+    }
+
+    // If vendor option is found, try to return its
+    // encapsulated option.
+    return (opt->getOption(option_code_));
+}
+
+TokenVendorClass::TokenVendorClass(Option::Universe u, uint32_t vendor_id,
+                                   RepresentationType repr)
+    :TokenVendor(u, vendor_id, repr, 0), index_(0) {
+}
+
+TokenVendorClass::TokenVendorClass(Option::Universe u, uint32_t vendor_id,
+                                   FieldType field, uint16_t index)
+    :TokenVendor(u, vendor_id, TokenOption::HEXADECIMAL, 0), index_(index)
+{
+    field_ = field;
+}
+
+uint16_t TokenVendorClass::getDataIndex() const {
+    return (index_);
+}
+
+void TokenVendorClass::evaluate(Pkt& pkt, ValueStack& values) {
+
+    // Get the option first.
+    uint16_t code = 0;
+    switch (universe_) {
+    case Option::V4:
+        code = DHO_VIVCO_SUBOPTIONS;
+        break;
+    case Option::V6:
+        code = D6O_VENDOR_CLASS;
+        break;
+    }
+
+    OptionPtr opt = pkt.getOption(code);
+    OptionVendorClassPtr vendor = boost::dynamic_pointer_cast<OptionVendorClass>(opt);
+    if (!vendor) {
+        // There's no vendor option, give up.
+        pushFailure(values);
+        return;
+    }
+
+    if (vendor_id_ && (vendor_id_ != vendor->getVendorId())) {
+        // There is vendor option, but it has other vendor-id value
+        // than we're looking for. (0 means accept any vendor-id)
+        pushFailure(values);
+        return;
+    }
+
+    switch (field_) {
+    case ENTERPRISE_ID:
+    {
+        // Extract enterprise-id
+        string txt(sizeof(uint32_t), 0);
+        uint32_t value = htonl(vendor->getVendorId());
+        memcpy(&txt[0], &value, sizeof(uint32_t));
+        values.push(txt);
+        return;
+    }
+    case SUBOPTION:
+        // Extract sub-options
+        isc_throw(EvalTypeError, "Field None is not valid for vendor-class");
+        return;
+    case EXISTS:
+        // We already passed all the checks: the option is there and has specified
+        // enterprise-id.
+        values.push("true");
+        return;
+    case DATA:
+    {
+        size_t max = vendor->getTuplesNum();
+        if (index_ + 1 > max) {
+            // The index specified if out of bound, e.g. there are only
+            // 2 tuples and index specified is 5.
+            values.push("");
+            return;
+        }
+
+        OpaqueDataTuple tuple = vendor->getTuple(index_);
+        OpaqueDataTuple::Buffer buf = tuple.getData();
+        string txt(buf.begin(), buf.end());
+        values.push(txt);
+        return;
+    }
+    default:
+        isc_throw(EvalTypeError, "Invalid field specified." << field_);
+    }
+}

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

@@ -256,6 +256,13 @@ protected:
     /// @return option instance (or NULL if not found)
     virtual OptionPtr getOption(Pkt& pkt);
 
+    /// @brief Auxiliary method that puts string representing a failure
+    ///
+    /// Depending on the representation type, this is either "" or "false".
+    ///
+    /// @param values a string representing failure will be pushed here.
+    virtual void pushFailure(ValueStack& values);
+
     uint16_t option_code_; ///< Code of the option to be extracted
     RepresentationType representation_type_; ///< Representation type.
 };
@@ -681,6 +688,191 @@ private:
     FieldType type_;
 };
 
+/// @brief Token that represents vendor options in DHCPv4 and DHCPv6.
+///
+/// It covers vendor independent vendor information option (125, DHCPv4)
+/// and vendor option (17, DHCPv6). Since both of those options may have
+/// suboptions, this class is derived from TokenOption and leverages its
+/// ability to operate on sub-options. It also adds additional capabilities.
+/// In particular, it allows retrieving enterprise-id.
+///
+/// It can represent the following expressions:
+/// vendor[4491].exist - if vendor option with enterprise-id = 4491 exists
+/// vendor[*].exist - if any vendor option exists
+/// vendor.enterprise - returns enterprise-id from vendor option
+/// vendor[4491].option[1].exist - check if suboption 1 exists for vendor 4491
+/// vendor[4491].option[1].hex - return content of suboption 1 for vendor 4491
+class TokenVendor : public TokenOption {
+public:
+
+    /// @brief Specifies a field of the vendor option
+    enum FieldType {
+        SUBOPTION,     ///< If this token fetches a suboption, not a field.
+        ENTERPRISE_ID, ///< enterprise-id field (vendor-info, vendor-class)
+        EXISTS,        ///< vendor[123].exists
+        DATA           ///< data chunk, used in derived vendor-class only
+    };
+
+    /// @brief Constructor used for accessing a field
+    ///
+    /// @param u universe (either V4 or V6)
+    /// @param vendor_id specifies enterprise-id (0 means any)
+    /// @param field specifies which field should be returned
+    TokenVendor(Option::Universe u, uint32_t vendor_id, FieldType field);
+
+
+    /// @brief Constructor used for accessing an option
+    ///
+    /// This constructor is used for accessing suboptions. In general
+    /// option_code is mandatory, except when repr is EXISTS. For
+    /// option_code = 0 and repr = EXISTS, the token will return true
+    /// if the whole option exists, not suboptions.
+    ///
+    /// @param u universe (either V4 or V6)
+    /// @param vendor_id specifies enterprise-id (0 means any)
+    /// @param repr representation type (hex or exists)
+    /// @param option_code sub-option code
+    TokenVendor(Option::Universe u, uint32_t vendor_id, RepresentationType repr,
+                uint16_t option_code = 0);
+
+    /// @brief Returns enterprise-id
+    ///
+    /// Used in tests only.
+    ///
+    /// @return enterprise-id
+    uint32_t getVendorId() const;
+
+    /// @brief Returns field.
+    ///
+    /// Used in tests only.
+    ///
+    /// @return field type.
+    FieldType getField() const;
+
+    /// @brief This is a method for evaluating a packet.
+    ///
+    /// Depending on the value of vendor_id, field type, representation and
+    /// option code, it will attempt to return specified characteristic of the
+    /// vendor option
+    ///
+    /// If vendor-id is specified, check only option with that particular
+    /// enterprise-id. If vendor-id is 0, check any vendor option, regardless
+    /// of its enterprise-id value.
+    ///
+    /// If FieldType is NONE, get specified suboption represented by option_code
+    /// and represent it as specified by repr.
+    ///
+    /// If FieldType is ENTERPRISE_ID, return value of the enterprise-id field
+    /// or "" if there's no vendor option.
+    ///
+    /// @throw EvalTypeError for any other FieldType values.
+    ///
+    /// The parameters passed are:
+    ///
+    /// @param pkt - vendor options will be searched for here.
+    /// @param values - the evaluated value will be pushed here.
+    virtual void evaluate(Pkt& pkt, ValueStack& values);
+
+protected:
+    /// @brief Attempts to get a suboption.
+    ///
+    /// This method overrides behavior of TokenOption method. It attempts to retrieve
+    /// the sub-option of the vendor option. Using derived method allows usage of
+    /// TokenOption routines.
+    ///
+    /// @param pkt vendor option will be searched here.
+    /// @return suboption of the vendor option (if exists)
+    virtual OptionPtr getOption(Pkt& pkt);
+
+    /// @brief Universe (V4 or V6)
+    ///
+    /// We need to remember it, because depending on the universe, the code needs
+    /// to retrieve either option 125 (DHCPv4) or 17 (DHCPv6).
+    Option::Universe universe_;
+
+    /// @brief Enterprise-id value
+    ///
+    /// Yeah, I know it's technically should be called enterprise-id, but that's
+    /// too long and everyone calls it vendor-id.
+    uint32_t vendor_id_;
+
+    /// @brief Specifies which field should be accessed.
+    FieldType field_;
+};
+
+/// @brief Token that represents vendor class options in DHCPv4 and DHCPv6.
+///
+/// It covers vendor independent vendor information option (124, DHCPv4)
+/// and vendor option (16, DHCPv6). Contrary to vendor options, vendor class
+/// options don't have suboptions, but have data chunks (tuples) instead.
+/// Therefore they're not referenced by option codes, but by indexes.
+/// The first data chunk is data[0], the second is data[1] etc.
+///
+/// This class is derived from OptionVendor to take advantage of the
+/// enterprise handling field and field type.
+///
+/// It can represent the following expressions:
+/// vendor-class[4491].exist
+/// vendor-class[*].exist
+/// vendor-class[*].enterprise
+/// vendor-class[4491].data - content of the opaque-data of the first tuple
+/// vendor-class[4491].data[3] - content of the opaque-data of the 4th tuple
+class TokenVendorClass : public TokenVendor {
+public:
+
+    /// @brief This constructor is used to access fields.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param vendor_id value of enterprise-id field (0 means any)
+    /// @param repr representation type (EXISTS or HEX)
+    TokenVendorClass(Option::Universe u, uint32_t vendor_id, RepresentationType repr);
+
+    /// @brief This constructor is used to access data chunks.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param vendor_id value of enterprise-id field (0 means any)
+    /// @param field type of the field (usually DATA or ENTERPRISE)
+    /// @param index specifies which data chunk to retrieve
+    TokenVendorClass(Option::Universe u, uint32_t vendor_id, FieldType field,
+                     uint16_t index = 0);
+
+    /// @brief Returns data index.
+    ///
+    /// Used in testing.
+    /// @return data index (specifies which data chunk to retrieve)
+    uint16_t getDataIndex() const;
+
+protected:
+
+    /// @brief This is a method for evaluating a packet.
+    ///
+    /// Depending on the value of vendor_id, field type, representation and
+    /// option code, it will attempt to return specified characteristic of the
+    /// vendor option
+    ///
+    /// If vendor-id is specified, check only option with that particular
+    /// enterprise-id. If vendor-id is 0, check any vendor option, regardless
+    /// of its enterprise-id value.
+    ///
+    /// If FieldType is ENTERPRISE_ID, return value of the enterprise-id field
+    /// or "" if there's no vendor option.
+    ///
+    /// If FieldType is DATA, get specified data chunk represented by index_.
+    ///
+    /// If FieldType is EXISTS, return true of vendor-id matches.
+    ///
+    /// @throw EvalTypeError for any other FieldType values.
+    ///
+    /// The parameters passed are:
+    ///
+    /// @param pkt - vendor options will be searched for here.
+    /// @param values - the evaluated value will be pushed here.
+    void evaluate(Pkt& pkt, ValueStack& values);
+
+    /// @brief Data chunk index.
+    uint16_t index_;
+};
+
 }; // end of isc::dhcp namespace
 }; // end of isc namespace