Commit ae2d3153 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[4271] Vendor and Vendor-class tokens implemented.

parent 71a38f02
......@@ -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
......
......@@ -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)
......
......@@ -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
......
......@@ -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);
......
......@@ -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
......
......@@ -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);
}
};
This diff is collapsed.
......@@ -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)
{