From: Francis Dupont Date: Tue, 1 Oct 2019 23:22:09 +0000 (+0200) Subject: [219-allow-an-option-value-to-be-set-from-an-expression] Checkpoint: almost finished X-Git-Tag: Kea-1.7.1~42 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=39d8d30f72fdeea51a7ee63efe9827208abaa8e0;p=thirdparty%2Fkea.git [219-allow-an-option-value-to-be-set-from-an-expression] Checkpoint: almost finished --- diff --git a/configure.ac b/configure.ac index ff3d33f705..b560dd6e8d 100755 --- a/configure.ac +++ b/configure.ac @@ -1695,6 +1695,7 @@ AC_CONFIG_FILES([Makefile src/hooks/Makefile src/hooks/dhcp/Makefile src/hooks/dhcp/flex_option/Makefile + src/hooks/dhcp/flex_option/libloadtests/Makefile src/hooks/dhcp/flex_option/tests/Makefile src/hooks/dhcp/high_availability/Makefile src/hooks/dhcp/high_availability/tests/Makefile @@ -1702,11 +1703,11 @@ AC_CONFIG_FILES([Makefile src/hooks/dhcp/lease_cmds/tests/Makefile src/hooks/dhcp/mysql_cb/Makefile src/hooks/dhcp/mysql_cb/tests/Makefile + src/hooks/dhcp/stat_cmds/Makefile + src/hooks/dhcp/stat_cmds/tests/Makefile src/hooks/dhcp/user_chk/Makefile src/hooks/dhcp/user_chk/tests/Makefile src/hooks/dhcp/user_chk/tests/test_data_files_config.h - src/hooks/dhcp/stat_cmds/Makefile - src/hooks/dhcp/stat_cmds/tests/Makefile src/lib/Makefile src/lib/asiodns/Makefile src/lib/asiodns/tests/Makefile diff --git a/src/hooks/dhcp/flex_option/Makefile.am b/src/hooks/dhcp/flex_option/Makefile.am index 8917366118..c5c8d45dc0 100644 --- a/src/hooks/dhcp/flex_option/Makefile.am +++ b/src/hooks/dhcp/flex_option/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = . tests +SUBDIRS = . libloadtests tests AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib AM_CPPFLAGS += $(BOOST_INCLUDES) diff --git a/src/hooks/dhcp/flex_option/flex_option.cc b/src/hooks/dhcp/flex_option/flex_option.cc index d36cf48052..c1f4703da7 100644 --- a/src/hooks/dhcp/flex_option/flex_option.cc +++ b/src/hooks/dhcp/flex_option/flex_option.cc @@ -24,7 +24,8 @@ using namespace std; namespace isc { namespace flex_option { -FlexOptionImpl::OptionConfig::OptionConfig(uint16_t code) : code_(code) { +FlexOptionImpl::OptionConfig::OptionConfig(uint16_t code) + : code_(code), action_(NONE) { } FlexOptionImpl::OptionConfig::~OptionConfig() { @@ -217,6 +218,11 @@ FlexOptionImpl::parseOptionConfig(ConstElementPtr option) { << remove << "] error: " << ex.what()); } } + + if (opt_cfg->getAction() == NONE) { + isc_throw(BadValue, "no action: " << option->str()); + } + option_config_map_[code] = opt_cfg; } } // end of namespace flex_option diff --git a/src/hooks/dhcp/flex_option/flex_option.h b/src/hooks/dhcp/flex_option/flex_option.h index ffb4a62fd0..fb820d312b 100644 --- a/src/hooks/dhcp/flex_option/flex_option.h +++ b/src/hooks/dhcp/flex_option/flex_option.h @@ -194,6 +194,14 @@ public: } } +protected: + /// @brief Get a mutable reference to the option config map + /// + /// @return The option config map. + OptionConfigMap& getMutableOptionConfigMap() { + return (option_config_map_); + } + private: /// @brief The option config map (code and pointer to option config). OptionConfigMap option_config_map_; diff --git a/src/hooks/dhcp/flex_option/libloadtests/.gitignore b/src/hooks/dhcp/flex_option/libloadtests/.gitignore new file mode 100644 index 0000000000..35b5e99aee --- /dev/null +++ b/src/hooks/dhcp/flex_option/libloadtests/.gitignore @@ -0,0 +1 @@ +/html diff --git a/src/hooks/dhcp/flex_option/libloadtests/Makefile.am b/src/hooks/dhcp/flex_option/libloadtests/Makefile.am new file mode 100644 index 0000000000..6ce02d0644 --- /dev/null +++ b/src/hooks/dhcp/flex_option/libloadtests/Makefile.am @@ -0,0 +1,54 @@ +SUBDIRS = . + +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib +AM_CPPFLAGS += -I$(top_builddir)/src/hooks/dhcp/flex_option -I$(top_srcdir)/src/hooks/dhcp/flex_option +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CPPFLAGS += -DFLEX_OPTION_LIB_SO=\"$(abs_top_builddir)/src/hooks/dhcp/flex_option/.libs/libdhcp_flex_option.so\" +AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" + +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +if USE_STATIC_LINK +AM_LDFLAGS = -static +endif + +# Unit test data files need to get installed. +EXTRA_DIST = + +CLEANFILES = *.gcno *.gcda + +# TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) +LOG_COMPILER = $(LIBTOOL) +AM_LOG_FLAGS = --mode=execute + +TESTS = +if HAVE_GTEST +TESTS += flex_option_unittests + +flex_option_unittests_SOURCES = run_unittests.cc +flex_option_unittests_SOURCES += load_unload_unittests.cc + +flex_option_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES) + +flex_option_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) + +flex_option_unittests_CXXFLAGS = $(AM_CXXFLAGS) + +flex_option_unittests_LDADD = $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la +flex_option_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la +flex_option_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la +flex_option_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la +flex_option_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la +flex_option_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la +flex_option_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +flex_option_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la +flex_option_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la +flex_option_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la +flex_option_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la +flex_option_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +flex_option_unittests_LDADD += $(LOG4CPLUS_LIBS) +flex_option_unittests_LDADD += $(CRYPTO_LIBS) +flex_option_unittests_LDADD += $(BOOST_LIBS) +flex_option_unittests_LDADD += $(GTEST_LDADD) +endif +noinst_PROGRAMS = $(TESTS) diff --git a/src/hooks/dhcp/flex_option/tests/load_unload_unittests.cc b/src/hooks/dhcp/flex_option/libloadtests/load_unload_unittests.cc similarity index 95% rename from src/hooks/dhcp/flex_option/tests/load_unload_unittests.cc rename to src/hooks/dhcp/flex_option/libloadtests/load_unload_unittests.cc index f0c339dcf1..d9a386a9bd 100644 --- a/src/hooks/dhcp/flex_option/tests/load_unload_unittests.cc +++ b/src/hooks/dhcp/flex_option/libloadtests/load_unload_unittests.cc @@ -12,20 +12,21 @@ #include -#include +#include #include + #include -#include #include using namespace std; using namespace isc; -using namespace hooks; +using namespace isc::hooks; using namespace isc::data; +using namespace isc::dhcp; namespace { -/// @brief Test fixture for testing loading and unloading the flex-id library +/// @brief Test fixture for testing loading and unloading the flex-option library class LibLoadTest : public ::testing::Test { public: /// @brief Constructor diff --git a/src/hooks/dhcp/flex_option/libloadtests/run_unittests.cc b/src/hooks/dhcp/flex_option/libloadtests/run_unittests.cc new file mode 100644 index 0000000000..5805b42c51 --- /dev/null +++ b/src/hooks/dhcp/flex_option/libloadtests/run_unittests.cc @@ -0,0 +1,19 @@ +// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#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/hooks/dhcp/flex_option/tests/Makefile.am b/src/hooks/dhcp/flex_option/tests/Makefile.am index 1623fe888d..dbeedf69ed 100644 --- a/src/hooks/dhcp/flex_option/tests/Makefile.am +++ b/src/hooks/dhcp/flex_option/tests/Makefile.am @@ -26,8 +26,7 @@ if HAVE_GTEST TESTS += flex_option_unittests flex_option_unittests_SOURCES = run_unittests.cc -flex_option_unittests_SOURCES += callout_unittests.cc -flex_option_unittests_SOURCES += load_unload_unittests.cc +flex_option_unittests_SOURCES += flex_option_unittests.cc flex_option_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES) @@ -35,7 +34,8 @@ flex_option_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS flex_option_unittests_CXXFLAGS = $(AM_CXXFLAGS) -flex_option_unittests_LDADD = $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la +flex_option_unittests_LDADD = $(top_builddir)/src/hooks/dhcp/flex_option/libflex_option.la +flex_option_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la flex_option_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la flex_option_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la flex_option_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la diff --git a/src/hooks/dhcp/flex_option/tests/callout_unittests.cc b/src/hooks/dhcp/flex_option/tests/callout_unittests.cc deleted file mode 100644 index cbb38f7526..0000000000 --- a/src/hooks/dhcp/flex_option/tests/callout_unittests.cc +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC") -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -/// @file This file contains tests which verify flexible option callouts. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace std; -using namespace isc; -using namespace isc::dhcp; -using namespace isc::hooks; -using namespace isc::flex_option; - -extern "C" { -extern int pkt4_send(CalloutHandle& handle); -extern int pkt6_send(CalloutHandle& handle); -} - -namespace { - -} // end of anonymous namespace diff --git a/src/hooks/dhcp/flex_option/tests/flex_option_unittests.cc b/src/hooks/dhcp/flex_option/tests/flex_option_unittests.cc new file mode 100644 index 0000000000..de41cefde8 --- /dev/null +++ b/src/hooks/dhcp/flex_option/tests/flex_option_unittests.cc @@ -0,0 +1,885 @@ +// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file This file contains tests which verify flexible option. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace isc; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::eval; +using namespace isc::hooks; +using namespace isc::flex_option; + +extern "C" { +extern int pkt4_send(CalloutHandle& handle); +extern int pkt6_send(CalloutHandle& handle); +} + +namespace { + +/// @brief Test class derived from FlexOptionImpl +class TestFlexOptionImpl : public FlexOptionImpl { +public: + using FlexOptionImpl::getMutableOptionConfigMap; +}; + +/// @brief The type of shared pointers to TestFlexOptionImpl +typedef boost::shared_ptr TestFlexOptionImplPtr; + +/// @brief Test fixture for testing the Flex Option library. +class FlexOptionTest : public ::testing::Test { +public: + /// @brief Constructor. + FlexOptionTest() { + impl_.reset(new TestFlexOptionImpl()); + CfgMgr::instance().setFamily(AF_INET); + } + + /// @brief Destructor. + virtual ~FlexOptionTest() { + CfgMgr::instance().setFamily(AF_INET); + impl_.reset(); + } + + /// @brief Flex Option implementation. + TestFlexOptionImplPtr impl_; +}; + +// Verify that the configuration must exist. +TEST_F(FlexOptionTest, noConfig) { + ElementPtr options; + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that the configuration must be a list. +TEST_F(FlexOptionTest, configNotList) { + ElementPtr options = Element::createMap(); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that the configuration can be the empty list. +TEST_F(FlexOptionTest, configEmpty) { + ElementPtr options = Element::createList(); + EXPECT_NO_THROW(impl_->configure(options)); +} + +// Verify that an option configuration must exist. +TEST_F(FlexOptionTest, noOptionConfig) { + ElementPtr options = Element::createList(); + ElementPtr option; + options->add(option); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that an option configuration must be a map. +TEST_F(FlexOptionTest, optionConfigNotMap) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createList(); + options->add(option); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that an option configuration must have code or name. +TEST_F(FlexOptionTest, optionConfigNoCodeName) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that the v4 option code must be in [1..254]. +TEST_F(FlexOptionTest, optionConfigBadCode4) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr add = Element::create(string("'ab'")); + option->set("add", add); + ElementPtr code = Element::create(-1); + option->set("code", code); + EXPECT_THROW(impl_->configure(options), OutOfRange); + + code = Element::create(DHO_PAD); + option->set("code", code); + EXPECT_THROW(impl_->configure(options), BadValue); + + code = Element::create(DHO_END); + option->set("code", code); + EXPECT_THROW(impl_->configure(options), BadValue); + + code = Element::create(256); + option->set("code", code); + EXPECT_THROW(impl_->configure(options), OutOfRange); + + code = Element::create(1); + option->set("code", code); + EXPECT_NO_THROW(impl_->configure(options)); + + code = Element::create(254); + option->set("code", code); + EXPECT_NO_THROW(impl_->configure(options)); +} + +// Verify that the v6 option code must be in [1..65535]. +TEST_F(FlexOptionTest, optionConfigBadCode6) { + CfgMgr::instance().setFamily(AF_INET6); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr add = Element::create(string("'ab'")); + option->set("add", add); + ElementPtr code = Element::create(-1); + option->set("code", code); + EXPECT_THROW(impl_->configure(options), OutOfRange); + + code = Element::create(0); + option->set("code", code); + EXPECT_THROW(impl_->configure(options), BadValue); + + code = Element::create(65536); + option->set("code", code); + EXPECT_THROW(impl_->configure(options), OutOfRange); + + code = Element::create(1); + option->set("code", code); + EXPECT_NO_THROW(impl_->configure(options)); + + code = Element::create(65535); + option->set("code", code); + EXPECT_NO_THROW(impl_->configure(options)); +} + +// Verify that the name must be a string. +TEST_F(FlexOptionTest, optionConfigBadName) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr add = Element::create(string("'ab'")); + option->set("add", add); + ElementPtr name = Element::create(true); + option->set("name", name); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that the name must not be empty. +TEST_F(FlexOptionTest, optionConfigEmptyName) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr add = Element::create(string("'ab'")); + option->set("add", add); + ElementPtr name = Element::create(string()); + option->set("name",name); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that the name must be a known option. +TEST_F(FlexOptionTest, optionConfigUnknownName) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr add = Element::create(string("'ab'")); + option->set("add", add); + ElementPtr name = Element::create(string("foobar")); + option->set("name",name); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that the name can be a standard option. +TEST_F(FlexOptionTest, optionConfigStandardName) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr add = Element::create(string("'ab'")); + option->set("add", add); + ElementPtr name = Element::create(string("host-name")); + option->set("name", name); + EXPECT_NO_THROW(impl_->configure(options)); + + auto map = impl_->getOptionConfigMap(); + EXPECT_EQ(1, map.count(DHO_HOST_NAME)); +} + +// Verify that the name can be an user defined option. +TEST_F(FlexOptionTest, optionConfigDefinedName) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-option", 222, "string")); + defs.addItem(def, DHCP4_OPTION_SPACE); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr add = Element::create(string("'ab'")); + option->set("add", add); + ElementPtr name = Element::create(string("my-option")); + option->set("name", name); + EXPECT_NO_THROW(impl_->configure(options)); + + auto map = impl_->getOptionConfigMap(); + EXPECT_EQ(1, map.count(222)); +} + +// Last resort is only option 43... + +// Verify that the name must match the code. +TEST_F(FlexOptionTest, optionConfigCodeNameMismatch) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr add = Element::create(string("'ab'")); + option->set("add", add); + ElementPtr code = Element::create(DHO_HOST_NAME + 1); + option->set("code", code); + ElementPtr name = Element::create(string("host-name")); + option->set("name", name); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that an option can be configured only once. +TEST_F(FlexOptionTest, optionConfigTwice) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr add = Element::create(string("'ab'")); + option->set("add", add); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + + options->add(option); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that the add value must be a string. +TEST_F(FlexOptionTest, optionConfigAddNotString) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr add = Element::create(true); + option->set("add", add); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that the add value must not be empty. +TEST_F(FlexOptionTest, optionConfigEmptyAdd) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr add = Element::create(string()); + option->set("add", add); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that the add value must parse. +TEST_F(FlexOptionTest, optionConfigBadAdd) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr add = Element::create(string("if")); + option->set("add", add); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that a valid v4 add value is accepted. +TEST_F(FlexOptionTest, optionConfigAdd4) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr add = Element::create(string("'abc'")); + option->set("add", add); + EXPECT_NO_THROW(impl_->configure(options)); + + auto map = impl_->getOptionConfigMap(); + FlexOptionImpl::OptionConfigPtr opt_cfg; + ASSERT_NO_THROW(opt_cfg = map.at(DHO_HOST_NAME)); + ASSERT_TRUE(opt_cfg); + EXPECT_EQ(DHO_HOST_NAME, opt_cfg->getCode()); + EXPECT_EQ(FlexOptionImpl::ADD, opt_cfg->getAction()); + EXPECT_EQ("'abc'", opt_cfg->getText()); + + ExpressionPtr expr = opt_cfg->getExpr(); + ASSERT_TRUE(expr); + ASSERT_EQ(1, expr->size()); + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345)); + ValueStack values; + EXPECT_NO_THROW(expr->at(0)->evaluate(*pkt4, values)); + ASSERT_EQ(1, values.size()); + EXPECT_EQ("abc", values.top()); +} + +// Verify that a valid v6 add value is accepted. +TEST_F(FlexOptionTest, optionConfigAdd6) { + CfgMgr::instance().setFamily(AF_INET6); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(D6O_BOOTFILE_URL); + option->set("code", code); + ElementPtr add = Element::create(string("'abc'")); + option->set("add", add); + EXPECT_NO_THROW(impl_->configure(options)); + + auto map = impl_->getOptionConfigMap(); + FlexOptionImpl::OptionConfigPtr opt_cfg; + ASSERT_NO_THROW(opt_cfg = map.at(D6O_BOOTFILE_URL)); + ASSERT_TRUE(opt_cfg); + EXPECT_EQ(D6O_BOOTFILE_URL, opt_cfg->getCode()); + EXPECT_EQ(FlexOptionImpl::ADD, opt_cfg->getAction()); + EXPECT_EQ("'abc'", opt_cfg->getText()); + + ExpressionPtr expr = opt_cfg->getExpr(); + ASSERT_TRUE(expr); + ASSERT_EQ(1, expr->size()); + Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 12345)); + ValueStack values; + EXPECT_NO_THROW(expr->at(0)->evaluate(*pkt6, values)); + ASSERT_EQ(1, values.size()); + EXPECT_EQ("abc", values.top()); +} + +// Verify that the supersede value must be a string. +TEST_F(FlexOptionTest, optionConfigSupersedeNotString) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr supersede = Element::create(true); + option->set("supersede", supersede); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that the supersede value must not be empty. +TEST_F(FlexOptionTest, optionConfigEmptySupersede) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr supersede = Element::create(string()); + option->set("supersede", supersede); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that the supersede value must parse. +TEST_F(FlexOptionTest, optionConfigBadSupersede) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr supersede = Element::create(string("if")); + option->set("supersede", supersede); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that a valid v4 supersede value is accepted. +TEST_F(FlexOptionTest, optionConfigSupersede4) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr supersede = Element::create(string("'abc'")); + option->set("supersede", supersede); + EXPECT_NO_THROW(impl_->configure(options)); + + auto map = impl_->getOptionConfigMap(); + FlexOptionImpl::OptionConfigPtr opt_cfg; + ASSERT_NO_THROW(opt_cfg = map.at(DHO_HOST_NAME)); + ASSERT_TRUE(opt_cfg); + EXPECT_EQ(DHO_HOST_NAME, opt_cfg->getCode()); + EXPECT_EQ(FlexOptionImpl::SUPERSEDE, opt_cfg->getAction()); + EXPECT_EQ("'abc'", opt_cfg->getText()); + + ExpressionPtr expr = opt_cfg->getExpr(); + ASSERT_TRUE(expr); + ASSERT_EQ(1, expr->size()); + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345)); + ValueStack values; + EXPECT_NO_THROW(expr->at(0)->evaluate(*pkt4, values)); + ASSERT_EQ(1, values.size()); + EXPECT_EQ("abc", values.top()); +} + +// Verify that a valid v6 supersede value is accepted. +TEST_F(FlexOptionTest, optionConfigSupersede6) { + CfgMgr::instance().setFamily(AF_INET6); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(D6O_BOOTFILE_URL); + option->set("code", code); + ElementPtr supersede = Element::create(string("'abc'")); + option->set("supersede", supersede); + EXPECT_NO_THROW(impl_->configure(options)); + + auto map = impl_->getOptionConfigMap(); + FlexOptionImpl::OptionConfigPtr opt_cfg; + ASSERT_NO_THROW(opt_cfg = map.at(D6O_BOOTFILE_URL)); + ASSERT_TRUE(opt_cfg); + EXPECT_EQ(D6O_BOOTFILE_URL, opt_cfg->getCode()); + EXPECT_EQ(FlexOptionImpl::SUPERSEDE, opt_cfg->getAction()); + EXPECT_EQ("'abc'", opt_cfg->getText()); + + ExpressionPtr expr = opt_cfg->getExpr(); + ASSERT_TRUE(expr); + ASSERT_EQ(1, expr->size()); + Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 12345)); + ValueStack values; + EXPECT_NO_THROW(expr->at(0)->evaluate(*pkt6, values)); + ASSERT_EQ(1, values.size()); + EXPECT_EQ("abc", values.top()); +} + +// Verify that the remove value must be a string. +TEST_F(FlexOptionTest, optionConfigRemoveNotString) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr remove = Element::create(true); + option->set("remove", remove); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that the remove value must not be empty. +TEST_F(FlexOptionTest, optionConfigEmptyRemove) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr remove = Element::create(string()); + option->set("remove", remove); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that the remove value must parse. +TEST_F(FlexOptionTest, optionConfigBadRemove) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr remove = Element::create(string("'abc'")); + option->set("remove", remove); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that a valid v4 remove value is accepted. +TEST_F(FlexOptionTest, optionConfigRemove4) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr remove = Element::create(string("'abc' == 'abc'")); + option->set("remove", remove); + EXPECT_NO_THROW(impl_->configure(options)); + + auto map = impl_->getOptionConfigMap(); + FlexOptionImpl::OptionConfigPtr opt_cfg; + ASSERT_NO_THROW(opt_cfg = map.at(DHO_HOST_NAME)); + ASSERT_TRUE(opt_cfg); + EXPECT_EQ(DHO_HOST_NAME, opt_cfg->getCode()); + EXPECT_EQ(FlexOptionImpl::REMOVE, opt_cfg->getAction()); + EXPECT_EQ("'abc' == 'abc'", opt_cfg->getText()); + + ExpressionPtr expr = opt_cfg->getExpr(); + ASSERT_TRUE(expr); + ASSERT_EQ(3, expr->size()); + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345)); + ValueStack values; + EXPECT_NO_THROW(expr->at(0)->evaluate(*pkt4, values)); + ASSERT_EQ(1, values.size()); + EXPECT_EQ("abc", values.top()); + EXPECT_NO_THROW(expr->at(1)->evaluate(*pkt4, values)); + ASSERT_EQ(2, values.size()); + EXPECT_NO_THROW(expr->at(2)->evaluate(*pkt4, values)); + ASSERT_EQ(1, values.size()); + EXPECT_EQ("true", values.top()); +} + +// Verify that a valid v6 remove value is accepted. +TEST_F(FlexOptionTest, optionConfigRemove6) { + CfgMgr::instance().setFamily(AF_INET6); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(D6O_BOOTFILE_URL); + option->set("code", code); + ElementPtr remove = Element::create(string("'abc' == 'abc'")); + option->set("remove", remove); + EXPECT_NO_THROW(impl_->configure(options)); + + auto map = impl_->getOptionConfigMap(); + FlexOptionImpl::OptionConfigPtr opt_cfg; + ASSERT_NO_THROW(opt_cfg = map.at(D6O_BOOTFILE_URL)); + ASSERT_TRUE(opt_cfg); + EXPECT_EQ(D6O_BOOTFILE_URL, opt_cfg->getCode()); + EXPECT_EQ(FlexOptionImpl::REMOVE, opt_cfg->getAction()); + EXPECT_EQ("'abc' == 'abc'", opt_cfg->getText()); + + ExpressionPtr expr = opt_cfg->getExpr(); + ASSERT_TRUE(expr); + ASSERT_EQ(3, expr->size()); + Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 12345)); + ValueStack values; + EXPECT_NO_THROW(expr->at(0)->evaluate(*pkt6, values)); + ASSERT_EQ(1, values.size()); + EXPECT_EQ("abc", values.top()); + EXPECT_NO_THROW(expr->at(1)->evaluate(*pkt6, values)); + ASSERT_EQ(2, values.size()); + EXPECT_NO_THROW(expr->at(2)->evaluate(*pkt6, values)); + ASSERT_EQ(1, values.size()); + EXPECT_EQ("true", values.top()); +} + +// Verify that multiple actions are not accepted. +TEST_F(FlexOptionTest, optionConfigMultipleAction) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + + // add and supersede. + ElementPtr add = Element::create(string("'abc'")); + option->set("add", add); + ElementPtr supersede = Element::create(string("'abc'")); + option->set("supersede", supersede); + EXPECT_THROW(impl_->configure(options), BadValue); + + // supersede and remove. + option->remove("add"); + ElementPtr remove = Element::create(string("'abc' == 'abc'")); + option->set("remove", remove); + EXPECT_THROW(impl_->configure(options), BadValue); + + // add and remove. + option->remove("supersede"); + option->set("add", add); + EXPECT_THROW(impl_->configure(options), BadValue); +} + +// Verify that multiple options are accepted. +TEST_F(FlexOptionTest, optionConfigList) { + ElementPtr options = Element::createList(); + + ElementPtr option1 = Element::createMap(); + options->add(option1); + ElementPtr code1 = Element::create(DHO_HOST_NAME); + option1->set("code", code1); + ElementPtr add1 = Element::create(string("'abc'")); + option1->set("add", add1); + + ElementPtr option2 = Element::createMap(); + options->add(option2); + ElementPtr code2 = Element::create(DHO_ROOT_PATH); + option2->set("code", code2); + ElementPtr supersede2 = Element::create(string("'/'")); + option2->set("supersede", supersede2); + + EXPECT_NO_THROW(impl_->configure(options)); + + auto map = impl_->getOptionConfigMap(); + EXPECT_EQ(2, map.size()); + + FlexOptionImpl::OptionConfigPtr opt1_cfg; + ASSERT_NO_THROW(opt1_cfg = map.at(DHO_HOST_NAME)); + ASSERT_TRUE(opt1_cfg); + EXPECT_EQ(DHO_HOST_NAME, opt1_cfg->getCode()); + EXPECT_EQ(FlexOptionImpl::ADD, opt1_cfg->getAction()); + EXPECT_EQ("'abc'", opt1_cfg->getText()); + + FlexOptionImpl::OptionConfigPtr opt2_cfg; + ASSERT_NO_THROW(opt2_cfg = map.at(DHO_ROOT_PATH)); + ASSERT_TRUE(opt2_cfg); + EXPECT_EQ(DHO_ROOT_PATH, opt2_cfg->getCode()); + EXPECT_EQ(FlexOptionImpl::SUPERSEDE, opt2_cfg->getAction()); + EXPECT_EQ("'/'", opt2_cfg->getText()); +} + +// Verify that empty option config list does nothing. +TEST_F(FlexOptionTest, processEmpty) { + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + string response_txt = response->toText(); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); +} + +// Verify that NONE action really does nothing. +TEST_F(FlexOptionTest, processNone) { + CfgMgr::instance().setFamily(AF_INET6); + + FlexOptionImpl::OptionConfigPtr + opt_cfg(new FlexOptionImpl::OptionConfig(D6O_BOOTFILE_URL)); + EXPECT_EQ(FlexOptionImpl::NONE, opt_cfg->getAction()); + auto map = impl_->getMutableOptionConfigMap(); + map[DHO_HOST_NAME] = opt_cfg; + + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345)); + Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345)); + string response_txt = response->toText(); + + EXPECT_NO_THROW(impl_->process(Option::V6, query, response)); + + EXPECT_EQ(response_txt, response->toText()); +} + +// Verify that ADD action adds the specified option. +TEST_F(FlexOptionTest, processAdd) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr add = Element::create(string("'abc'")); + option->set("add", add); + EXPECT_NO_THROW(impl_->configure(options)); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + EXPECT_FALSE(response->getOption(DHO_HOST_NAME)); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + OptionPtr opt = response->getOption(DHO_HOST_NAME); + ASSERT_TRUE(opt); + EXPECT_EQ(DHO_HOST_NAME, opt->getType()); + const OptionBuffer& buffer = opt->getData(); + ASSERT_EQ(3, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "abc", 3)); +} + +// Verify that ADD action does not add an already existing option. +TEST_F(FlexOptionTest, processAddExisting) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(D6O_BOOTFILE_URL); + option->set("code", code); + ElementPtr add = Element::create(string("'abc'")); + option->set("add", add); + EXPECT_NO_THROW(impl_->configure(options)); + + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345)); + Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345)); + OptionStringPtr str(new OptionString(Option::V6, D6O_BOOTFILE_URL, "http")); + response->addOption(str); + + EXPECT_NO_THROW(impl_->process(Option::V6, query, response)); + + OptionPtr opt = response->getOption(D6O_BOOTFILE_URL); + ASSERT_TRUE(opt); + EXPECT_EQ(D6O_BOOTFILE_URL, opt->getType()); + EXPECT_EQ("http", opt->toString()); +} + +// Verify that ADD action does not add an empty value. +TEST_F(FlexOptionTest, processAddEmpty) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr add = Element::create(string("''")); + option->set("add", add); + EXPECT_NO_THROW(impl_->configure(options)); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + string response_txt = response->toText(); + EXPECT_FALSE(response->getOption(DHO_HOST_NAME)); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + EXPECT_FALSE(response->getOption(DHO_HOST_NAME)); +} + +// Verify that SUPERSEDE action supersedes the specified option. +TEST_F(FlexOptionTest, processSupersede) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr supersede = Element::create(string("'abc'")); + option->set("supersede", supersede); + EXPECT_NO_THROW(impl_->configure(options)); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + EXPECT_FALSE(response->getOption(DHO_HOST_NAME)); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + OptionPtr opt = response->getOption(DHO_HOST_NAME); + ASSERT_TRUE(opt); + EXPECT_EQ(DHO_HOST_NAME, opt->getType()); + const OptionBuffer& buffer = opt->getData(); + ASSERT_EQ(3, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "abc", 3)); +} + +// Verify that SUPERSEDE action supersedes an already existing option. +TEST_F(FlexOptionTest, processSupersedeExisting) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(D6O_BOOTFILE_URL); + option->set("code", code); + ElementPtr supersede = Element::create(string("'abc'")); + option->set("supersede", supersede); + EXPECT_NO_THROW(impl_->configure(options)); + + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345)); + Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345)); + OptionStringPtr str(new OptionString(Option::V6, D6O_BOOTFILE_URL, "http")); + response->addOption(str); + + EXPECT_NO_THROW(impl_->process(Option::V6, query, response)); + + OptionPtr opt = response->getOption(D6O_BOOTFILE_URL); + ASSERT_TRUE(opt); + EXPECT_EQ(D6O_BOOTFILE_URL, opt->getType()); + const OptionBuffer& buffer = opt->getData(); + ASSERT_EQ(3, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "abc", 3)); +} + +// Verify that SUPERSEDE action does not supersede an empty value. +TEST_F(FlexOptionTest, processSupersedeEmpty) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr supersede = Element::create(string("''")); + option->set("supersede", supersede); + EXPECT_NO_THROW(impl_->configure(options)); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + string response_txt = response->toText(); + EXPECT_FALSE(response->getOption(DHO_HOST_NAME)); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + EXPECT_FALSE(response->getOption(DHO_HOST_NAME)); + + // Empty value does not remove existing values. + OptionStringPtr str(new OptionString(Option::V4, DHO_HOST_NAME, "abc")); + response->addOption(str); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + OptionPtr opt = response->getOption(DHO_HOST_NAME); + ASSERT_TRUE(opt); + EXPECT_EQ(DHO_HOST_NAME, opt->getType()); + const OptionBuffer& buffer = opt->getData(); + ASSERT_EQ(3, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "abc", 3)); +} + +// Verify that REMOVE action removes an already existing option. +TEST_F(FlexOptionTest, processRemove) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(D6O_BOOTFILE_URL); + option->set("code", code); + ElementPtr remove = Element::create(string("'abc' == 'abc'")); + option->set("remove", remove); + EXPECT_NO_THROW(impl_->configure(options)); + + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345)); + Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345)); + OptionStringPtr str(new OptionString(Option::V6, D6O_BOOTFILE_URL, "http")); + response->addOption(str); + + EXPECT_NO_THROW(impl_->process(Option::V6, query, response)); + + EXPECT_FALSE(response->getOption(D6O_BOOTFILE_URL)); +} + +// Verify that REMOVE action does nothing if the option is not present. +TEST_F(FlexOptionTest, processRemoveNoOption) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_HOST_NAME); + option->set("code", code); + ElementPtr remove = Element::create(string("'abc' == 'abc'")); + option->set("remove", remove); + EXPECT_NO_THROW(impl_->configure(options)); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + string response_txt = response->toText(); + EXPECT_FALSE(response->getOption(DHO_HOST_NAME)); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + EXPECT_FALSE(response->getOption(DHO_HOST_NAME)); +} + +// Verify that REMOVE action does nothing when the expression evaluates to false. +TEST_F(FlexOptionTest, processRemoveFalse) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(D6O_BOOTFILE_URL); + option->set("code", code); + ElementPtr remove = Element::create(string("'abc' == 'xyz'")); + option->set("remove", remove); + EXPECT_NO_THROW(impl_->configure(options)); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + OptionStringPtr str(new OptionString(Option::V6, D6O_BOOTFILE_URL, "http")); + response->addOption(str); + string response_txt = response->toText(); + + EXPECT_NO_THROW(impl_->process(Option::V6, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + EXPECT_TRUE(response->getOption(D6O_BOOTFILE_URL)); +} + +} // end of anonymous namespace