diff --git a/src/bin/dhcp4/json_config_parser.cc b/src/bin/dhcp4/json_config_parser.cc index d9c381e33c..95e883c030 100644 --- a/src/bin/dhcp4/json_config_parser.cc +++ b/src/bin/dhcp4/json_config_parser.cc @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -514,6 +513,12 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { // Remove any existing timers. TimerMgr::instance()->unregisterTimers(); + // Revert any runtime option definitions configured so far and not committed. + LibDHCP::revertRuntimeOptionDefs(); + // Let's set empty container in case a user hasn't specified any configuration + // for option definitions. This is equivalent to commiting empty container. + LibDHCP::setRuntimeOptionDefs(OptionDefSpaceContainer()); + // Some of the values specified in the configuration depend on // other values. Typically, the values in the subnet4 structure // depend on the global values. Also, option values configuration @@ -700,6 +705,9 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { // Rollback changes as the configuration parsing failed. if (rollback) { globalContext().reset(new ParserContext(original_context)); + // Revert to original configuration of runtime option definitions + // in the libdhcp++. + LibDHCP::revertRuntimeOptionDefs(); return (answer); } diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc index 02d4e3bedc..21dfea05b6 100644 --- a/src/bin/dhcp4/tests/config_parser_unittest.cc +++ b/src/bin/dhcp4/tests/config_parser_unittest.cc @@ -1346,6 +1346,11 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) { ASSERT_TRUE(status); checkResult(status, 0); + // We need to commit option definitions because later in this test we + // will be checking if they get removed when "option-def" parameter + // is removed from a configuration. + LibDHCP::commitRuntimeOptionDefs(); + // The option definition should now be available in the CfgMgr. def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100); ASSERT_TRUE(def); @@ -1356,6 +1361,25 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) { EXPECT_FALSE(def->getArrayType()); EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType()); EXPECT_TRUE(def->getEncapsulatedSpace().empty()); + + // The copy of the option definition should be available in the libdhcp++. + OptionDefinitionPtr def_libdhcp = LibDHCP::getRuntimeOptionDef("isc", 100); + ASSERT_TRUE(def_libdhcp); + + // Both definitions should be held in distinct pointers but they should + // be equal. + EXPECT_TRUE(def_libdhcp != def); + EXPECT_TRUE(*def_libdhcp == *def); + + // Let's apply empty configuration. This removes the option definitions + // configuration and should result in removal of the option 100 from the + // libdhcp++. + config = "{ }"; + json = Element::fromJSON(config); + ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json)); + checkResult(status, 0); + + EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("isc", 100)); } // The goal of this test is to check whether an option definition @@ -1468,6 +1492,14 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) { // The goal of this test is to verify that the duplicated option // definition is not accepted. TEST_F(Dhcp4ParserTest, optionDefDuplicate) { + // Preconfigure libdhcp++ with option definitions. The new configuration + // should override it, but when the new configuration fails, it should + // revert to this original configuration. + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("bar", 233, "string")); + defs.addItem(def, "isc"); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); // Configuration string. Both option definitions have // the same code and belong to the same option space. @@ -1498,6 +1530,15 @@ TEST_F(Dhcp4ParserTest, optionDefDuplicate) { ASSERT_TRUE(status); checkResult(status, 1); EXPECT_TRUE(errorContainsPosition(status, "")); + + // The new configuration should have inserted option 100, but + // once configuration failed (on the duplicate option definition) + // the original configuration in libdhcp++ should be reverted. + EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("isc", 100)); + def = LibDHCP::getRuntimeOptionDef("isc", 233); + ASSERT_TRUE(def); + EXPECT_EQ("bar", def->getName()); + EXPECT_EQ(233, def->getCode()); } // The goal of this test is to verify that the option definition diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc index e02f7be2cb..8ac0aa1e62 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -219,6 +220,10 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) { } } + // Finally, we can commit runtime option definitions in libdhcp++. This is + // exception free. + LibDHCP::commitRuntimeOptionDefs(); + return (answer); } diff --git a/src/bin/dhcp6/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc index fe0af18311..edb17989cb 100644 --- a/src/bin/dhcp6/json_config_parser.cc +++ b/src/bin/dhcp6/json_config_parser.cc @@ -752,6 +752,12 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) { // Remove any existing timers. TimerMgr::instance()->unregisterTimers(); + // Revert any runtime option definitions configured so far and not committed. + LibDHCP::revertRuntimeOptionDefs(); + // Let's set empty container in case a user hasn't specified any configuration + // for option definitions. This is equivalent to commiting empty container. + LibDHCP::setRuntimeOptionDefs(OptionDefSpaceContainer()); + // Some of the values specified in the configuration depend on // other values. Typically, the values in the subnet6 structure // depend on the global values. Also, option values configuration @@ -945,6 +951,9 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) { // Rollback changes as the configuration parsing failed. if (rollback) { globalContext().reset(new ParserContext(original_context)); + // Revert to original configuration of runtime option definitions + // in the libdhcp++. + LibDHCP::revertRuntimeOptionDefs(); return (answer); } diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc index 5da2d23409..218686e1ef 100644 --- a/src/bin/dhcp6/tests/config_parser_unittest.cc +++ b/src/bin/dhcp6/tests/config_parser_unittest.cc @@ -1583,6 +1583,12 @@ TEST_F(Dhcp6ParserTest, optionDefIpv6Address) { ConstElementPtr status; EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json)); ASSERT_TRUE(status); + checkResult(status, 0); + + // We need to commit option definitions because later in this test we + // will be checking if they get removed when "option-def" parameter + // is removed from a configuration. + LibDHCP::commitRuntimeOptionDefs(); // The option definition should now be available in the CfgMgr. def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100); @@ -1593,6 +1599,25 @@ TEST_F(Dhcp6ParserTest, optionDefIpv6Address) { EXPECT_EQ(100, def->getCode()); EXPECT_FALSE(def->getArrayType()); EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType()); + + // The copy of the option definition should be available in the libdhcp++. + OptionDefinitionPtr def_libdhcp = LibDHCP::getRuntimeOptionDef("isc", 100); + ASSERT_TRUE(def_libdhcp); + + // Both definitions should be held in distinct pointers but they should + // be equal. + EXPECT_TRUE(def_libdhcp != def); + EXPECT_TRUE(*def_libdhcp == *def); + + // Let's apply empty configuration. This removes the option definitions + // configuration and should result in removal of the option 100 from the + // libdhcp++. + config = "{ }"; + json = Element::fromJSON(config); + ASSERT_NO_THROW(status = configureDhcp6Server(srv_, json)); + checkResult(status, 0); + + EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("isc", 100)); } // The goal of this test is to check whether an option definition @@ -1702,6 +1727,14 @@ TEST_F(Dhcp6ParserTest, optionDefMultiple) { // The goal of this test is to verify that the duplicated option // definition is not accepted. TEST_F(Dhcp6ParserTest, optionDefDuplicate) { + // Preconfigure libdhcp++ with option definitions. The new configuration + // should override it, but when the new configuration fails, it should + // revert to this original configuration. + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("bar", 233, "string")); + defs.addItem(def, "isc"); + LibDHCP::setRuntimeOptionDefs(defs); + LibDHCP::commitRuntimeOptionDefs(); // Configuration string. Both option definitions have // the same code and belong to the same option space. @@ -1732,6 +1765,15 @@ TEST_F(Dhcp6ParserTest, optionDefDuplicate) { ASSERT_TRUE(status); checkResult(status, 1); EXPECT_TRUE(errorContainsPosition(status, "")); + + // The new configuration should have inserted option 100, but + // once configuration failed (on the duplicate option definition) + // the original configuration in libdhcp++ should be reverted. + EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("isc", 100)); + def = LibDHCP::getRuntimeOptionDef("isc", 233); + ASSERT_TRUE(def); + EXPECT_EQ("bar", def->getName()); + EXPECT_EQ(233, def->getCode()); } // The goal of this test is to verify that the option definition diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am index b892192b9c..1e1719925b 100644 --- a/src/lib/dhcp/Makefile.am +++ b/src/lib/dhcp/Makefile.am @@ -43,6 +43,7 @@ libkea_dhcp___la_SOURCES += option_data_types.cc option_data_types.h libkea_dhcp___la_SOURCES += option_definition.cc option_definition.h libkea_dhcp___la_SOURCES += option_opaque_data_tuples.cc option_opaque_data_tuples.h libkea_dhcp___la_SOURCES += option_space.cc option_space.h +libkea_dhcp___la_SOURCES += option_space_container.h libkea_dhcp___la_SOURCES += option_string.cc option_string.h libkea_dhcp___la_SOURCES += protocol_util.cc protocol_util.h libkea_dhcp___la_SOURCES += pkt.cc pkt.h diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc index 069f09a04a..b450a6307a 100644 --- a/src/lib/dhcp/libdhcp++.cc +++ b/src/lib/dhcp/libdhcp++.cc @@ -32,6 +32,8 @@ #include #include +#include + using namespace std; using namespace isc::dhcp; using namespace isc::util; @@ -52,6 +54,10 @@ VendorOptionDefContainers LibDHCP::vendor4_defs_; VendorOptionDefContainers LibDHCP::vendor6_defs_; +// Static container with option definitions created in runtime. +StagedValue LibDHCP::runtime_option_defs_; + + // Those two vendor classes are used for cable modems: /// DOCSIS3.0 compatible cable modem @@ -194,6 +200,66 @@ LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id, return (OptionDefinitionPtr()); } +OptionDefinitionPtr +LibDHCP::getRuntimeOptionDef(const std::string& space, const uint16_t code) { + OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space); + const OptionDefContainerTypeIndex& index = container->get<1>(); + const OptionDefContainerTypeRange& range = index.equal_range(code); + if (range.first != range.second) { + return (*range.first); + } + + return (OptionDefinitionPtr()); +} + +OptionDefinitionPtr +LibDHCP::getRuntimeOptionDef(const std::string& space, const std::string& name) { + OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space); + const OptionDefContainerNameIndex& index = container->get<2>(); + const OptionDefContainerNameRange& range = index.equal_range(name); + if (range.first != range.second) { + return (*range.first); + } + + return (OptionDefinitionPtr()); +} + +OptionDefContainerPtr +LibDHCP::getRuntimeOptionDefs(const std::string& space) { + return (runtime_option_defs_.getValue().getItems(space)); +} + +void +LibDHCP::setRuntimeOptionDefs(const OptionDefSpaceContainer& defs) { + OptionDefSpaceContainer defs_copy; + std::list option_space_names = defs.getOptionSpaceNames(); + for (std::list::const_iterator name = option_space_names.begin(); + name != option_space_names.end(); ++name) { + OptionDefContainerPtr container = defs.getItems(*name); + for (OptionDefContainer::const_iterator def = container->begin(); + def != container->end(); ++def) { + OptionDefinitionPtr def_copy(new OptionDefinition(**def)); + defs_copy.addItem(def_copy, *name); + } + } + runtime_option_defs_ = defs_copy; +} + +void +LibDHCP::clearRuntimeOptionDefs() { + runtime_option_defs_.reset(); +} + +void +LibDHCP::revertRuntimeOptionDefs() { + runtime_option_defs_.revert(); +} + +void +LibDHCP::commitRuntimeOptionDefs() { + runtime_option_defs_.commit(); +} + bool LibDHCP::isStandardOption(const Option::Universe u, const uint16_t code) { if (u == Option::V6) { @@ -260,7 +326,14 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf, OptionDefContainer option_defs; if (option_space == "dhcp6") { option_defs = LibDHCP::getOptionDefs(Option::V6); + } else { + OptionDefContainerPtr option_defs_ptr = + LibDHCP::getRuntimeOptionDefs(option_space); + if (option_defs_ptr) { + option_defs = *option_defs_ptr; + } } + // @todo Once we implement other option spaces we should add else clause // here and gather option definitions for them. For now leaving option_defs // empty will imply creation of generic Option. diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h index 57fac075bf..767a200384 100644 --- a/src/lib/dhcp/libdhcp++.h +++ b/src/lib/dhcp/libdhcp++.h @@ -16,8 +16,10 @@ #define LIBDHCP_H #include +#include #include #include +#include #include #include @@ -90,6 +92,36 @@ public: const uint32_t vendor_id, const std::string& name); + + /// @brief Returns runtime (non-standard) option definition by space and + /// option code. + /// + /// @param space Option space name. + /// @param code Option code. + /// + /// @return Pointer to option definition or NULL if it doesn't exist. + static OptionDefinitionPtr getRuntimeOptionDef(const std::string& space, + const uint16_t code); + + /// @brief Returns runtime (non-standard) option definition by space and + /// option name. + /// + /// @param space Option space name. + /// @param name Option name. + /// + /// @return Pointer to option definition or NULL if it doesn't exist. + static OptionDefinitionPtr getRuntimeOptionDef(const std::string& space, + const std::string& name); + + /// @brief Returns runtime (non-standard) option definitions for specified + /// option space name. + /// + /// @param space Option space name. + /// + /// @return Pointer to the container holding option definitions or NULL. + static OptionDefContainerPtr + getRuntimeOptionDefs(const std::string& space); + /// @brief Check if the specified option is a standard option. /// /// @param u universe (V4 or V6) @@ -256,6 +288,27 @@ public: const OptionBuffer& buf, isc::dhcp::OptionCollection& options); + + /// @brief Copies option definitions created at runtime. + /// + /// Copied option definitions will be used as "runtime" option definitions. + /// A typical use case is to set option definitions specified by the user + /// in the server configuration. These option definitions should be removed + /// or replaced with new option definitions upon reconfiguration. + /// + /// @param defs Const reference to a container holding option definitions + /// grouped by option spaces. + static void setRuntimeOptionDefs(const OptionDefSpaceContainer& defs); + + /// @brief Removes runtime option definitions. + static void clearRuntimeOptionDefs(); + + /// @brief Reverts uncommited changes to runtime option definitions. + static void revertRuntimeOptionDefs(); + + /// @brief Commits runtime option definitions. + static void commitRuntimeOptionDefs(); + private: /// Initialize standard DHCPv4 option definitions. @@ -301,6 +354,9 @@ private: /// Container for v6 vendor option definitions static VendorOptionDefContainers vendor6_defs_; + + /// Container for additional option defnitions created in runtime. + static util::StagedValue runtime_option_defs_; }; } diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc index c6cfdddca6..4caf4bb488 100644 --- a/src/lib/dhcp/option.cc +++ b/src/lib/dhcp/option.cc @@ -220,8 +220,8 @@ Option::toString() { return (toText(0)); } -std::string -Option::toHexString(const bool include_header) { +std::vector +Option::toBinary(const bool include_header) { OutputBuffer buf(len()); try { // If the option is too long, exception will be thrown. We allow @@ -233,12 +233,18 @@ Option::toHexString(const bool include_header) { " of option " << getType() << ": " << ex.what()); } const uint8_t* option_data = static_cast(buf.getData()); - std::vector option_vec; // Assign option data to a vector, with or without option header depending // on the value of "include_header" flag. - option_vec.assign(option_data + (include_header ? 0 : getHeaderLen()), - option_data + buf.getLength()); + std::vector option_vec(option_data + (include_header ? 0 : getHeaderLen()), + option_data + buf.getLength()); + return (option_vec); +} + +std::string +Option::toHexString(const bool include_header) { + // Prepare binary version of the option. + std::vector option_vec = toBinary(include_header); // Return hexadecimal representation prepended with 0x or empty string // if option has no payload and the header fields are excluded. diff --git a/src/lib/dhcp/option.h b/src/lib/dhcp/option.h index e993aac75c..adabadc117 100644 --- a/src/lib/dhcp/option.h +++ b/src/lib/dhcp/option.h @@ -216,6 +216,15 @@ public: /// @return string that represents the value of the option. virtual std::string toString(); + /// @brief Returns binary representation of the option. + /// + /// @param include_header Boolean flag which indicates if the output should + /// also contain header fields. The default is that it shouldn't include + /// header fields. + /// + /// @return Vector holding binary representation of the option. + virtual std::vector toBinary(const bool include_header = false); + /// @brief Returns string containing hexadecimal representation of option. /// /// @param include_header Boolean flag which indicates if the output should diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h index 04a9693b54..d1e548e72b 100644 --- a/src/lib/dhcp/option_definition.h +++ b/src/lib/dhcp/option_definition.h @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -766,6 +767,10 @@ typedef OptionDefContainer::nth_index<2>::type OptionDefContainerNameIndex; typedef std::pair OptionDefContainerNameRange; +typedef OptionSpaceContainer< + OptionDefContainer, OptionDefinitionPtr, std::string +> OptionDefSpaceContainer; + } // namespace isc::dhcp } // namespace isc diff --git a/src/lib/dhcpsrv/option_space_container.h b/src/lib/dhcp/option_space_container.h similarity index 100% rename from src/lib/dhcpsrv/option_space_container.h rename to src/lib/dhcp/option_space_container.h diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc index 048e31f361..a4290aa884 100644 --- a/src/lib/dhcp/tests/libdhcp++_unittest.cc +++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc @@ -147,6 +147,60 @@ public: return (OptionBuffer(opt_data, opt_data + sizeof(opt_data))); } + /// @brief Create option definitions and store in the container. + /// + /// @param spaces_num Number of option spaces to be created. + /// @param defs_num Number of option definitions to be created for + /// each option space. + /// @param [out] defs Container to which option definitions should be + /// added. + static void createRuntimeOptionDefs(const uint16_t spaces_num, + const uint16_t defs_num, + OptionDefSpaceContainer& defs) { + for (uint16_t space = 0; space < spaces_num; ++space) { + std::ostringstream space_name; + space_name << "option-space-" << space; + for (uint16_t code = 0; code < defs_num; ++code) { + std::ostringstream name; + name << "name-for-option-" << code; + OptionDefinitionPtr opt_def(new OptionDefinition(name.str(), + code, "string")); + defs.addItem(opt_def, space_name.str()); + } + } + } + + /// @brief Test if runtime option definitions have been added. + /// + /// This method uses the same naming conventions for space names and + /// options names as @c createRuntimeOptionDefs method. + /// + /// @param spaces_num Number of option spaces to be tested. + /// @param defs_num Number of option definitions that should exist + /// in each option space. + /// @param should_exist Boolean value which indicates if option + /// definitions should exist. If this is false, this function will + /// check that they don't exist. + static void testRuntimeOptionDefs(const uint16_t spaces_num, + const uint16_t defs_num, + const bool should_exist) { + for (uint16_t space = 0; space < spaces_num; ++space) { + std::ostringstream space_name; + space_name << "option-space-" << space; + for (uint16_t code = 0; code < defs_num; ++code) { + std::ostringstream name; + name << "name-for-option-" << code; + OptionDefinitionPtr opt_def = + LibDHCP::getRuntimeOptionDef(space_name.str(), name.str()); + if (should_exist) { + ASSERT_TRUE(opt_def); + } else { + ASSERT_FALSE(opt_def); + } + } + } + } + private: /// @brief Test DHCPv4 or DHCPv6 option definition. @@ -1319,4 +1373,28 @@ TEST_F(LibDhcpTest, vendorClass6) { EXPECT_EQ("eRouter1.0", vclass->getTuple(0).getText()); } +// This test verifies that it is possible to add runtime option definitions, +// retrieve them and remove them. +TEST_F(LibDhcpTest, setRuntimeOptionDefs) { + // Create option definitions in 5 namespaces. + OptionDefSpaceContainer defs; + createRuntimeOptionDefs(5, 100, defs); + + // Apply option definitions. + ASSERT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + // Retrieve all inserted option definitions. + testRuntimeOptionDefs(5, 100, true); + + // Attempting to retrieve non existing definitions. + EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("option-space-non-existent", 1)); + EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("option-space-0", 145)); + + // Remove all runtime option definitions. + ASSERT_NO_THROW(LibDHCP::clearRuntimeOptionDefs()); + + // All option definitions should be gone now. + testRuntimeOptionDefs(5, 100, false); +} + } // end of anonymous space diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index 151fb574c5..b140c7e48f 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -128,7 +128,6 @@ libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h if HAVE_PGSQL libkea_dhcpsrv_la_SOURCES += pgsql_lease_mgr.cc pgsql_lease_mgr.h endif -libkea_dhcpsrv_la_SOURCES += option_space_container.h libkea_dhcpsrv_la_SOURCES += pool.cc pool.h libkea_dhcpsrv_la_SOURCES += srv_config.cc srv_config.h libkea_dhcpsrv_la_SOURCES += subnet.cc subnet.h diff --git a/src/lib/dhcpsrv/cfg_option.h b/src/lib/dhcpsrv/cfg_option.h index 2872924a2a..22935b4ad0 100644 --- a/src/lib/dhcpsrv/cfg_option.h +++ b/src/lib/dhcpsrv/cfg_option.h @@ -16,8 +16,8 @@ #define CFG_OPTION_H #include +#include #include -#include #include #include #include diff --git a/src/lib/dhcpsrv/cfg_option_def.h b/src/lib/dhcpsrv/cfg_option_def.h index 81850e8738..8da68bd350 100644 --- a/src/lib/dhcpsrv/cfg_option_def.h +++ b/src/lib/dhcpsrv/cfg_option_def.h @@ -16,7 +16,7 @@ #define CFG_OPTION_DEF_H #include -#include +#include #include namespace isc { @@ -120,14 +120,18 @@ public: OptionDefinitionPtr get(const std::string& option_space, const std::string& option_name) const; + /// @brief Returns reference to container holding option definitions. + const OptionDefSpaceContainer& getContainer() const { + return (option_definitions_); + } + private: /// @brief A collection of option definitions. /// /// The option definitions stored in this container can be accessed /// using the option space name they belong to. - OptionSpaceContainer option_definitions_; + OptionDefSpaceContainer option_definitions_; }; diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc index 96f10cdc29..e3deeccfaf 100644 --- a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc +++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc @@ -50,7 +50,7 @@ ExpressionParser::build(ConstElementPtr expression_cfg) { std::string value; expression_cfg->getValue(value); try { - EvalContext eval_ctx; + EvalContext eval_ctx(global_context_->universe_); eval_ctx.parseString(value); local_expression_.reset(new Expression()); *local_expression_ = eval_ctx.expression; diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index 819fb6bb98..873793044e 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -786,6 +786,12 @@ OptionDefParser::build(ConstElementPtr option_def) { isc_throw(DhcpConfigError, ex.what() << " (" << option_def->getPosition() << ")"); } + + // All definitions have been prepared. Put them as runtime options into + // the libdhcp++. + const OptionDefSpaceContainer& container = + CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->getContainer(); + LibDHCP::setRuntimeOptionDefs(container); } void diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.h b/src/lib/dhcpsrv/parsers/dhcp_parsers.h index f16584e172..63cd98582a 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.h +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.h @@ -18,10 +18,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -36,12 +36,6 @@ namespace isc { namespace dhcp { -/// @brief Storage for option definitions. -typedef OptionSpaceContainer OptionDefStorage; -/// @brief Shared pointer to option definitions storage. -typedef boost::shared_ptr OptionDefStoragePtr; - /// Collection of containers holding option spaces. Each container within /// a particular option space holds so-called option descriptors. typedef OptionSpaceContainer #include #include +#include #include -#include #include #include #include diff --git a/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc b/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc index 6504dcc53d..eb71b2c551 100644 --- a/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc +++ b/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc @@ -15,6 +15,8 @@ #include #include +#include +#include #include #include #include @@ -32,6 +34,61 @@ using namespace isc::dhcp; namespace { +/// @brief Test fixture class for @c ExpressionParser. +class ExpressionParserTest : public ::testing::Test { +protected: + + /// @brief Test that validate expression can be evaluated against v4 or + /// v6 packet. + /// + /// Verifies that given a valid expression, the ExpressionParser + /// produces an Expression which can be evaluated against a v4 or v6 + /// packet. + /// + /// @param universe V4 or V6. + /// @param expression Textual representation of the expression to be + /// evaluated. + /// @param option_string String data to be placed in the hostname + /// option, being placed in the packet used for evaluation. + /// @tparam Type of the packet: @c Pkt4 or @c Pkt6. + template + void testValidExpression(const Option::Universe& universe, + const std::string& expression, + const std::string& option_string) { + ParserContextPtr context(new ParserContext(universe)); + ExpressionParserPtr parser; + ExpressionPtr parsed_expr; + + // Turn config into elements. This may emit exceptions. + ElementPtr config_element = Element::fromJSON(expression); + + // Create the parser. + ASSERT_NO_THROW(parser.reset(new ExpressionParser("", parsed_expr, + context))); + // Expression should parse and commit. + ASSERT_NO_THROW(parser->build(config_element)); + ASSERT_NO_THROW(parser->commit()); + + // Parsed expression should exist. + ASSERT_TRUE(parsed_expr); + + // Build a packet that will fail evaluation. + boost::shared_ptr pkt(new PktType(universe == Option::V4 ? + DHCPDISCOVER : DHCPV6_SOLICIT, + 123)); + EXPECT_FALSE(evaluate(*parsed_expr, *pkt)); + + // Now add the option so it will pass. Use a standard option carrying a + // single string value, i.e. hostname for DHCPv4 and bootfile url for + // DHCPv6. + OptionPtr opt(new OptionString(universe, universe == Option::V4 ? + DHO_HOST_NAME : D6O_BOOTFILE_URL, + option_string)); + pkt->addOption(opt); + EXPECT_TRUE(evaluate(*parsed_expr, *pkt)); + } +}; + /// @brief Test fixture class for @c ClientClassDefParser. class ClientClassDefParserTest : public ::testing::Test { protected: @@ -113,69 +170,66 @@ protected: // Verifies that given a valid expression, the ExpressionParser // produces an Expression which can be evaluated against a v4 packet. -TEST(ExpressionParserTest, validExpression4) { - ParserContextPtr context(new ParserContext(Option::V4)); - ExpressionParserPtr parser; - ExpressionPtr parsed_expr; +TEST_F(ExpressionParserTest, validExpression4) { + testValidExpression(Option::V4, "\"option[12].text == 'hundred4'\"", + "hundred4"); +} - // Turn config into elements. This may emit exceptions. - std::string cfg_txt = "\"option[100].text == 'hundred4'\""; - ElementPtr config_element = Element::fromJSON(cfg_txt); +// Verifies that the option name can be used in the evaluated expression. +TEST_F(ExpressionParserTest, validExpressionWithOptionName4) { + testValidExpression(Option::V4, + "\"option[host-name].text == 'hundred4'\"", + "hundred4"); +} - // Create the parser. - ASSERT_NO_THROW(parser.reset(new ExpressionParser("", parsed_expr, - context))); - // Expression should parse and commit. - ASSERT_NO_THROW(parser->build(config_element)); - ASSERT_NO_THROW(parser->commit()); +// Verifies that given a valid expression using .hex operator for option, the +// ExpressionParser produces an Expression which can be evaluated against +// a v4 packet. +TEST_F(ExpressionParserTest, validExpressionWithHex4) { + testValidExpression(Option::V4, "\"option[12].hex == 0x68756E6472656434\"", + "hundred4"); +} - // Parsed expression should exist. - ASSERT_TRUE(parsed_expr); - - // Build a packet that will fail evaluation. - Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123)); - EXPECT_FALSE(evaluate(*parsed_expr, *pkt4)); - - // Now add the option so it will pass. - OptionPtr opt(new OptionString(Option::V4, 100, "hundred4")); - pkt4->addOption(opt); - EXPECT_TRUE(evaluate(*parsed_expr, *pkt4)); +// Verifies that the option name can be used together with .hex operator in +// the evaluated expression. +TEST_F(ExpressionParserTest, validExpressionWithOptionNameAndHex4) { + testValidExpression(Option::V4, + "\"option[host-name].text == 0x68756E6472656434\"", + "hundred4"); } // Verifies that given a valid expression, the ExpressionParser // produces an Expression which can be evaluated against a v6 packet. -TEST(ExpressionParserTest, validExpression6) { - ParserContextPtr context(new ParserContext(Option::V6)); - ExpressionParserPtr parser; - ExpressionPtr parsed_expr; - - // Turn config into elements. This may emit exceptions. - std::string cfg_txt = "\"option[100].text == 'hundred6'\""; - ElementPtr config_element = Element::fromJSON(cfg_txt); - - // Create the parser. - ASSERT_NO_THROW(parser.reset(new ExpressionParser("", parsed_expr, - context))); - // Expression should parse and commit. - ASSERT_NO_THROW(parser->build(config_element)); - ASSERT_NO_THROW(parser->commit()); - - // Parsed expression should exist. - ASSERT_TRUE(parsed_expr); - - // Build a packet that will fail evaluation. - Pkt6Ptr pkt6(new Pkt6(DHCPDISCOVER, 123)); - EXPECT_FALSE(evaluate(*parsed_expr, *pkt6)); - - // Now add the option so it will pass. - OptionPtr opt(new OptionString(Option::V6, 100, "hundred6")); - pkt6->addOption(opt); - EXPECT_TRUE(evaluate(*parsed_expr, *pkt6)); +TEST_F(ExpressionParserTest, validExpression6) { + testValidExpression(Option::V6, "\"option[59].text == 'hundred6'\"", + "hundred6"); } +// Verifies that the option name can be used in the evaluated expression. +TEST_F(ExpressionParserTest, validExpressionWithOptionName6) { + testValidExpression(Option::V6, + "\"option[bootfile-url].text == 'hundred6'\"", + "hundred6"); +} + +// Verifies that given a valid expression using .hex operator for option, the +// ExpressionParser produces an Expression which can be evaluated against +// a v6 packet. +TEST_F(ExpressionParserTest, validExpressionWithHex6) { + testValidExpression(Option::V6, "\"option[59].hex == 0x68756E6472656436\"", + "hundred6"); +} + +// Verifies that the option name can be used together with .hex operator in +// the evaluated expression. +TEST_F(ExpressionParserTest, validExpressionWithOptionNameAndHex6) { + testValidExpression(Option::V6, + "\"option[bootfile-url].text == 0x68756E6472656436\"", + "hundred6"); +} // Verifies that an the ExpressionParser only accepts StringElements. -TEST(ExpressionParserTest, invalidExpressionElement) { +TEST_F(ExpressionParserTest, invalidExpressionElement) { ParserContextPtr context(new ParserContext(Option::V4)); ExpressionParserPtr parser; ExpressionPtr parsed_expr; @@ -196,7 +250,7 @@ TEST(ExpressionParserTest, invalidExpressionElement) { // is not intended to be an exhaustive test or expression syntax. // It is simply to ensure that if the parser fails, it does so // Properly. -TEST(ExpressionParserTest, expressionSyntaxError) { +TEST_F(ExpressionParserTest, expressionSyntaxError) { ParserContextPtr context(new ParserContext(Option::V4)); ExpressionParserPtr parser; ExpressionPtr parsed_expr; diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc index 39be6dd50e..7880938854 100644 --- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc +++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc @@ -495,6 +495,15 @@ TEST_F(ParseConfigTest, basicOptionDefTest) { EXPECT_FALSE(def->getArrayType()); EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType()); EXPECT_TRUE(def->getEncapsulatedSpace().empty()); + + // Check if libdhcp++ runtime options have been updated. + OptionDefinitionPtr def_libdhcp = LibDHCP::getRuntimeOptionDef("isc", 100); + ASSERT_TRUE(def_libdhcp); + + // The LibDHCP should return a separate instance of the option definition + // but the values should be equal. + EXPECT_TRUE(def_libdhcp != def); + EXPECT_TRUE(*def_libdhcp == *def); } /// @brief Check minimal parsing of option definitions. diff --git a/src/lib/eval/eval_context.cc b/src/lib/eval/eval_context.cc index 7dc46fc4cc..ac760860f5 100644 --- a/src/lib/eval/eval_context.cc +++ b/src/lib/eval/eval_context.cc @@ -15,10 +15,12 @@ #include #include #include +#include #include -EvalContext::EvalContext() - : trace_scanning_(false), trace_parsing_(false) +EvalContext::EvalContext(const Option::Universe& option_universe) + : trace_scanning_(false), trace_parsing_(false), + option_universe_(option_universe) { } @@ -32,7 +34,7 @@ EvalContext::parseString(const std::string& str) file_ = ""; string_ = str; scanStringBegin(); - isc::eval::EvalParser parser(*this); + isc::eval::EvalParser parser(*this, option_universe_); parser.set_debug_level(trace_parsing_); int res = parser.parse(); scanStringEnd(); diff --git a/src/lib/eval/eval_context.h b/src/lib/eval/eval_context.h index 6b28e2b161..1e7b9b9e30 100644 --- a/src/lib/eval/eval_context.h +++ b/src/lib/eval/eval_context.h @@ -42,7 +42,11 @@ class EvalContext { public: /// @brief Default constructor. - EvalContext(); + /// + /// @param option_universe Option universe: DHCPv4 or DHCPv6. This is used + /// by the parser to determine which option definitions set should be used + /// to map option names to option codes. + EvalContext(const Option::Universe& option_universe); /// @brief destructor virtual ~EvalContext(); @@ -55,7 +59,7 @@ public: /// @brief Method called after the last tokens are scanned from a string. void scanStringEnd(); - + /// @brief Run the parser on the string specified. /// /// @param str string to be written @@ -87,7 +91,12 @@ public: /// @brief Flag determing parser debugging. bool trace_parsing_; - + + /// @brief Option universe: DHCPv4 or DHCPv6. + /// + /// This is used by the parser to determine which option definitions + /// set should be used to map option name to option code. + Option::Universe option_universe_; }; }; // end of isc::eval namespace diff --git a/src/lib/eval/lexer.cc b/src/lib/eval/lexer.cc index bd76920f27..35653efe6a 100644 --- a/src/lib/eval/lexer.cc +++ b/src/lib/eval/lexer.cc @@ -460,8 +460,8 @@ static void yy_fatal_error (yyconst char msg[] ); (yy_c_buf_p) = yy_cp; /* %% [4.0] data tables for the DFA and the user's section 1 definitions go here */ -#define YY_NUM_RULES 19 -#define YY_END_OF_BUFFER 20 +#define YY_NUM_RULES 20 +#define YY_END_OF_BUFFER 21 /* This struct is not used in this scanner, but its presence is necessary. */ struct yy_trans_info @@ -469,14 +469,27 @@ struct yy_trans_info flex_int32_t yy_verify; flex_int32_t yy_nxt; }; -static yyconst flex_int16_t yy_accept[52] = +static yyconst flex_int16_t yy_acclist[84] = { 0, - 0, 0, 20, 18, 1, 2, 18, 13, 14, 17, - 18, 12, 5, 5, 18, 15, 16, 18, 18, 18, - 18, 18, 1, 2, 0, 3, 5, 0, 6, 0, - 0, 0, 0, 0, 4, 11, 9, 0, 0, 0, - 0, 0, 8, 0, 0, 7, 0, 0, 0, 10, - 0 + 21, 19, 20, 1, 19, 20, 2, 20, 19, 20, + 13, 19, 20, 14, 19, 20, 17, 19, 20, 19, + 20, 12, 19, 20, 5, 19, 20, 5, 19, 20, + 19, 20, 19, 20, 15, 19, 20, 16, 19, 20, + 19, 20, 19, 20, 19, 20, 19, 20, 19, 20, + 1, 2, 3, 5, 6,16402,16402,16402,16402,16402, + 16402, 4, 8210, 11,16402, 9,16402,16402,16402,16402, + 16402,16402, 8,16402,16402,16402, 7,16402,16402,16402, + 16402, 10,16402 + } ; + +static yyconst flex_int16_t yy_accept[57] = + { 0, + 1, 1, 1, 2, 4, 7, 9, 11, 14, 17, + 20, 22, 25, 28, 31, 33, 35, 38, 41, 43, + 45, 47, 49, 51, 52, 53, 53, 54, 55, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 63, 64, + 66, 68, 69, 70, 71, 72, 73, 75, 76, 77, + 79, 80, 81, 82, 84, 84 } ; static yyconst flex_int32_t yy_ec[256] = @@ -488,13 +501,13 @@ static yyconst flex_int32_t yy_ec[256] = 6, 1, 1, 7, 8, 9, 1, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 1, 1, 1, 12, 1, 1, 1, 13, 13, 13, 13, 13, 13, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 14, 1, 1, - 15, 1, 16, 1, 1, 1, 17, 18, 13, 13, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 15, 14, 14, + 16, 1, 17, 1, 18, 1, 19, 20, 13, 13, - 19, 13, 20, 21, 22, 1, 1, 23, 1, 24, - 25, 26, 1, 27, 28, 29, 30, 1, 1, 31, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 21, 13, 22, 23, 24, 14, 14, 25, 14, 26, + 27, 28, 14, 29, 30, 31, 32, 14, 14, 33, + 14, 14, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -511,88 +524,110 @@ static yyconst flex_int32_t yy_ec[256] = 1, 1, 1, 1, 1 } ; -static yyconst flex_int32_t yy_meta[32] = +static yyconst flex_int32_t yy_meta[34] = { 0, - 1, 1, 2, 1, 1, 1, 1, 1, 1, 3, - 3, 1, 3, 1, 1, 1, 3, 3, 3, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1 + 1, 1, 2, 1, 1, 1, 1, 3, 1, 4, + 4, 1, 4, 3, 3, 1, 1, 3, 4, 4, + 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3 } ; -static yyconst flex_int16_t yy_base[54] = +static yyconst flex_int16_t yy_base[59] = { 0, - 0, 0, 72, 73, 69, 67, 65, 73, 73, 73, - 22, 73, 24, 26, 56, 73, 73, 44, 47, 39, - 34, 44, 60, 58, 56, 73, 29, 0, 73, 36, - 26, 25, 35, 21, 0, 73, 73, 29, 22, 20, - 23, 18, 73, 22, 18, 73, 22, 19, 22, 73, - 73, 55, 38 + 0, 0, 121, 122, 118, 116, 110, 122, 122, 122, + 24, 122, 26, 28, 98, 0, 122, 122, 77, 79, + 67, 61, 63, 68, 50, 47, 122, 32, 0, 122, + 38, 43, 44, 45, 46, 47, 0, 48, 122, 50, + 52, 54, 55, 56, 72, 73, 77, 79, 80, 81, + 84, 86, 89, 90, 122, 112, 114, 40 } ; -static yyconst flex_int16_t yy_def[54] = +static yyconst flex_int16_t yy_def[59] = { 0, - 51, 1, 51, 51, 51, 51, 52, 51, 51, 51, - 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, - 51, 51, 51, 51, 52, 51, 51, 53, 51, 51, - 51, 51, 51, 51, 53, 51, 51, 51, 51, 51, - 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, - 0, 51, 51 + 55, 1, 55, 55, 55, 55, 56, 55, 55, 55, + 55, 55, 55, 55, 55, 57, 55, 55, 57, 57, + 57, 57, 57, 55, 55, 56, 55, 55, 58, 55, + 57, 57, 57, 57, 57, 57, 58, 55, 55, 57, + 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, + 57, 57, 57, 57, 0, 55, 55, 55 } ; -static yyconst flex_int16_t yy_nxt[105] = +static yyconst flex_int16_t yy_nxt[156] = { 0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, - 14, 15, 4, 4, 16, 17, 18, 4, 4, 4, - 19, 4, 4, 4, 20, 4, 4, 21, 22, 4, - 4, 27, 27, 27, 27, 27, 27, 28, 27, 27, - 35, 50, 49, 48, 47, 46, 45, 44, 43, 42, - 41, 40, 39, 38, 28, 25, 37, 25, 36, 26, - 24, 23, 34, 33, 32, 31, 30, 29, 26, 24, - 23, 51, 3, 51, 51, 51, 51, 51, 51, 51, - 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, - 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 14, 15, 16, 16, 16, 17, 18, 4, 19, 16, + 16, 16, 20, 16, 16, 16, 21, 16, 16, 22, + 23, 16, 16, 28, 28, 28, 28, 28, 28, 38, + 29, 28, 28, 37, 38, 38, 38, 38, 38, 38, + 27, 38, 25, 38, 39, 38, 38, 38, 29, 39, + 39, 39, 39, 39, 39, 43, 39, 40, 39, 24, + 39, 39, 39, 38, 38, 42, 41, 45, 38, 44, + 38, 38, 38, 36, 46, 38, 47, 38, 39, 39, + 38, 38, 35, 39, 34, 39, 39, 39, 48, 33, - 51, 51, 51, 51 + 39, 32, 39, 49, 50, 39, 39, 52, 51, 30, + 54, 53, 26, 27, 26, 26, 31, 31, 25, 24, + 55, 3, 55, 55, 55, 55, 55, 55, 55, 55, + 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, + 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, + 55, 55, 55, 55, 55 } ; -static yyconst flex_int16_t yy_chk[105] = +static yyconst flex_int16_t yy_chk[156] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 11, 11, 13, 13, 14, 14, 13, 27, 27, - 53, 49, 48, 47, 45, 44, 42, 41, 40, 39, - 38, 34, 33, 32, 13, 52, 31, 52, 30, 25, - 24, 23, 22, 21, 20, 19, 18, 15, 7, 6, - 5, 3, 51, 51, 51, 51, 51, 51, 51, 51, - 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, - 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 1, 1, 1, 11, 11, 13, 13, 14, 14, 31, + 13, 28, 28, 58, 32, 33, 34, 35, 36, 38, + 26, 40, 25, 41, 31, 42, 43, 44, 13, 32, + 33, 34, 35, 36, 38, 35, 40, 32, 41, 24, + 42, 43, 44, 45, 46, 34, 33, 42, 47, 36, + 48, 49, 50, 23, 43, 51, 44, 52, 45, 46, + 53, 54, 22, 47, 21, 48, 49, 50, 45, 20, - 51, 51, 51, 51 + 51, 19, 52, 46, 48, 53, 54, 51, 49, 15, + 53, 52, 56, 7, 56, 56, 57, 57, 6, 5, + 3, 55, 55, 55, 55, 55, 55, 55, 55, 55, + 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, + 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, + 55, 55, 55, 55, 55 } ; /* Table of booleans, true if rule could match eol. */ -static yyconst flex_int32_t yy_rule_can_match_eol[20] = +static yyconst flex_int32_t yy_rule_can_match_eol[21] = { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - }; - -static yy_state_type yy_last_accepting_state; -static char *yy_last_accepting_cpos; + 0, }; extern int yy_flex_debug; int yy_flex_debug = 1; -static yyconst flex_int16_t yy_rule_linenum[19] = +static yyconst flex_int16_t yy_rule_linenum[20] = { 0, 83, 87, 93, 103, 109, 123, 124, 125, 126, 127, - 128, 129, 130, 131, 132, 133, 134, 136 + 128, 129, 130, 131, 132, 133, 134, 136, 143 } ; -/* The intent behind this definition is that it'll catch - * any uses of REJECT which flex missed. - */ -#define REJECT reject_used_but_not_detected +static yy_state_type *yy_state_buf=0, *yy_state_ptr=0; +static char *yy_full_match; +static int yy_lp; +static int yy_looking_for_trail_begin = 0; +static int yy_full_lp; +static int *yy_full_state; +#define YY_TRAILING_MASK 0x2000 +#define YY_TRAILING_HEAD_MASK 0x4000 +#define REJECT \ +{ \ +*yy_cp = (yy_hold_char); /* undo effects of setting up yytext */ \ +yy_cp = (yy_full_match); /* restore poss. backed-over text */ \ +(yy_lp) = (yy_full_lp); /* restore orig. accepting pos. */ \ +(yy_state_ptr) = (yy_full_state); /* restore orig. state */ \ +yy_current_state = *(yy_state_ptr); /* restore curr. state */ \ +++(yy_lp); \ +goto find_rule; \ +} + #define yymore() yymore_used_but_not_detected #define YY_MORE_ADJ 0 #define YY_RESTORE_YY_MORE_OFFSET @@ -653,7 +688,7 @@ static isc::eval::location loc; // by moving it ahead by yyleng bytes. yyleng specifies the length of the // currently matched token. #define YY_USER_ACTION loc.columns(yyleng); -#line 657 "lexer.cc" +#line 692 "lexer.cc" #define INITIAL 0 @@ -901,7 +936,7 @@ YY_DECL loc.step(); -#line 905 "lexer.cc" +#line 940 "lexer.cc" if ( !(yy_init) ) { @@ -911,6 +946,12 @@ YY_DECL YY_USER_INIT; #endif + /* Create the reject buffer large enough to save one state per allowed character. */ + if ( ! (yy_state_buf) ) + (yy_state_buf) = (yy_state_type *)yyalloc(YY_STATE_BUF_SIZE ); + if ( ! (yy_state_buf) ) + YY_FATAL_ERROR( "out of dynamic memory in yylex()" ); + if ( ! (yy_start) ) (yy_start) = 1; /* first start state */ @@ -952,31 +993,66 @@ YY_DECL /* %% [9.0] code to set up and find next match goes here */ yy_current_state = (yy_start); + + (yy_state_ptr) = (yy_state_buf); + *(yy_state_ptr)++ = yy_current_state; + yy_match: do { register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; - if ( yy_accept[yy_current_state] ) - { - (yy_last_accepting_state) = yy_current_state; - (yy_last_accepting_cpos) = yy_cp; - } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; - if ( yy_current_state >= 52 ) + if ( yy_current_state >= 56 ) yy_c = yy_meta[(unsigned int) yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + *(yy_state_ptr)++ = yy_current_state; ++yy_cp; } - while ( yy_current_state != 51 ); - yy_cp = (yy_last_accepting_cpos); - yy_current_state = (yy_last_accepting_state); + while ( yy_current_state != 55 ); yy_find_action: /* %% [10.0] code to find the action number goes here */ - yy_act = yy_accept[yy_current_state]; + yy_current_state = *--(yy_state_ptr); + (yy_lp) = yy_accept[yy_current_state]; +goto find_rule; /* Shut up GCC warning -Wall */ +find_rule: /* we branch to this label when backing up */ + for ( ; ; ) /* until we find what rule we matched */ + { + if ( (yy_lp) && (yy_lp) < yy_accept[yy_current_state + 1] ) + { + yy_act = yy_acclist[(yy_lp)]; + if ( yy_act & YY_TRAILING_HEAD_MASK || + (yy_looking_for_trail_begin) ) + { + if ( yy_act == (yy_looking_for_trail_begin) ) + { + (yy_looking_for_trail_begin) = 0; + yy_act &= ~YY_TRAILING_HEAD_MASK; + break; + } + } + else if ( yy_act & YY_TRAILING_MASK ) + { + (yy_looking_for_trail_begin) = yy_act & ~YY_TRAILING_MASK; + (yy_looking_for_trail_begin) |= YY_TRAILING_HEAD_MASK; + } + else + { + (yy_full_match) = yy_cp; + (yy_full_state) = (yy_state_ptr); + (yy_full_lp) = (yy_lp); + break; + } + ++(yy_lp); + goto find_rule; + } + --yy_cp; + yy_current_state = *--(yy_state_ptr); + (yy_lp) = yy_accept[yy_current_state]; + } YY_DO_BEFORE_ACTION; @@ -999,13 +1075,13 @@ do_action: /* This label is used only to access EOF actions. */ { if ( yy_act == 0 ) fprintf( stderr, "--scanner backing up\n" ); - else if ( yy_act < 19 ) + else if ( yy_act < 20 ) fprintf( stderr, "--accepting rule at line %ld (\"%s\")\n", (long)yy_rule_linenum[yy_act], yytext ); - else if ( yy_act == 19 ) + else if ( yy_act == 20 ) fprintf( stderr, "--accepting default rule (\"%s\")\n", yytext ); - else if ( yy_act == 20 ) + else if ( yy_act == 21 ) fprintf( stderr, "--(end of buffer or a NUL)\n" ); else fprintf( stderr, "--EOF (start condition %d)\n", YY_START ); @@ -1014,13 +1090,6 @@ do_action: /* This label is used only to access EOF actions. */ switch ( yy_act ) { /* beginning of action switch */ /* %% [13.0] actions go here */ - case 0: /* must back up */ - /* undo the effects of YY_DO_BEFORE_ACTION */ - *yy_cp = (yy_hold_char); - yy_cp = (yy_last_accepting_cpos); - yy_current_state = (yy_last_accepting_state); - goto yy_find_action; - case 1: YY_RULE_SETUP #line 83 "lexer.ll" @@ -1141,18 +1210,28 @@ return isc::eval::EvalParser::make_COMA(loc); case 18: YY_RULE_SETUP #line 136 "lexer.ll" -driver.error (loc, "Invalid character: " + std::string(yytext)); - YY_BREAK -case YY_STATE_EOF(INITIAL): -#line 137 "lexer.ll" -return isc::eval::EvalParser::make_END(loc); +{ + // This string specifies option name starting with a letter + // and further containing letters, digits, hyphens and + // underscores. + return isc::eval::EvalParser::make_OPTION_NAME(yytext, loc); +} YY_BREAK case 19: YY_RULE_SETUP -#line 138 "lexer.ll" +#line 143 "lexer.ll" +driver.error (loc, "Invalid character: " + std::string(yytext)); + YY_BREAK +case YY_STATE_EOF(INITIAL): +#line 144 "lexer.ll" +return isc::eval::EvalParser::make_END(loc); + YY_BREAK +case 20: +YY_RULE_SETUP +#line 145 "lexer.ll" ECHO; YY_BREAK -#line 1156 "lexer.cc" +#line 1235 "lexer.cc" case YY_END_OF_BUFFER: { @@ -1218,8 +1297,7 @@ ECHO; else { /* %% [14.0] code to do back-up for compressed tables and set up yy_cp goes here */ - yy_cp = (yy_last_accepting_cpos); - yy_current_state = (yy_last_accepting_state); + yy_cp = (yy_c_buf_p); goto yy_find_action; } } @@ -1356,37 +1434,8 @@ static int yy_get_next_buffer (void) while ( num_to_read <= 0 ) { /* Not enough room in the buffer - grow it. */ - /* just a shorter name for the current buffer */ - YY_BUFFER_STATE b = YY_CURRENT_BUFFER; - - int yy_c_buf_p_offset = - (int) ((yy_c_buf_p) - b->yy_ch_buf); - - if ( b->yy_is_our_buffer ) - { - yy_size_t new_size = b->yy_buf_size * 2; - - if ( new_size <= 0 ) - b->yy_buf_size += b->yy_buf_size / 8; - else - b->yy_buf_size *= 2; - - b->yy_ch_buf = (char *) - /* Include room in for 2 EOB chars. */ - yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ); - } - else - /* Can't grow it, we don't own it. */ - b->yy_ch_buf = 0; - - if ( ! b->yy_ch_buf ) - YY_FATAL_ERROR( - "fatal error - scanner input buffer overflow" ); - - (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; - - num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - - number_to_move - 1; + YY_FATAL_ERROR( +"input buffer overflow, can't enlarge buffer because scanner uses REJECT" ); } @@ -1452,22 +1501,21 @@ static int yy_get_next_buffer (void) /* %% [15.0] code to get the start state into yy_current_state goes here */ yy_current_state = (yy_start); + (yy_state_ptr) = (yy_state_buf); + *(yy_state_ptr)++ = yy_current_state; + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) { /* %% [16.0] code to find the next state goes here */ register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); - if ( yy_accept[yy_current_state] ) - { - (yy_last_accepting_state) = yy_current_state; - (yy_last_accepting_cpos) = yy_cp; - } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; - if ( yy_current_state >= 52 ) + if ( yy_current_state >= 56 ) yy_c = yy_meta[(unsigned int) yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + *(yy_state_ptr)++ = yy_current_state; } return yy_current_state; @@ -1486,22 +1534,18 @@ static int yy_get_next_buffer (void) { register int yy_is_jam; /* %% [17.0] code to find the next state, and perhaps do backing up, goes here */ - register char *yy_cp = (yy_c_buf_p); register YY_CHAR yy_c = 1; - if ( yy_accept[yy_current_state] ) - { - (yy_last_accepting_state) = yy_current_state; - (yy_last_accepting_cpos) = yy_cp; - } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; - if ( yy_current_state >= 52 ) + if ( yy_current_state >= 56 ) yy_c = yy_meta[(unsigned int) yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; - yy_is_jam = (yy_current_state == 51); + yy_is_jam = (yy_current_state == 55); + if ( ! yy_is_jam ) + *(yy_state_ptr)++ = yy_current_state; return yy_is_jam ? 0 : yy_current_state; } @@ -2154,6 +2198,11 @@ static int yy_init_globals (void) (yy_init) = 0; (yy_start) = 0; + (yy_state_buf) = 0; + (yy_state_ptr) = 0; + (yy_full_match) = 0; + (yy_lp) = 0; + /* Defined in main.c */ #ifdef YY_STDINIT yyin = stdin; @@ -2186,6 +2235,9 @@ int yylex_destroy (void) yyfree((yy_buffer_stack) ); (yy_buffer_stack) = NULL; + yyfree ( (yy_state_buf) ); + (yy_state_buf) = NULL; + /* Reset the globals. This is important in a non-reentrant scanner so the next time * yylex() is called, initialization will occur. */ yy_init_globals( ); @@ -2249,7 +2301,7 @@ void yyfree (void * ptr ) /* %ok-for-header */ -#line 138 "lexer.ll" +#line 145 "lexer.ll" diff --git a/src/lib/eval/lexer.ll b/src/lib/eval/lexer.ll index 5dd1556512..c037874115 100644 --- a/src/lib/eval/lexer.ll +++ b/src/lib/eval/lexer.ll @@ -133,6 +133,13 @@ blank [ \t] "]" return isc::eval::EvalParser::make_RBRACKET(loc); "," return isc::eval::EvalParser::make_COMA(loc); +[A-Za-z][A-Za-z0-9_\-]+/{blank}*] { + // This string specifies option name starting with a letter + // and further containing letters, digits, hyphens and + // underscores. + return isc::eval::EvalParser::make_OPTION_NAME(yytext, loc); +} + . driver.error (loc, "Invalid character: " + std::string(yytext)); <> return isc::eval::EvalParser::make_END(loc); %% diff --git a/src/lib/eval/parser.cc b/src/lib/eval/parser.cc index 137c6d6fa2..46f902eb45 100644 --- a/src/lib/eval/parser.cc +++ b/src/lib/eval/parser.cc @@ -49,10 +49,10 @@ #line 51 "parser.cc" // lalr1.cc:412 // Unqualified %code blocks. -#line 39 "parser.yy" // lalr1.cc:413 +#line 40 "parser.yy" // lalr1.cc:413 # include "eval_context.h" -#line 67 "parser.yy" // lalr1.cc:413 +#line 73 "parser.yy" // lalr1.cc:413 namespace { @@ -206,13 +206,14 @@ namespace isc { namespace eval { /// Build a parser object. - EvalParser::EvalParser (EvalContext& ctx_yyarg) + EvalParser::EvalParser (EvalContext& ctx_yyarg, const Option::Universe& option_universe_yyarg) : #if YYDEBUG yydebug_ (false), yycdebug_ (&std::cerr), #endif - ctx (ctx_yyarg) + ctx (ctx_yyarg), + option_universe (option_universe_yyarg) {} EvalParser::~EvalParser () @@ -280,7 +281,8 @@ namespace isc { namespace eval { case 15: // "constant string" case 16: // "integer" case 17: // "constant hexstring" - case 18: // TOKEN + case 18: // "option name" + case 19: // TOKEN value.move< std::string > (that.value); break; @@ -302,7 +304,8 @@ namespace isc { namespace eval { case 15: // "constant string" case 16: // "integer" case 17: // "constant hexstring" - case 18: // TOKEN + case 18: // "option name" + case 19: // TOKEN value.copy< std::string > (that.value); break; @@ -344,30 +347,37 @@ namespace isc { namespace eval { { case 15: // "constant string" -#line 64 "parser.yy" // lalr1.cc:636 +#line 70 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< std::string > (); } -#line 350 "parser.cc" // lalr1.cc:636 +#line 353 "parser.cc" // lalr1.cc:636 break; case 16: // "integer" -#line 64 "parser.yy" // lalr1.cc:636 +#line 70 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< std::string > (); } -#line 357 "parser.cc" // lalr1.cc:636 +#line 360 "parser.cc" // lalr1.cc:636 break; case 17: // "constant hexstring" -#line 64 "parser.yy" // lalr1.cc:636 +#line 70 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< std::string > (); } -#line 364 "parser.cc" // lalr1.cc:636 +#line 367 "parser.cc" // lalr1.cc:636 break; - case 18: // TOKEN + case 18: // "option name" -#line 64 "parser.yy" // lalr1.cc:636 +#line 70 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< std::string > (); } -#line 371 "parser.cc" // lalr1.cc:636 +#line 374 "parser.cc" // lalr1.cc:636 + break; + + case 19: // TOKEN + +#line 70 "parser.yy" // lalr1.cc:636 + { yyoutput << yysym.value.template as< std::string > (); } +#line 381 "parser.cc" // lalr1.cc:636 break; @@ -570,7 +580,8 @@ namespace isc { namespace eval { case 15: // "constant string" case 16: // "integer" case 17: // "constant hexstring" - case 18: // TOKEN + case 18: // "option name" + case 19: // TOKEN yylhs.value.build< std::string > (); break; @@ -592,90 +603,124 @@ namespace isc { namespace eval { switch (yyn) { case 3: -#line 105 "parser.yy" // lalr1.cc:859 +#line 111 "parser.yy" // lalr1.cc:859 { TokenPtr eq(new TokenEqual()); ctx.expression.push_back(eq); } -#line 601 "parser.cc" // lalr1.cc:859 +#line 612 "parser.cc" // lalr1.cc:859 break; case 4: -#line 112 "parser.yy" // lalr1.cc:859 +#line 118 "parser.yy" // lalr1.cc:859 { TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ())); ctx.expression.push_back(str); } -#line 610 "parser.cc" // lalr1.cc:859 +#line 621 "parser.cc" // lalr1.cc:859 break; case 5: -#line 117 "parser.yy" // lalr1.cc:859 +#line 123 "parser.yy" // lalr1.cc:859 { TokenPtr hex(new TokenHexString(yystack_[0].value.as< std::string > ())); ctx.expression.push_back(hex); } -#line 619 "parser.cc" // lalr1.cc:859 +#line 630 "parser.cc" // lalr1.cc:859 break; case 6: -#line 122 "parser.yy" // lalr1.cc:859 +#line 128 "parser.yy" // lalr1.cc:859 { uint16_t numeric_code = convert_option_code(yystack_[3].value.as< std::string > (), yystack_[3].location, ctx); TokenPtr opt(new TokenOption(numeric_code, TokenOption::TEXTUAL)); ctx.expression.push_back(opt); } -#line 629 "parser.cc" // lalr1.cc:859 +#line 640 "parser.cc" // lalr1.cc:859 break; case 7: -#line 128 "parser.yy" // lalr1.cc:859 +#line 134 "parser.yy" // lalr1.cc:859 { uint16_t numeric_code = convert_option_code(yystack_[3].value.as< std::string > (), yystack_[3].location, ctx); TokenPtr opt(new TokenOption(numeric_code, TokenOption::HEXADECIMAL)); ctx.expression.push_back(opt); } -#line 639 "parser.cc" // lalr1.cc:859 +#line 650 "parser.cc" // lalr1.cc:859 break; case 8: -#line 134 "parser.yy" // lalr1.cc:859 +#line 140 "parser.yy" // lalr1.cc:859 + { + try { + // This may result in exception if the specified + // name is unknown. + TokenPtr opt(new TokenOption(yystack_[3].value.as< std::string > (), option_universe, + TokenOption::TEXTUAL)); + ctx.expression.push_back(opt); + + } catch (const std::exception& ex) { + ctx.error(yystack_[3].location, ex.what()); + } + } +#line 667 "parser.cc" // lalr1.cc:859 + break; + + case 9: +#line 153 "parser.yy" // lalr1.cc:859 + { + try { + // This may result in exception if the specified + // name is unknown. + TokenPtr opt(new TokenOption(yystack_[3].value.as< std::string > (), option_universe, + TokenOption::HEXADECIMAL)); + ctx.expression.push_back(opt); + + } catch (const std::exception& ex) { + ctx.error(yystack_[3].location, ex.what()); + } + } +#line 684 "parser.cc" // lalr1.cc:859 + break; + + case 10: +#line 166 "parser.yy" // lalr1.cc:859 { TokenPtr sub(new TokenSubstring()); ctx.expression.push_back(sub); } -#line 648 "parser.cc" // lalr1.cc:859 +#line 693 "parser.cc" // lalr1.cc:859 break; - case 10: -#line 143 "parser.yy" // lalr1.cc:859 + case 12: +#line 175 "parser.yy" // lalr1.cc:859 { TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ())); ctx.expression.push_back(str); } -#line 657 "parser.cc" // lalr1.cc:859 +#line 702 "parser.cc" // lalr1.cc:859 break; - case 11: -#line 150 "parser.yy" // lalr1.cc:859 + case 13: +#line 182 "parser.yy" // lalr1.cc:859 { TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ())); ctx.expression.push_back(str); } -#line 666 "parser.cc" // lalr1.cc:859 +#line 711 "parser.cc" // lalr1.cc:859 break; - case 12: -#line 155 "parser.yy" // lalr1.cc:859 + case 14: +#line 187 "parser.yy" // lalr1.cc:859 { TokenPtr str(new TokenString("all")); ctx.expression.push_back(str); } -#line 675 "parser.cc" // lalr1.cc:859 +#line 720 "parser.cc" // lalr1.cc:859 break; -#line 679 "parser.cc" // lalr1.cc:859 +#line 724 "parser.cc" // lalr1.cc:859 default: break; } @@ -930,74 +975,79 @@ namespace isc { namespace eval { } - const signed char EvalParser::yypact_ninf_ = -10; + const signed char EvalParser::yypact_ninf_ = -14; const signed char EvalParser::yytable_ninf_ = -1; const signed char EvalParser::yypact_[] = { - -4, -9, -3, -10, -10, -10, 9, -10, 12, 1, - -4, -10, -4, -2, 6, -10, 10, 2, 0, -10, - 11, -10, -10, -6, -10, -10, 8, -10 + -4, -9, -5, -14, -14, -14, 8, -14, 9, -13, + -4, -14, -4, 0, 6, 11, -14, 13, 14, 15, + 10, 12, -14, 16, -14, -14, -14, -14, -6, -14, + -14, 17, -14 }; const unsigned char EvalParser::yydefact_[] = { - 0, 0, 0, 4, 5, 9, 0, 2, 0, 0, - 0, 1, 0, 0, 0, 3, 0, 0, 0, 10, - 0, 6, 7, 0, 12, 11, 0, 8 + 0, 0, 0, 4, 5, 11, 0, 2, 0, 0, + 0, 1, 0, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 12, 0, 6, 7, 8, 9, 0, 14, + 13, 0, 10 }; const signed char EvalParser::yypgoto_[] = { - -10, -10, -10, -7, -10, -10 + -14, -14, -14, -3, -14, -14 }; const signed char EvalParser::yydefgoto_[] = { - -1, 6, 7, 8, 20, 26 + -1, 6, 7, 8, 23, 31 }; const unsigned char EvalParser::yytable_[] = { - 1, 2, 24, 14, 9, 15, 21, 22, 10, 11, - 25, 3, 16, 4, 5, 12, 17, 13, 19, 18, - 27, 23 + 1, 2, 29, 13, 9, 14, 10, 15, 11, 16, + 30, 3, 12, 4, 17, 5, 24, 25, 26, 27, + 18, 19, 20, 21, 0, 0, 28, 0, 0, 32, + 0, 22 }; - const unsigned char + const signed char EvalParser::yycheck_[] = { - 4, 5, 8, 10, 13, 12, 6, 7, 11, 0, - 16, 15, 14, 17, 18, 3, 10, 16, 16, 9, - 12, 10 + 4, 5, 8, 16, 13, 18, 11, 10, 0, 12, + 16, 15, 3, 17, 14, 19, 6, 7, 6, 7, + 14, 10, 9, 9, -1, -1, 10, -1, -1, 12, + -1, 16 }; const unsigned char EvalParser::yystos_[] = { - 0, 4, 5, 15, 17, 18, 20, 21, 22, 13, - 11, 0, 3, 16, 22, 22, 14, 10, 9, 16, - 23, 6, 7, 10, 8, 16, 24, 12 + 0, 4, 5, 15, 17, 19, 21, 22, 23, 13, + 11, 0, 3, 16, 18, 23, 23, 14, 14, 10, + 9, 9, 16, 24, 6, 7, 6, 7, 10, 8, + 16, 25, 12 }; const unsigned char EvalParser::yyr1_[] = { - 0, 19, 20, 21, 22, 22, 22, 22, 22, 22, - 23, 24, 24 + 0, 20, 21, 22, 23, 23, 23, 23, 23, 23, + 23, 23, 24, 25, 25 }; const unsigned char EvalParser::yyr2_[] = { - 0, 2, 1, 3, 1, 1, 6, 6, 8, 1, - 1, 1, 1 + 0, 2, 1, 3, 1, 1, 6, 6, 6, 6, + 8, 1, 1, 1, 1 }; @@ -1010,16 +1060,16 @@ namespace isc { namespace eval { "\"end of file\"", "error", "$undefined", "\"==\"", "\"option\"", "\"substring\"", "\"text\"", "\"hex\"", "\"all\"", "\".\"", "\",\"", "\"(\"", "\")\"", "\"[\"", "\"]\"", "\"constant string\"", "\"integer\"", - "\"constant hexstring\"", "TOKEN", "$accept", "expression", "bool_expr", - "string_expr", "start_expr", "length_expr", YY_NULLPTR + "\"constant hexstring\"", "\"option name\"", "TOKEN", "$accept", + "expression", "bool_expr", "string_expr", "start_expr", "length_expr", YY_NULLPTR }; #if YYDEBUG const unsigned char EvalParser::yyrline_[] = { - 0, 101, 101, 104, 111, 116, 121, 127, 133, 138, - 142, 149, 154 + 0, 107, 107, 110, 117, 122, 127, 133, 139, 152, + 165, 170, 174, 181, 186 }; // Print the state stack on the debug stream. @@ -1054,8 +1104,8 @@ namespace isc { namespace eval { #line 21 "parser.yy" // lalr1.cc:1167 } } // isc::eval -#line 1058 "parser.cc" // lalr1.cc:1167 -#line 161 "parser.yy" // lalr1.cc:1168 +#line 1108 "parser.cc" // lalr1.cc:1167 +#line 193 "parser.yy" // lalr1.cc:1168 void isc::eval::EvalParser::error(const location_type& loc, diff --git a/src/lib/eval/parser.h b/src/lib/eval/parser.h index 73467d5cf2..309daf747f 100644 --- a/src/lib/eval/parser.h +++ b/src/lib/eval/parser.h @@ -45,12 +45,13 @@ #include #include #include +#include #include using namespace isc::dhcp; using namespace isc::eval; -#line 54 "parser.h" // lalr1.cc:392 +#line 55 "parser.h" // lalr1.cc:392 # include # include // std::abort @@ -127,7 +128,7 @@ using namespace isc::eval; #line 21 "parser.yy" // lalr1.cc:392 namespace isc { namespace eval { -#line 131 "parser.h" // lalr1.cc:392 +#line 132 "parser.h" // lalr1.cc:392 @@ -297,6 +298,7 @@ namespace isc { namespace eval { // "constant string" // "integer" // "constant hexstring" + // "option name" // TOKEN char dummy1[sizeof(std::string)]; }; @@ -337,7 +339,8 @@ namespace isc { namespace eval { TOKEN_STRING = 270, TOKEN_INTEGER = 271, TOKEN_HEXSTRING = 272, - TOKEN_TOKEN = 273 + TOKEN_OPTION_NAME = 273, + TOKEN_TOKEN = 274 }; }; @@ -508,13 +511,17 @@ namespace isc { namespace eval { symbol_type make_HEXSTRING (const std::string& v, const location_type& l); + static inline + symbol_type + make_OPTION_NAME (const std::string& v, const location_type& l); + static inline symbol_type make_TOKEN (const std::string& v, const location_type& l); /// Build a parser object. - EvalParser (EvalContext& ctx_yyarg); + EvalParser (EvalContext& ctx_yyarg, const Option::Universe& option_universe_yyarg); virtual ~EvalParser (); /// Parse. @@ -597,7 +604,7 @@ namespace isc { namespace eval { // number is the opposite. If YYTABLE_NINF, syntax error. static const unsigned char yytable_[]; - static const unsigned char yycheck_[]; + static const signed char yycheck_[]; // YYSTOS[STATE-NUM] -- The (internal number of the) accessing // symbol of state STATE-NUM. @@ -717,17 +724,18 @@ namespace isc { namespace eval { enum { yyeof_ = 0, - yylast_ = 21, ///< Last index in yytable_. + yylast_ = 31, ///< Last index in yytable_. yynnts_ = 6, ///< Number of nonterminal symbols. yyfinal_ = 11, ///< Termination state number. yyterror_ = 1, yyerrcode_ = 256, - yyntokens_ = 19 ///< Number of tokens. + yyntokens_ = 20 ///< Number of tokens. }; // User arguments. EvalContext& ctx; + const Option::Universe& option_universe; }; // Symbol number corresponding to token number t. @@ -766,9 +774,9 @@ namespace isc { namespace eval { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18 + 15, 16, 17, 18, 19 }; - const unsigned int user_token_number_max_ = 273; + const unsigned int user_token_number_max_ = 274; const token_number_type undef_token_ = 2; if (static_cast(t) <= yyeof_) @@ -804,7 +812,8 @@ namespace isc { namespace eval { case 15: // "constant string" case 16: // "integer" case 17: // "constant hexstring" - case 18: // TOKEN + case 18: // "option name" + case 19: // TOKEN value.copy< std::string > (other.value); break; @@ -828,7 +837,8 @@ namespace isc { namespace eval { case 15: // "constant string" case 16: // "integer" case 17: // "constant hexstring" - case 18: // TOKEN + case 18: // "option name" + case 19: // TOKEN value.copy< std::string > (v); break; @@ -883,7 +893,8 @@ namespace isc { namespace eval { case 15: // "constant string" case 16: // "integer" case 17: // "constant hexstring" - case 18: // TOKEN + case 18: // "option name" + case 19: // TOKEN value.template destroy< std::string > (); break; @@ -913,7 +924,8 @@ namespace isc { namespace eval { case 15: // "constant string" case 16: // "integer" case 17: // "constant hexstring" - case 18: // TOKEN + case 18: // "option name" + case 19: // TOKEN value.move< std::string > (s.value); break; @@ -973,7 +985,7 @@ namespace isc { namespace eval { yytoken_number_[] = { 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, - 265, 266, 267, 268, 269, 270, 271, 272, 273 + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274 }; return static_cast (yytoken_number_[type]); } @@ -1074,6 +1086,12 @@ namespace isc { namespace eval { return symbol_type (token::TOKEN_HEXSTRING, v, l); } + EvalParser::symbol_type + EvalParser::make_OPTION_NAME (const std::string& v, const location_type& l) + { + return symbol_type (token::TOKEN_OPTION_NAME, v, l); + } + EvalParser::symbol_type EvalParser::make_TOKEN (const std::string& v, const location_type& l) { @@ -1083,7 +1101,7 @@ namespace isc { namespace eval { #line 21 "parser.yy" // lalr1.cc:392 } } // isc::eval -#line 1087 "parser.h" // lalr1.cc:392 +#line 1105 "parser.h" // lalr1.cc:392 diff --git a/src/lib/eval/parser.yy b/src/lib/eval/parser.yy index 3cec6889d4..e462cb63d6 100644 --- a/src/lib/eval/parser.yy +++ b/src/lib/eval/parser.yy @@ -25,6 +25,7 @@ #include #include #include +#include #include using namespace isc::dhcp; @@ -39,6 +40,10 @@ using namespace isc::eval; { # include "eval_context.h" } +// Option universe: DHCPv4 or DHCPv6. This is required to use correct option +// definition set to map option names to codes. +%parse-param { const Option::Universe& option_universe } + %define api.token.prefix {TOKEN_} %token END 0 "end of file" @@ -59,6 +64,7 @@ using namespace isc::eval; %token STRING "constant string" %token INTEGER "integer" %token HEXSTRING "constant hexstring" +%token OPTION_NAME "option name" %token TOKEN %printer { yyoutput << $$; } <*>; @@ -130,6 +136,32 @@ string_expr : STRING TokenPtr opt(new TokenOption(numeric_code, TokenOption::HEXADECIMAL)); ctx.expression.push_back(opt); } + | OPTION "[" OPTION_NAME "]" DOT TEXT + { + try { + // This may result in exception if the specified + // name is unknown. + TokenPtr opt(new TokenOption($3, option_universe, + TokenOption::TEXTUAL)); + ctx.expression.push_back(opt); + + } catch (const std::exception& ex) { + ctx.error(@3, ex.what()); + } + } + | OPTION "[" OPTION_NAME "]" DOT HEX + { + try { + // This may result in exception if the specified + // name is unknown. + TokenPtr opt(new TokenOption($3, option_universe, + TokenOption::HEXADECIMAL)); + ctx.expression.push_back(opt); + + } catch (const std::exception& ex) { + ctx.error(@3, ex.what()); + } + } | SUBSTRING "(" string_expr "," start_expr "," length_expr ")" { TokenPtr sub(new TokenSubstring()); diff --git a/src/lib/eval/tests/context_unittest.cc b/src/lib/eval/tests/context_unittest.cc index d1770fe39b..a305d76286 100644 --- a/src/lib/eval/tests/context_unittest.cc +++ b/src/lib/eval/tests/context_unittest.cc @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -94,7 +95,7 @@ public: /// @brief checks if the given expression raises the expected message /// when it is parsed. void checkError(const string& expr, const string& msg) { - EvalContext eval; + EvalContext eval(Option::V4); parsed_ = false; try { parsed_ = eval.parseString(expr); @@ -115,15 +116,15 @@ public: // Test the parsing of a basic expression TEST_F(EvalContextTest, basic) { - EvalContext tmp; + EvalContext eval(Option::V4); - EXPECT_NO_THROW(parsed_ = tmp.parseString("option[123].text == 'MSFT'")); + EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].text == 'MSFT'")); EXPECT_TRUE(parsed_); } // Test the parsing of a string terminal TEST_F(EvalContextTest, string) { - EvalContext eval; + EvalContext eval(Option::V4); EXPECT_NO_THROW(parsed_ = eval.parseString("'foo' == 'bar'")); EXPECT_TRUE(parsed_); @@ -140,7 +141,7 @@ TEST_F(EvalContextTest, string) { // Test the parsing of a basic expression using integers TEST_F(EvalContextTest, integer) { - EvalContext eval; + EvalContext eval(Option::V4); EXPECT_NO_THROW(parsed_ = eval.parseString("substring(option[123].text, 0, 2) == '42'")); @@ -149,7 +150,7 @@ TEST_F(EvalContextTest, integer) { // Test the parsing of a hexstring terminal TEST_F(EvalContextTest, hexstring) { - EvalContext eval; + EvalContext eval(Option::V4); EXPECT_NO_THROW(parsed_ = eval.parseString("0x666f6f == 'foo'")); EXPECT_TRUE(parsed_); @@ -164,7 +165,7 @@ TEST_F(EvalContextTest, hexstring) { // Test the parsing of a hexstring terminal with an odd number of // hexadecimal digits TEST_F(EvalContextTest, oddHexstring) { - EvalContext eval; + EvalContext eval(Option::V4); EXPECT_NO_THROW(parsed_ = eval.parseString("0X7 == 'foo'")); EXPECT_TRUE(parsed_); @@ -178,7 +179,7 @@ TEST_F(EvalContextTest, oddHexstring) { // Test the parsing of an equal expression TEST_F(EvalContextTest, equal) { - EvalContext eval; + EvalContext eval(Option::V4); EXPECT_NO_THROW(parsed_ = eval.parseString("'foo' == 'bar'")); EXPECT_TRUE(parsed_); @@ -196,7 +197,7 @@ TEST_F(EvalContextTest, equal) { // Test the parsing of an option terminal TEST_F(EvalContextTest, option) { - EvalContext eval; + EvalContext eval(Option::V4); EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].text == 'foo'")); EXPECT_TRUE(parsed_); @@ -204,9 +205,31 @@ TEST_F(EvalContextTest, option) { checkTokenOption(eval.expression.at(0), 123); } +// Test parsing of an option identified by name. +TEST_F(EvalContextTest, optionWithName) { + EvalContext eval(Option::V4); + + // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++. + EXPECT_NO_THROW(parsed_ = eval.parseString("option[host-name].text == 'foo'")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(3, eval.expression.size()); + checkTokenOption(eval.expression.at(0), 12); +} + +// Test checking that whitespace can surround option name. +TEST_F(EvalContextTest, optionWithNameAndWhitespace) { + EvalContext eval(Option::V4); + + // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++. + EXPECT_NO_THROW(parsed_ = eval.parseString("option[ host-name ].text == 'foo'")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(3, eval.expression.size()); + checkTokenOption(eval.expression.at(0), 12); +} + // Test parsing of an option represented as hexadecimal string. TEST_F(EvalContextTest, optionHex) { - EvalContext eval; + EvalContext eval(Option::V4); EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].hex == 0x666F6F")); EXPECT_TRUE(parsed_); @@ -216,7 +239,7 @@ TEST_F(EvalContextTest, optionHex) { // Test the parsing of a substring expression TEST_F(EvalContextTest, substring) { - EvalContext eval; + EvalContext eval(Option::V4); EXPECT_NO_THROW(parsed_ = eval.parseString("substring('foobar',2,all) == 'obar'")); @@ -288,14 +311,12 @@ TEST_F(EvalContextTest, parseErrors) { checkError("option(10) == 'ab'", ":1.7: syntax error, " "unexpected (, expecting ["); - checkError("option['ab'].text == 'foo'", - ":1.8-11: syntax error, " - "unexpected constant string, " - "expecting integer"); + checkError("option[ab].text == 'foo'", + ":1.8-9: option 'ab' is not defined"); checkError("option[0xa].text == 'ab'", ":1.8-10: syntax error, " "unexpected constant hexstring, " - "expecting integer"); + "expecting integer or option name"); checkError("substring('foobar') == 'f'", ":1.19: syntax error, " "unexpected ), expecting \",\""); diff --git a/src/lib/eval/tests/evaluate_unittest.cc b/src/lib/eval/tests/evaluate_unittest.cc index 7aa1f78c9a..d404b12830 100644 --- a/src/lib/eval/tests/evaluate_unittest.cc +++ b/src/lib/eval/tests/evaluate_unittest.cc @@ -217,7 +217,7 @@ TEST_F(EvaluateTest, optionHex) { ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::HEXADECIMAL))); e_.push_back(toption); - ASSERT_NO_THROW(tstring.reset(new TokenString("0x68756E6472656434"))); + ASSERT_NO_THROW(tstring.reset(new TokenString("hundred4"))); e_.push_back(tstring); ASSERT_NO_THROW(tequal.reset(new TokenEqual())); e_.push_back(tequal); diff --git a/src/lib/eval/tests/token_unittest.cc b/src/lib/eval/tests/token_unittest.cc index c4d6e07653..39ca7f3889 100644 --- a/src/lib/eval/tests/token_unittest.cc +++ b/src/lib/eval/tests/token_unittest.cc @@ -14,6 +14,9 @@ #include #include +#include +#include +#include #include #include #include @@ -52,6 +55,13 @@ public: pkt6_->addOption(option_str6_); } + /// @brief Destructor. + /// + /// Removes any option definitions registered in runtime. + virtual ~TokenTest() { + LibDHCP::clearRuntimeOptionDefs(); + } + TokenPtr t_; ///< Just a convenience pointer ValueStack values_; ///< evaluated values will be stored here @@ -62,6 +72,23 @@ public: OptionPtr option_str4_; ///< A string option for DHCPv4 OptionPtr option_str6_; ///< A string option for DHCPv6 + /// @brief Create definitions of DHCPv4 options used by unit tests. + void createOptionDefinitions4() { + OptionDefSpaceContainer defs; + OptionDefinitionPtr opt_def4(new OptionDefinition("name-hundred4", + 100, "string")); + defs.addItem(opt_def4, "dhcp4"); + LibDHCP::setRuntimeOptionDefs(defs); + } + + /// @brief Create definitions of DHCPv6 options used by unit tests. + void createOptionDefinitions6() { + OptionDefSpaceContainer defs; + OptionDefinitionPtr opt_def6(new OptionDefinition("name-hundred6", + 100, "string")); + defs.addItem(opt_def6, "dhcp6"); + LibDHCP::setRuntimeOptionDefs(defs); + } /// @brief Verify that the substring eval works properly /// @@ -274,6 +301,28 @@ TEST_F(TokenTest, optionString4) { EXPECT_EQ("hundred4", values_.top()); } +// This test checks if a token representing an option identified by name is +// able to extract this option from an IPv4 packet and properly store the +// option's value. +TEST_F(TokenTest, optionWithNameString4) { + // Create definition of option 100 to provide a mapping between option + // code and option name. + ASSERT_NO_THROW(createOptionDefinitions4()); + + // Create token for option referenced by name. The constructor should + // map the option name to its code. + TokenPtr token; + ASSERT_NO_THROW(token.reset(new TokenOption("name-hundred4", Option::V4, + TokenOption::TEXTUAL))); + + // Evaluate option in the packet. + ASSERT_NO_THROW(token->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + + // Then the content of the option. + EXPECT_EQ("hundred4", values_.top()); +} + // This test checks if a token representing option value is able to extract // the option from an IPv4 packet and properly store its value in a // hexadecimal format. @@ -300,7 +349,29 @@ TEST_F(TokenTest, optionHexString4) { values_.pop(); // Then the content of the option 100. - EXPECT_EQ("0x68756E6472656434", values_.top()); + EXPECT_EQ("hundred4", values_.top()); +} + +// This test checks if a token representing an option identified by name is +// able to extract this option from an IPv4 packet and properly store its +// value in the hexadecimal format. +TEST_F(TokenTest, optionWithNameHexString4) { + // Create definition of option 100 to provide a mapping between option + // code and option name. + ASSERT_NO_THROW(createOptionDefinitions4()); + + // Create token for option referenced by name. The constructor should + // map the option name to its code. + TokenPtr token; + ASSERT_NO_THROW(token.reset(new TokenOption("name-hundred4", Option::V4, + TokenOption::HEXADECIMAL))); + + // Evaluate option in the packet. + ASSERT_NO_THROW(token->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + + // Then the content of the option. + EXPECT_EQ("hundred4", values_.top()); } // This test checks if a token representing an option value is able to extract @@ -331,6 +402,29 @@ TEST_F(TokenTest, optionString6) { EXPECT_EQ("hundred6", values_.top()); } +// This test checks if a token representing an option identified by name is +// able to extract this option from an IPv6 packet and properly store the +// option's value. +TEST_F(TokenTest, optionWithNameString6) { + // Create definition of option 100 to provide a mapping between option + // code and option name. + ASSERT_NO_THROW(createOptionDefinitions6()); + + // Create token for option referenced by name. The constructor should + // map the option name to its code. + TokenPtr token; + ASSERT_NO_THROW(token.reset(new TokenOption("name-hundred6", Option::V6, + TokenOption::TEXTUAL))); + + // Evaluate option in the packet. + ASSERT_NO_THROW(token->evaluate(*pkt6_, values_)); + ASSERT_EQ(1, values_.size()); + + // Then the content of the option. + EXPECT_EQ("hundred6", values_.top()); +} + + // This test checks if a token representing an option value is able to extract // the option from an IPv6 packet and properly store its value in hexadecimal // format. @@ -357,7 +451,29 @@ TEST_F(TokenTest, optionHexString6) { values_.pop(); // Then the content of the option 100. - EXPECT_EQ("0x68756E6472656436", values_.top()); + EXPECT_EQ("hundred6", values_.top()); +} + +// This test checks if a token representing an option identified by name is +// able to extract this option from an IPv6 packet and properly store its +// value in the hexadecimal format. +TEST_F(TokenTest, optionWithNameHexString6) { + // Create definition of option 100 to provide a mapping between option + // code and option name. + ASSERT_NO_THROW(createOptionDefinitions6()); + + // Create token for option referenced by name. The constructor should + // map the option name to its code. + TokenPtr token; + ASSERT_NO_THROW(token.reset(new TokenOption("name-hundred6", Option::V6, + TokenOption::HEXADECIMAL))); + + // Evaluate option in the packet. + ASSERT_NO_THROW(token->evaluate(*pkt6_, values_)); + ASSERT_EQ(1, values_.size()); + + // Then the content of the option. + EXPECT_EQ("hundred6", values_.top()); } // This test checks if a token representing an == operator is able to diff --git a/src/lib/eval/token.cc b/src/lib/eval/token.cc index 85a4806f12..4cbae9568b 100644 --- a/src/lib/eval/token.cc +++ b/src/lib/eval/token.cc @@ -14,6 +14,8 @@ #include #include +#include +#include #include #include #include @@ -62,16 +64,42 @@ TokenHexString::evaluate(const Pkt& /*pkt*/, ValueStack& values) { values.push(value_); } +TokenOption::TokenOption(const std::string& option_name, + const Option::Universe& option_universe, + const RepresentationType& rep_type) + : option_code_(0), representation_type_(rep_type) { + OptionDefinitionPtr option_def = LibDHCP::getOptionDef(option_universe, + option_name); + if (!option_def) { + const std::string global_space = + (option_universe == Option::V4) ? "dhcp4" : "dhcp6"; + option_def = LibDHCP::getRuntimeOptionDef(global_space, option_name); + } + + if (!option_def) { + isc_throw(BadValue, "option '" << option_name << "' is not defined"); + } + + option_code_ = option_def->getCode(); +} + + void TokenOption::evaluate(const Pkt& pkt, ValueStack& values) { OptionPtr opt = pkt.getOption(option_code_); + std::string opt_str; if (opt) { - values.push(representation_type_ == TEXTUAL ? opt->toString() - : opt->toHexString()); - } else { - // Option not found, push empty string - values.push(""); + if (representation_type_ == TEXTUAL) { + opt_str = opt->toString(); + } else { + std::vector binary = opt->toBinary(); + opt_str.assign(binary.begin(), binary.end()); + } } + + // Push value of the option or empty string if there was no such option + // in the packet. + values.push(opt_str); } void diff --git a/src/lib/eval/token.h b/src/lib/eval/token.h index a476948adc..4592958a79 100644 --- a/src/lib/eval/token.h +++ b/src/lib/eval/token.h @@ -16,8 +16,10 @@ #define TOKEN_H #include +#include #include #include +#include namespace isc { namespace dhcp { @@ -161,16 +163,24 @@ public: }; /// @brief Constructor that takes an option code as a parameter - /// @param option_code code of the option /// - /// Note: There is no constructor that takes option_name, as it would - /// introduce complex dependency of the libkea-eval on libdhcpsrv. - /// - /// @param option_code code of the option to be represented. + /// @param option_code Code of the option to be represented. /// @param rep_type Token representation type. TokenOption(const uint16_t option_code, const RepresentationType& rep_type) : option_code_(option_code), representation_type_(rep_type) {} + /// @brief Constructor that takes option name as a parameter. + /// + /// This constructor will throw exception if there is no definition using + /// specified option name in libdhcp++. + /// + /// @param option_name Name of the option to be represented. + /// @param option_universe Option universe: DHCPv4 or DHCPv6. + /// @param rep_type Token representation type. + TokenOption(const std::string& option_name, + const Option::Universe& option_universe, + const RepresentationType& rep_type); + /// @brief Evaluates the values of the option /// /// This token represents a value of the option, so this method attempts diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am index 6806ca2ffa..b6ba4a095a 100644 --- a/src/lib/util/Makefile.am +++ b/src/lib/util/Makefile.am @@ -21,6 +21,7 @@ libkea_util_la_SOURCES += pointer_util.h libkea_util_la_SOURCES += process_spawn.h process_spawn.cc libkea_util_la_SOURCES += range_utilities.h libkea_util_la_SOURCES += signal_set.cc signal_set.h +libkea_util_la_SOURCES += staged_value.h libkea_util_la_SOURCES += stopwatch.cc stopwatch.h libkea_util_la_SOURCES += stopwatch_impl.cc stopwatch_impl.h libkea_util_la_SOURCES += versioned_csv_file.h versioned_csv_file.cc diff --git a/src/lib/util/staged_value.h b/src/lib/util/staged_value.h new file mode 100644 index 0000000000..bb41399af5 --- /dev/null +++ b/src/lib/util/staged_value.h @@ -0,0 +1,126 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef STAGED_VALUE_H +#define STAGED_VALUE_H + +#include +#include + +namespace isc { +namespace util { + +/// @brief This class implements set/commit mechanism for a single object. +/// +/// In some cases it is desired to set value for an object while keeping +/// ability to revert to an original value under certain conditions. +/// This is often desired for objects holding some part of application's +/// configuration. Configuration is usually a multi-step process and +/// may fail on almost any stage. If this happens, the last good +/// configuration should be used. This implies that some of the state +/// of some of the objects needs to be reverted. +/// +/// This class implements a mechanism for setting and committing a value. +/// Until the new value has been committed it is possible to revert to +/// an original value. +/// +/// @tparam ValueType Type of the value represented by this class. +template +class StagedValue : public boost::noncopyable { +public: + + /// @brief Constructor. + /// + /// Initializes the default value. + StagedValue() + : staging_(new ValueType()), current_(new ValueType()), + modified_(false) { + } + + /// @brief Retrieves current value. + /// + /// If the value hasn't been modified since last commit, reset or + /// revert operation, a committed value is returned. If the value + /// has been modified, the modified value is returned. + const ValueType& getValue() const { + return (modified_ ? *staging_ : *current_); + } + + /// @brief Sets new value. + /// + /// @param new_value New value to be assigned. + void setValue(const ValueType& new_value) { + *staging_ = new_value; + modified_ = true; + } + + /// @brief Commits a value. + void commit() { + // Only apply changes if any modifications made. + if (modified_) { + current_ = staging_; + } + revert(); + } + + /// @brief Resets value to defaults. + void reset() { + revert(); + current_.reset(new ValueType()); + } + + /// @brief Reverts any modifications since last commit. + void revert() { + staging_.reset(new ValueType()); + modified_ = false; + } + + /// @brief Assignment operator. + /// + /// @param value New value to be assigned. + /// @return Reference to this. + StagedValue& operator=(const ValueType& value) { + setValue(value); + return (*this); + } + + /// @brief Conversion operator to value type. + /// + /// @return Reference to value represented by this object. + operator const ValueType&() const { + return (getValue()); + } + +private: + + /// @brief Pointer to staging value. + /// + /// This value holds any modifications made. + boost::shared_ptr staging_; + + /// @brief Pointer to committed value. + /// + /// This value holds last committed changes. + boost::shared_ptr current_; + + /// @brief Boolean flag which indicates if any modifications have been + /// applied since last commit. + bool modified_; + +}; + +} // namespace isc::util +} // namespace isc + +#endif // STAGED_VALUE_H diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am index 00122b62f3..96cd389fd6 100644 --- a/src/lib/util/tests/Makefile.am +++ b/src/lib/util/tests/Makefile.am @@ -45,6 +45,7 @@ run_unittests_SOURCES += process_spawn_unittest.cc run_unittests_SOURCES += qid_gen_unittest.cc run_unittests_SOURCES += random_number_generator_unittest.cc run_unittests_SOURCES += socketsession_unittest.cc +run_unittests_SOURCES += staged_value_unittest.cc run_unittests_SOURCES += strutil_unittest.cc run_unittests_SOURCES += time_utilities_unittest.cc run_unittests_SOURCES += range_utilities_unittest.cc diff --git a/src/lib/util/tests/staged_value_unittest.cc b/src/lib/util/tests/staged_value_unittest.cc new file mode 100644 index 0000000000..a108d5def9 --- /dev/null +++ b/src/lib/util/tests/staged_value_unittest.cc @@ -0,0 +1,114 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include +#include +#include + +namespace { + +using namespace isc::util; + +// This test verifies that the value can be assigned and committed. +TEST(StagedValueTest, assignAndCommit) { + // Initally the value should be set to a default + StagedValue value; + ASSERT_EQ(0, value.getValue()); + + // Set the new value without committing it and make sure it + // can be retrieved. + value.setValue(4); + ASSERT_EQ(4, value.getValue()); + + // Commit the value and make sure it still can be retrieved. + value.commit(); + ASSERT_EQ(4, value.getValue()); + + // Set new value and retrieve it. + value.setValue(10); + ASSERT_EQ(10, value.getValue()); + + // Do it again and commit it. + value.setValue(20); + ASSERT_EQ(20, value.getValue()); + value.commit(); + EXPECT_EQ(20, value.getValue()); +} + +// This test verifies that the value can be reverted if it hasn't been +// committed. +TEST(StagedValueTest, revert) { + // Set the value and commit. + StagedValue value; + value.setValue(123); + value.commit(); + + // Set new value and do not commit. + value.setValue(500); + // The new value should be the one returned. + ASSERT_EQ(500, value.getValue()); + // But, reverting gets us back to original value. + value.revert(); + EXPECT_EQ(123, value.getValue()); + // Reverting again doesn't have any effect. + value.revert(); + EXPECT_EQ(123, value.getValue()); +} + +// This test verifies that the value can be restored to an original one. +TEST(StagedValueTest, reset) { + // Set the new value and commit. + StagedValue value; + value.setValue(123); + value.commit(); + + // Override the value but do not commit. + value.setValue(500); + + // Resetting should take us back to default value. + value.reset(); + EXPECT_EQ(0, value.getValue()); + value.revert(); + EXPECT_EQ(0, value.getValue()); +} + +// This test verifies that second commit doesn't modify a value. +TEST(StagedValueTest, commit) { + // Set the value and commit. + StagedValue value; + value.setValue(123); + value.commit(); + + // Second commit should have no effect. + value.commit(); + EXPECT_EQ(123, value.getValue()); +} + +// This test checks that type conversion operator works correctly. +TEST(StagedValueTest, conversionOperator) { + StagedValue value; + value.setValue(244); + EXPECT_EQ(244, value); +} + +// This test checks that the assignment operator works correctly. +TEST(StagedValueTest, assignmentOperator) { + StagedValue value; + value = 111; + EXPECT_EQ(111, value); +} + + +} // end of anonymous namespace