diff --git a/doc/sphinx/arm/hooks.rst b/doc/sphinx/arm/hooks.rst index 7e131c173aaa1940c5f15308c6745aca6a670da3..8dd1399d46dd9a65b7f7cc1188415612e9c3e5b3 100644 --- a/doc/sphinx/arm/hooks.rst +++ b/doc/sphinx/arm/hooks.rst @@ -1931,6 +1931,9 @@ to the option one with: parameter in the container option entry applies to the whole ``sub-options`` list. +- ``sub-options`` - specifies a list of sub-option configuration similar to the + option one. + For instance this configuration adds a string sub-option in the DHCPv4 ``vendor-encapsulated-options`` (code 43) option. Note this option in last resort encapsulates the ``vendor-encapsulated-options`` space. diff --git a/src/hooks/dhcp/flex_option/flex_option.cc b/src/hooks/dhcp/flex_option/flex_option.cc index 848989f04408ff1c81c8991f4fce4d9223ed2cf2..c2c83a919641269b9371d133ec700d22569b57d3 100644 --- a/src/hooks/dhcp/flex_option/flex_option.cc +++ b/src/hooks/dhcp/flex_option/flex_option.cc @@ -95,6 +95,7 @@ const SimpleKeywords FlexOptionImpl::SUB_OPTION_PARAMETERS = { { "add", Element::string }, { "supersede", Element::string }, { "remove", Element::string }, + { "sub-options", Element::list }, { "container-add", Element::boolean }, { "container-remove", Element::boolean }, { "client-class", Element::string }, @@ -126,7 +127,6 @@ FlexOptionImpl::FlexOptionImpl() { } FlexOptionImpl::~FlexOptionImpl() { - sub_option_config_map_.clear(); option_config_map_.clear(); } @@ -292,6 +292,15 @@ FlexOptionImpl::parseOptionConfig(ConstElementPtr option) { << "incompatible in the same entry"); } parseSubOptions(sub_options, opt_cfg, universe); + + // Add the option configuration only if there was at least one + // sub-option added. + if (opt_cfg->getSubOptionConfigMap().size()) { + // The [] operator creates the item if it does not exist before + // returning a reference to it. + OptionConfigList& opt_lst = option_config_map_[code]; + opt_lst.push_back(opt_cfg); + } } else { parseAction(option, opt_cfg, universe, "add", ADD, EvalContext::PARSER_STRING); @@ -448,6 +457,8 @@ FlexOptionImpl::parseSubOption(ConstElementPtr sub_option, sub_cfg->setClass(class_elem->stringValue()); } + ConstElementPtr sub_options = sub_option->get("sub-options"); + // sub_cfg initial action is NONE. parseAction(sub_option, sub_cfg, universe, "add", ADD, EvalContext::PARSER_STRING); @@ -456,7 +467,7 @@ FlexOptionImpl::parseSubOption(ConstElementPtr sub_option, parseAction(sub_option, sub_cfg, universe, "remove", REMOVE, EvalContext::PARSER_BOOL); - if (sub_cfg->getAction() == NONE) { + if (sub_cfg->getAction() == NONE && !sub_options) { isc_throw(BadValue, "no action: " << sub_option->str()); } @@ -477,12 +488,28 @@ FlexOptionImpl::parseSubOption(ConstElementPtr sub_option, // The [] operator creates the item if it does not exist before // returning a reference to it. uint16_t opt_code = opt_cfg->getCode(); - SubOptionConfigMap& sub_map = sub_option_config_map_[opt_code]; + SubOptionConfigMap& sub_map = opt_cfg->getMutableSubOptionConfigMap(); if (sub_map.count(code)) { isc_throw(BadValue, "sub-option " << code << " of option " << opt_code << " was already specified"); } sub_map[code] = sub_cfg; + + if (sub_options) { + string action; + if (sub_option->contains("add")) { + action = "add"; + } else if (sub_option->contains("supersede")) { + action = "supersede"; + } else if (sub_option->contains("remove")) { + action = "remove"; + } + if (!action.empty()) { + isc_throw(BadValue, "'sub-options' and '" << action << "' are " + << "incompatible in the same entry"); + } + parseSubOptions(sub_options, sub_cfg, universe); + } } void diff --git a/src/hooks/dhcp/flex_option/flex_option.h b/src/hooks/dhcp/flex_option/flex_option.h index bda8ca186ef8d547213549d731a78075f6916c21..56a6179abc496561d50675b2120654645fd2df4b 100644 --- a/src/hooks/dhcp/flex_option/flex_option.h +++ b/src/hooks/dhcp/flex_option/flex_option.h @@ -50,6 +50,16 @@ public: REMOVE }; + /// @brief Forward declaration of Sub-option configuration. + class SubOptionConfig; + + /// @brief The type of shared pointers to sub-option config. + typedef boost::shared_ptr SubOptionConfigPtr; + + /// @brief The type of the sub-option config map. + /// @note the index is the sub-option code. + typedef std::map SubOptionConfigMap; + /// @brief Base option configuration. /// /// Per option configuration. @@ -134,6 +144,35 @@ public: return (class_); } + /// @brief Process a query / response pair. + /// + /// @tparam PktType The type of pointers to packets: Pkt4Ptr or Pkt6Ptr. + /// @param universe The option universe: Option::V4 or Option::V6. + /// @param query The query packet. + /// @param response The response packet. + /// @param parent_opt The parent option or null if options are stored in + /// the packet itself. + /// + /// @return true if parent option needs to be added to the option chain. + template + bool processSubOptions(isc::dhcp::Option::Universe universe, + PktType query, PktType response, + isc::dhcp::OptionPtr parent_opt = isc::dhcp::OptionPtr()); + + /// @brief Get the sub-option config map. + /// + /// @return The sub-option config map. + const SubOptionConfigMap& getSubOptionConfigMap() const { + return (sub_option_config_map_); + } + + /// @brief Get a mutable reference to the sub-option config map. + /// + /// @return The sub-option config map. + SubOptionConfigMap& getMutableSubOptionConfigMap() { + return (sub_option_config_map_); + } + private: /// @brief The code. uint16_t code_; @@ -153,6 +192,9 @@ public: /// @brief The client class aka guard name. isc::dhcp::ClientClass class_; + + /// @brief The sub-option config map. + SubOptionConfigMap sub_option_config_map_; }; /// @brief The type of shared pointers to option config. @@ -164,93 +206,6 @@ public: /// @brief The type of the option config map. typedef std::map OptionConfigMap; - /// @brief Sub-option configuration. - /// - /// Per sub-option configuration. - class SubOptionConfig : public OptionConfig { - public: - /// @brief Constructor. - /// - /// @param code the sub-option code. - /// @param def the sub-option definition. - /// @param container pointer to the container option. - SubOptionConfig(uint16_t code, isc::dhcp::OptionDefinitionPtr def, - OptionConfigPtr container); - - /// @brief Destructor. - virtual ~SubOptionConfig(); - - /// @brief Set vendor id. - /// - /// @param vendor_id the vendor id. - void setVendorId(uint32_t vendor_id) { - vendor_id_ = vendor_id; - } - - /// @brief Return vendor id. - /// - /// @return vendor id. - uint32_t getVendorId() const { - return (vendor_id_); - } - - /// @brief Return container code. - /// - /// @return container code. - uint16_t getContainerCode() const { - return (container_->getCode()); - } - - /// @brief Return container definition. - /// - /// @return container definition. - isc::dhcp::OptionDefinitionPtr getContainerDef() const { - return (container_->getOptionDef()); - } - - /// @brief Return container client class. - /// - /// @return container client class name. - const isc::dhcp::ClientClass& getContainerClass() const { - return (container_->getClass()); - } - - /// @brief Set action on the container. - /// - /// @param action the action. - void setContainerAction(Action action) { - container_action_ = action; - } - - /// @brief Return action on the container. - /// - /// @return action on the container. - Action getContainerAction() const { - return (container_action_); - } - - private: - /// @brief Pointer to the container option configuration. - OptionConfigPtr container_; - - /// @brief Vendor id (0 when the container is not a vendor one). - uint32_t vendor_id_; - - /// @brief The action on the container. - Action container_action_; - }; - - /// @brief The type of shared pointers to sub-option config. - typedef boost::shared_ptr SubOptionConfigPtr; - - /// @brief The type of the sub-option config map. - /// @note the index is the sub-option code. - typedef std::map SubOptionConfigMap; - - /// @brief The type of the map of sub-option config maps. - /// @note the index is the container option code. - typedef std::map SubOptionConfigMapMap; - /// @brief Constructor. FlexOptionImpl(); @@ -264,13 +219,6 @@ public: return (option_config_map_); } - /// @brief Get the sub-option config map of maps. - /// - /// @return The sub-option config map of maps. - const SubOptionConfigMapMap& getSubOptionConfigMap() const { - return (sub_option_config_map_); - } - /// @brief Configure the Flex Option implementation. /// /// @param options The element with option config list. @@ -372,175 +320,86 @@ public: logAction(REMOVE, code, ""); break; } - } - } - for (auto pair : getSubOptionConfigMap()) { - for (auto sub_pair : pair.second) { - const SubOptionConfigPtr& sub_cfg = sub_pair.second; - uint16_t sub_code = sub_cfg->getCode(); - uint16_t opt_code = sub_cfg->getContainerCode(); - const isc::dhcp::ClientClass& opt_class = - sub_cfg->getContainerClass(); - if (!opt_class.empty()) { - if (!query->inClass(opt_class)) { - logClass(opt_class, opt_code); - continue; - } - } - const isc::dhcp::ClientClass& sub_class = - sub_cfg->getClass(); - if (!sub_class.empty()) { - if (!query->inClass(sub_class)) { - logSubClass(sub_class, sub_code, opt_code); - continue; - } - } - std::string value; - isc::dhcp::OptionBuffer buffer; - isc::dhcp::OptionPtr opt = response->getOption(opt_code); - isc::dhcp::OptionPtr sub; - isc::dhcp::OptionDefinitionPtr def = sub_cfg->getOptionDef(); - uint32_t vendor_id = sub_cfg->getVendorId(); - switch (sub_cfg->getAction()) { - case NONE: - break; - case ADD: - // If no container and no magic add return - if (!opt && (sub_cfg->getContainerAction() != ADD)) { - break; - } - // Do nothing is the expression evaluates to empty. - value = isc::dhcp::evaluateString(*sub_cfg->getExpr(), - *query); - if (value.empty()) { - break; - } - // Check vendor id mismatch. - if (opt && vendor_id && !checkVendor(opt, vendor_id)) { - break; - } - // Don't add if sub-option is already there. - if (opt && opt->getOption(sub_code)) { - break; - } - // Set the value. - if (def) { - std::vector split_vec = - isc::util::str::tokens(value, ",", true); - sub = def->optionFactory(universe, sub_code, - split_vec); - } else { - buffer.assign(value.begin(), value.end()); - sub.reset(new isc::dhcp::Option(universe, sub_code, - buffer)); - } - // If the container does not exist add it. - if (!opt) { - if (!vendor_id) { - opt.reset(new isc::dhcp::Option(universe, - opt_code)); - } else { - opt.reset(new isc::dhcp::OptionVendor(universe, - vendor_id)); - } - response->addOption(opt); - if (vendor_id) { - logAction(ADD, opt_code, vendor_id); - } else { - logAction(ADD, opt_code, ""); - } - } - // Add the sub-option. - opt->addOption(sub); - logSubAction(ADD, sub_code, opt_code, value); - break; - case SUPERSEDE: - // If no container and no magic add return - if (!opt && (sub_cfg->getContainerAction() != ADD)) { - break; - } - // Do nothing is the expression evaluates to empty. - value = isc::dhcp::evaluateString(*sub_cfg->getExpr(), - *query); - if (value.empty()) { - break; - } - // Check vendor id mismatch. - if (opt && vendor_id && !checkVendor(opt, vendor_id)) { - break; - } - // Set the value. - if (def) { - std::vector split_vec = - isc::util::str::tokens(value, ",", true); - sub = def->optionFactory(universe, sub_code, - split_vec); - } else { - buffer.assign(value.begin(), value.end()); - sub.reset(new isc::dhcp::Option(universe, sub_code, - buffer)); - } - // Remove the sub-option if already there. - if (opt) { - while (opt->getOption(sub_code)) { - opt->delOption(sub_code); - } - } - // If the container does not exist add it. - if (!opt) { - if (!vendor_id) { - opt.reset(new isc::dhcp::Option(universe, - opt_code)); - } else { - opt.reset(new isc::dhcp::OptionVendor(universe, - vendor_id)); - } - response->addOption(opt); - if (vendor_id) { - logAction(ADD, opt_code, vendor_id); - } else { - logAction(ADD, opt_code, ""); - } - } - // Add the sub-option. - opt->addOption(sub); - logSubAction(SUPERSEDE, sub_code, opt_code, value); - break; - case REMOVE: - // Nothing to remove if container is not present. - if (!opt) { - break; - } - sub = opt->getOption(sub_code); - // Nothing to remove if sub-option is not present. - if (!sub) { - break; - } - // Do nothing is the expression evaluates to false. - if (!isc::dhcp::evaluateBool(*sub_cfg->getExpr(), *query)) { - break; - } - // Check vendor id mismatch. - if (opt && vendor_id && !checkVendor(opt, vendor_id)) { - break; - } - // Remove the sub-option. - while (opt->getOption(sub_code)) { - opt->delOption(sub_code); - } - logSubAction(REMOVE, sub_code, opt_code, ""); - // Remove the empty container when wanted. - if ((sub_cfg->getContainerAction() == REMOVE) && - opt->getOptions().empty()) { - response->delOption(opt_code); - logAction(REMOVE, opt_code, ""); - } - break; - } + opt_cfg->processSubOptions(universe, query, response); } } } + /// @brief Sub-option configuration. + /// + /// Per sub-option configuration. + class SubOptionConfig : public OptionConfig { + public: + /// @brief Constructor. + /// + /// @param code the sub-option code. + /// @param def the sub-option definition. + /// @param container pointer to the container option. + SubOptionConfig(uint16_t code, isc::dhcp::OptionDefinitionPtr def, + OptionConfigPtr container); + + /// @brief Destructor. + virtual ~SubOptionConfig(); + + /// @brief Set vendor id. + /// + /// @param vendor_id the vendor id. + void setVendorId(uint32_t vendor_id) { + vendor_id_ = vendor_id; + } + + /// @brief Return vendor id. + /// + /// @return vendor id. + uint32_t getVendorId() const { + return (vendor_id_); + } + + /// @brief Return container code. + /// + /// @return container code. + uint16_t getContainerCode() const { + return (container_->getCode()); + } + + /// @brief Return container definition. + /// + /// @return container definition. + isc::dhcp::OptionDefinitionPtr getContainerDef() const { + return (container_->getOptionDef()); + } + + /// @brief Return container client class. + /// + /// @return container client class name. + const isc::dhcp::ClientClass& getContainerClass() const { + return (container_->getClass()); + } + + /// @brief Set action on the container. + /// + /// @param action the action. + void setContainerAction(Action action) { + container_action_ = action; + } + + /// @brief Return action on the container. + /// + /// @return action on the container. + Action getContainerAction() const { + return (container_action_); + } + + private: + /// @brief Pointer to the container option configuration. + OptionConfigPtr container_; + + /// @brief Vendor id (0 when the container is not a vendor one). + uint32_t vendor_id_; + + /// @brief The action on the container. + Action container_action_; + }; /// @brief Log the client class for option. /// @@ -596,13 +455,6 @@ protected: return (option_config_map_); } - /// @brief Get a mutable reference to the sub-option config map of maps. - /// - /// @return The sub-option config map of maps. - SubOptionConfigMapMap& getMutableSubOptionConfigMap() { - return (sub_option_config_map_); - } - private: /// @brief Option parameters. static const data::SimpleKeywords OPTION_PARAMETERS; @@ -613,9 +465,6 @@ private: /// @brief The option config map (code and pointer to option config). OptionConfigMap option_config_map_; - /// @brief The sub-option config map of maps. - SubOptionConfigMapMap sub_option_config_map_; - /// @brief Parse an option config. /// /// @param option The element with option config. @@ -641,6 +490,240 @@ private: isc::dhcp::Option::Universe universe); }; +template +bool +FlexOptionImpl::OptionConfig::processSubOptions(isc::dhcp::Option::Universe universe, + PktType query, PktType response, + isc::dhcp::OptionPtr parent_opt) { + bool result = false; + for (auto sub_pair : getSubOptionConfigMap()) { + const SubOptionConfigPtr& sub_cfg = sub_pair.second; + uint16_t sub_code = sub_cfg->getCode(); + uint16_t opt_code = sub_cfg->getContainerCode(); + const isc::dhcp::ClientClass& opt_class = + sub_cfg->getContainerClass(); + if (!opt_class.empty()) { + if (!query->inClass(opt_class)) { + logClass(opt_class, opt_code); + continue; + } + } + const isc::dhcp::ClientClass& sub_class = + sub_cfg->getClass(); + if (!sub_class.empty()) { + if (!query->inClass(sub_class)) { + logSubClass(sub_class, sub_code, opt_code); + continue; + } + } + std::string value; + isc::dhcp::OptionBuffer buffer; + isc::dhcp::OptionPtr opt; + if (parent_opt) { + opt = parent_opt->getOption(opt_code); + } else { + opt = response->getOption(opt_code); + } + + isc::dhcp::OptionPtr sub; + isc::dhcp::OptionDefinitionPtr def = sub_cfg->getOptionDef(); + uint32_t vendor_id = sub_cfg->getVendorId(); + switch (sub_cfg->getAction()) { + case NONE: { + // If there is no parent option, but the top level requires creating + // one, this temporary option will be added at the end. + bool empty = false; + bool option_created = false; + if (!opt) { + if (!vendor_id) { + opt.reset(new isc::dhcp::Option(universe, + opt_code)); + } else { + opt.reset(new isc::dhcp::OptionVendor(universe, + vendor_id)); + } + option_created = true; + } else { + empty = opt->getOptions().empty(); + } + result = result || sub_cfg->processSubOptions(universe, query, response, opt); + if (result && option_created) { + if (parent_opt) { + parent_opt->addOption(opt); + } else { + response->addOption(opt); + } + if (vendor_id) { + logAction(ADD, opt_code, vendor_id); + } else { + logAction(ADD, opt_code, ""); + } + } + // If the option existed and was not empty but now it is, + // remove it from the parent. + if (!option_created && !empty && opt->getOptions().empty()) { + if (parent_opt) { + parent_opt->delOption(opt_code); + } else { + response->delOption(opt_code); + } + logAction(REMOVE, opt_code, ""); + } + } + break; + case ADD: + // If no container and no magic add return + if (!opt && (sub_cfg->getContainerAction() != ADD)) { + break; + } + // Do nothing is the expression evaluates to empty. + value = isc::dhcp::evaluateString(*sub_cfg->getExpr(), + *query); + if (value.empty()) { + break; + } + // Check vendor id mismatch. + if (opt && vendor_id && !checkVendor(opt, vendor_id)) { + break; + } + // Don't add if sub-option is already there. + if (opt && opt->getOption(sub_code)) { + break; + } + // Set the value. + if (def) { + std::vector split_vec = + isc::util::str::tokens(value, ",", true); + sub = def->optionFactory(universe, sub_code, + split_vec); + } else { + buffer.assign(value.begin(), value.end()); + sub.reset(new isc::dhcp::Option(universe, sub_code, + buffer)); + } + // If the container does not exist add it. + if (!opt) { + if (!vendor_id) { + opt.reset(new isc::dhcp::Option(universe, + opt_code)); + } else { + opt.reset(new isc::dhcp::OptionVendor(universe, + vendor_id)); + } + if (parent_opt) { + parent_opt->addOption(opt); + } else { + response->addOption(opt); + } + if (vendor_id) { + logAction(ADD, opt_code, vendor_id); + } else { + logAction(ADD, opt_code, ""); + } + } + + // Add the sub-option. + opt->addOption(sub); + logSubAction(ADD, sub_code, opt_code, value); + result = true; + break; + case SUPERSEDE: + // If no container and no magic add return + if (!opt && (sub_cfg->getContainerAction() != ADD)) { + break; + } + // Do nothing is the expression evaluates to empty. + value = isc::dhcp::evaluateString(*sub_cfg->getExpr(), + *query); + if (value.empty()) { + break; + } + // Check vendor id mismatch. + if (opt && vendor_id && !checkVendor(opt, vendor_id)) { + break; + } + // Set the value. + if (def) { + std::vector split_vec = + isc::util::str::tokens(value, ",", true); + sub = def->optionFactory(universe, sub_code, + split_vec); + } else { + buffer.assign(value.begin(), value.end()); + sub.reset(new isc::dhcp::Option(universe, sub_code, + buffer)); + } + // Remove the sub-option if already there. + if (opt) { + while (opt->getOption(sub_code)) { + opt->delOption(sub_code); + } + } + // If the container does not exist add it. + if (!opt) { + if (!vendor_id) { + opt.reset(new isc::dhcp::Option(universe, + opt_code)); + } else { + opt.reset(new isc::dhcp::OptionVendor(universe, + vendor_id)); + } + if (parent_opt) { + parent_opt->addOption(opt); + } else { + response->addOption(opt); + } + if (vendor_id) { + logAction(ADD, opt_code, vendor_id); + } else { + logAction(ADD, opt_code, ""); + } + } + + // Add the sub-option. + opt->addOption(sub); + logSubAction(SUPERSEDE, sub_code, opt_code, value); + result = true; + break; + case REMOVE: + // Nothing to remove if container is not present. + if (!opt) { + break; + } + sub = opt->getOption(sub_code); + // Nothing to remove if sub-option is not present. + if (!sub) { + break; + } + // Do nothing is the expression evaluates to false. + if (!isc::dhcp::evaluateBool(*sub_cfg->getExpr(), *query)) { + break; + } + // Check vendor id mismatch. + if (opt && vendor_id && !checkVendor(opt, vendor_id)) { + break; + } + // Remove the sub-option. + while (opt->getOption(sub_code)) { + opt->delOption(sub_code); + } + logSubAction(REMOVE, sub_code, opt_code, ""); + // Remove the empty container when wanted. + if ((sub_cfg->getContainerAction() == REMOVE) && + opt->getOptions().empty()) { + if (parent_opt) { + parent_opt->delOption(opt_code); + } else { + response->delOption(opt_code); + } + logAction(REMOVE, opt_code, ""); + } + break; + } + } + return (result); +} + /// @brief The type of shared pointers to Flex Option implementations. typedef boost::shared_ptr FlexOptionImplPtr; diff --git a/src/hooks/dhcp/flex_option/tests/sub_option_unittests.cc b/src/hooks/dhcp/flex_option/tests/sub_option_unittests.cc index f4772b0a967b018ac3805342e3710a2f557ec186..11c4ab498bc8ebdb5f291c9702bf1e5399b8e5fb 100644 --- a/src/hooks/dhcp/flex_option/tests/sub_option_unittests.cc +++ b/src/hooks/dhcp/flex_option/tests/sub_option_unittests.cc @@ -66,7 +66,6 @@ TEST_F(FlexSubOptionTest, configEmpty) { EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); EXPECT_TRUE(impl_->getOptionConfigMap().empty()); - EXPECT_TRUE(impl_->getSubOptionConfigMap().empty()); } // Verify that a sub-option configuration must exist. @@ -378,9 +377,9 @@ TEST_F(FlexSubOptionTest, subOptionConfigUnknownCodeNoCSVFormat) { EXPECT_NO_THROW(impl_->testConfigure(options)); EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); - auto map = impl_->getSubOptionConfigMap(); + auto map = impl_->getMutableOptionConfigMap(); EXPECT_EQ(1, map.count(109)); - auto smap = map[109]; + auto smap = map[109].front()->getSubOptionConfigMap(); EXPECT_EQ(1, smap.count(222)); } @@ -406,9 +405,9 @@ TEST_F(FlexSubOptionTest, subOptionConfigUnknownCodeDisableCSVFormat) { EXPECT_NO_THROW(impl_->testConfigure(options)); EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); - auto map = impl_->getSubOptionConfigMap(); + auto map = impl_->getMutableOptionConfigMap(); EXPECT_EQ(1, map.count(109)); - auto smap = map[109]; + auto smap = map[109].front()->getSubOptionConfigMap(); EXPECT_EQ(1, smap.count(222)); } @@ -462,9 +461,9 @@ TEST_F(FlexSubOptionTest, subOptionConfigDefinedName) { EXPECT_NO_THROW(impl_->testConfigure(options)); EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); - auto map = impl_->getSubOptionConfigMap(); + auto map = impl_->getMutableOptionConfigMap(); EXPECT_EQ(1, map.count(109)); - auto smap = map[109]; + auto smap = map[109].front()->getSubOptionConfigMap(); EXPECT_EQ(1, smap.count(222)); } @@ -495,9 +494,9 @@ TEST_F(FlexSubOptionTest, subOptionConfigLastResortName) { EXPECT_NO_THROW(impl_->testConfigure(options)); EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); - auto map = impl_->getSubOptionConfigMap(); + auto map = impl_->getMutableOptionConfigMap(); EXPECT_EQ(1, map.count(DHO_VENDOR_ENCAPSULATED_OPTIONS)); - auto smap = map[DHO_VENDOR_ENCAPSULATED_OPTIONS]; + auto smap = map[DHO_VENDOR_ENCAPSULATED_OPTIONS].front()->getSubOptionConfigMap(); EXPECT_EQ(1, smap.count(222)); } @@ -534,9 +533,9 @@ TEST_F(FlexSubOptionTest, subOptionConfigLastResortCode) { EXPECT_NO_THROW(impl_->testConfigure(options)); EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); - auto map = impl_->getSubOptionConfigMap(); + auto map = impl_->getMutableOptionConfigMap(); EXPECT_EQ(1, map.count(DHO_VENDOR_ENCAPSULATED_OPTIONS)); - auto smap = map[DHO_VENDOR_ENCAPSULATED_OPTIONS]; + auto smap = map[DHO_VENDOR_ENCAPSULATED_OPTIONS].front()->getSubOptionConfigMap(); EXPECT_EQ(1, smap.count(222)); } @@ -568,9 +567,9 @@ TEST_F(FlexSubOptionTest, subOptionConfigVendorName) { EXPECT_NO_THROW(impl_->testConfigure(options)); EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); - auto map = impl_->getSubOptionConfigMap(); + auto map = impl_->getMutableOptionConfigMap(); EXPECT_EQ(1, map.count(D6O_VENDOR_OPTS)); - auto smap = map[D6O_VENDOR_OPTS]; + auto smap = map[D6O_VENDOR_OPTS].front()->getSubOptionConfigMap(); EXPECT_EQ(1, smap.count(222)); } @@ -609,9 +608,9 @@ TEST_F(FlexSubOptionTest, subOptionConfigVendorCode) { EXPECT_NO_THROW(impl_->testConfigure(options)); EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); - auto map = impl_->getSubOptionConfigMap(); + auto map = impl_->getMutableOptionConfigMap(); EXPECT_EQ(1, map.count(D6O_VENDOR_OPTS)); - auto smap = map[D6O_VENDOR_OPTS]; + auto smap = map[D6O_VENDOR_OPTS].front()->getSubOptionConfigMap(); EXPECT_EQ(1, smap.count(222)); } @@ -636,9 +635,9 @@ TEST_F(FlexSubOptionTest, subOptionConfigDosSISName) { EXPECT_NO_THROW(impl_->testConfigure(options)); EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); - auto map = impl_->getSubOptionConfigMap(); + auto map = impl_->getMutableOptionConfigMap(); EXPECT_EQ(1, map.count(DHO_VIVSO_SUBOPTIONS)); - auto smap = map[DHO_VIVSO_SUBOPTIONS]; + auto smap = map[DHO_VIVSO_SUBOPTIONS].front()->getSubOptionConfigMap(); // DOCSIS3_V4_TFTP_SERVERS is 2 EXPECT_EQ(1, smap.count(2)); } @@ -667,9 +666,9 @@ TEST_F(FlexSubOptionTest, subOptionConfigDosSISCode) { EXPECT_NO_THROW(impl_->testConfigure(options)); EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); - auto map = impl_->getSubOptionConfigMap(); + auto map = impl_->getMutableOptionConfigMap(); EXPECT_EQ(1, map.count(DHO_VIVSO_SUBOPTIONS)); - auto smap = map[DHO_VIVSO_SUBOPTIONS]; + auto smap = map[DHO_VIVSO_SUBOPTIONS].front()->getSubOptionConfigMap(); // DOCSIS3_V4_TFTP_SERVERS is 2 EXPECT_EQ(1, smap.count(2)); } @@ -1041,9 +1040,9 @@ TEST_F(FlexSubOptionTest, subOptionConfigComplex) { EXPECT_NO_THROW(impl_->testConfigure(options)); EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); - auto map = impl_->getSubOptionConfigMap(); + auto map = impl_->getMutableOptionConfigMap(); EXPECT_EQ(1, map.count(109)); - auto smap = map[109]; + auto smap = map[109].front()->getSubOptionConfigMap(); FlexOptionImpl::SubOptionConfigPtr sub_cfg; ASSERT_NO_THROW(sub_cfg = smap.at(1)); ASSERT_TRUE(sub_cfg); @@ -1091,8 +1090,9 @@ TEST_F(FlexSubOptionTest, subProcessNone) { OptionDefinitionPtr def; FlexOptionImpl::SubOptionConfigPtr sub_cfg(new FlexOptionImpl::SubOptionConfig(1, def, opt_cfg)); - auto& map = impl_->getMutableSubOptionConfigMap(); - auto& smap = map[DHO_DHCP_AGENT_OPTIONS]; + auto& map = impl_->getMutableOptionConfigMap(); + map[DHO_DHCP_AGENT_OPTIONS].push_back(opt_cfg); + auto& smap = map[DHO_DHCP_AGENT_OPTIONS].front()->getMutableSubOptionConfigMap(); smap[1] = sub_cfg; Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); @@ -1158,6 +1158,76 @@ TEST_F(FlexSubOptionTest, subProcessAddEnableCSVFormat) { EXPECT_EQ(0, buffer[12]); } +// Verify that ADD action adds the specified sub-option in csv format in +// deep encapsulated space. +TEST_F(FlexSubOptionTest, subProcessAddComplexEnableCSVFormat) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-container", 4, + "my-space", "empty", + "my-other-space")); + defs.addItem(sdef); + OptionDefinitionPtr ssdef(new OptionDefinition("my-option", 1, "my-other-space", + "fqdn", true)); + defs.addItem(ssdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(222); + option->set("code", code); + ElementPtr sub_options = Element::createList(); + option->set("sub-options", sub_options); + ElementPtr sub_option = Element::createMap(); + sub_options->add(sub_option); + ElementPtr space = Element::create(string("my-space")); + sub_option->set("space", space); + ElementPtr other_code = Element::create(4); + sub_option->set("code", other_code); + ElementPtr sub_sub_options = Element::createList(); + sub_option->set("sub-options", sub_sub_options); + ElementPtr sub_sub_option = Element::createMap(); + sub_sub_options->add(sub_sub_option); + ElementPtr other_space = Element::create(string("my-other-space")); + sub_sub_option->set("space", other_space); + ElementPtr add = Element::create(string("'example.com'")); + sub_sub_option->set("add", add); + ElementPtr name = Element::create(string("my-option")); + sub_sub_option->set("name", name); + sub_sub_option->set("csv-format", Element::create(true)); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + EXPECT_FALSE(response->getOption(222)); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + OptionPtr opt = response->getOption(222); + ASSERT_TRUE(opt); + EXPECT_EQ(222, opt->getType()); + OptionPtr sub = opt->getOption(4); + ASSERT_TRUE(sub); + EXPECT_EQ(4, sub->getType()); + OptionPtr sub_sub = sub->getOption(1); + ASSERT_TRUE(sub_sub); + EXPECT_EQ(1, sub_sub->getType()); + // The fqdn array is the most complex encoding of one element... + const OptionBuffer& buffer = sub_sub->getData(); + ASSERT_EQ(13, buffer.size()); + EXPECT_EQ(7, buffer[0]); + EXPECT_EQ(0, memcmp(&buffer[1], "example", 7)); + EXPECT_EQ(3, buffer[8]); + EXPECT_EQ(0, memcmp(&buffer[9], "com", 3)); + EXPECT_EQ(0, buffer[12]); +} + // Verify that ADD action does nothing when the container does not exist and // container-add is false. TEST_F(FlexSubOptionTest, subProcessAddNoContainer) { @@ -1202,6 +1272,62 @@ TEST_F(FlexSubOptionTest, subProcessAddNoContainer) { EXPECT_FALSE(response->getOption(222)); } +// Verify that ADD action does nothing when the container does not exist +// and container-add is false in deep encapsulated space. +TEST_F(FlexSubOptionTest, subProcessAddComplexNoContainer) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-container", 4, + "my-space", "empty", + "my-other-space")); + defs.addItem(sdef); + OptionDefinitionPtr ssdef(new OptionDefinition("my-option", 1, "my-other-space", + "fqdn", true)); + defs.addItem(ssdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(222); + option->set("code", code); + ElementPtr sub_options = Element::createList(); + option->set("sub-options", sub_options); + ElementPtr sub_option = Element::createMap(); + sub_options->add(sub_option); + ElementPtr space = Element::create(string("my-space")); + sub_option->set("space", space); + ElementPtr other_code = Element::create(4); + sub_option->set("code", other_code); + ElementPtr sub_sub_options = Element::createList(); + sub_option->set("sub-options", sub_sub_options); + ElementPtr sub_sub_option = Element::createMap(); + sub_sub_options->add(sub_sub_option); + ElementPtr other_space = Element::create(string("my-other-space")); + sub_sub_option->set("space", other_space); + ElementPtr add = Element::create(string("'example.com'")); + sub_sub_option->set("add", add); + ElementPtr name = Element::create(string("my-option")); + sub_sub_option->set("name", name); + sub_sub_option->set("container-add", Element::create(false)); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + string response_txt = response->toText(); + EXPECT_FALSE(response->getOption(222)); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + EXPECT_FALSE(response->getOption(222)); +} + // Verify that ADD action adds the specified sub-option in raw format. TEST_F(FlexSubOptionTest, subProcessAddDisableCSVFormat) { OptionDefSpaceContainer defs; @@ -1787,6 +1913,76 @@ TEST_F(FlexSubOptionTest, subProcessSupersedeEnableCSVFormat) { EXPECT_EQ(0, buffer[12]); } +// Verify that SUPERSEDE action adds the specified sub-option in csv format in +// deep encapsulated space. +TEST_F(FlexSubOptionTest, subProcessSupersedeComplexEnableCSVFormat) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-container", 4, + "my-space", "empty", + "my-other-space")); + defs.addItem(sdef); + OptionDefinitionPtr ssdef(new OptionDefinition("my-option", 1, "my-other-space", + "fqdn", true)); + defs.addItem(ssdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(222); + option->set("code", code); + ElementPtr sub_options = Element::createList(); + option->set("sub-options", sub_options); + ElementPtr sub_option = Element::createMap(); + sub_options->add(sub_option); + ElementPtr space = Element::create(string("my-space")); + sub_option->set("space", space); + ElementPtr other_code = Element::create(4); + sub_option->set("code", other_code); + ElementPtr sub_sub_options = Element::createList(); + sub_option->set("sub-options", sub_sub_options); + ElementPtr sub_sub_option = Element::createMap(); + sub_sub_options->add(sub_sub_option); + ElementPtr other_space = Element::create(string("my-other-space")); + sub_sub_option->set("space", other_space); + ElementPtr supersede = Element::create(string("'example.com'")); + sub_sub_option->set("supersede", supersede); + ElementPtr name = Element::create(string("my-option")); + sub_sub_option->set("name", name); + sub_sub_option->set("csv-format", Element::create(true)); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + EXPECT_FALSE(response->getOption(222)); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + OptionPtr opt = response->getOption(222); + ASSERT_TRUE(opt); + EXPECT_EQ(222, opt->getType()); + OptionPtr sub = opt->getOption(4); + ASSERT_TRUE(sub); + EXPECT_EQ(4, sub->getType()); + OptionPtr sub_sub = sub->getOption(1); + ASSERT_TRUE(sub_sub); + EXPECT_EQ(1, sub_sub->getType()); + // The fqdn array is the most complex encoding of one element... + const OptionBuffer& buffer = sub_sub->getData(); + ASSERT_EQ(13, buffer.size()); + EXPECT_EQ(7, buffer[0]); + EXPECT_EQ(0, memcmp(&buffer[1], "example", 7)); + EXPECT_EQ(3, buffer[8]); + EXPECT_EQ(0, memcmp(&buffer[9], "com", 3)); + EXPECT_EQ(0, buffer[12]); +} + // Verify that SUPERSEDE action does nothing when the container does not exist // and container-add is false. TEST_F(FlexSubOptionTest, subProcessSupersedeNoContainer) { @@ -1831,6 +2027,62 @@ TEST_F(FlexSubOptionTest, subProcessSupersedeNoContainer) { EXPECT_FALSE(response->getOption(222)); } +// Verify that SUPERSEDE action does nothing when the container does not exist +// and container-add is false in deep encapsulated space. +TEST_F(FlexSubOptionTest, subProcessSupersedeComplexNoContainer) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-container", 4, + "my-space", "empty", + "my-other-space")); + defs.addItem(sdef); + OptionDefinitionPtr ssdef(new OptionDefinition("my-option", 1, "my-other-space", + "fqdn", true)); + defs.addItem(ssdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(222); + option->set("code", code); + ElementPtr sub_options = Element::createList(); + option->set("sub-options", sub_options); + ElementPtr sub_option = Element::createMap(); + sub_options->add(sub_option); + ElementPtr space = Element::create(string("my-space")); + sub_option->set("space", space); + ElementPtr other_code = Element::create(4); + sub_option->set("code", other_code); + ElementPtr sub_sub_options = Element::createList(); + sub_option->set("sub-options", sub_sub_options); + ElementPtr sub_sub_option = Element::createMap(); + sub_sub_options->add(sub_sub_option); + ElementPtr other_space = Element::create(string("my-other-space")); + sub_sub_option->set("space", other_space); + ElementPtr supersede = Element::create(string("'example.com'")); + sub_sub_option->set("supersede", supersede); + ElementPtr name = Element::create(string("my-option")); + sub_sub_option->set("name", name); + sub_sub_option->set("container-add", Element::create(false)); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + string response_txt = response->toText(); + EXPECT_FALSE(response->getOption(222)); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + EXPECT_FALSE(response->getOption(222)); +} + // Verify that SUPERSEDE action adds the specified sub-option in raw format. TEST_F(FlexSubOptionTest, subProcessSupersedeDisableCSVFormat) { OptionDefSpaceContainer defs; @@ -2407,6 +2659,68 @@ TEST_F(FlexSubOptionTest, subProcessRemove) { EXPECT_FALSE(response->getOption(222)); } +// Verify that REMOVE action removes an already existing sub-option in deep +// encapsulated space. +TEST_F(FlexSubOptionTest, subProcessRemoveComplex) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-container", 4, + "my-space", "empty", + "my-other-space")); + defs.addItem(sdef); + OptionDefinitionPtr ssdef(new OptionDefinition("my-option", 1, "my-other-space", + "string")); + defs.addItem(ssdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(222); + option->set("code", code); + ElementPtr sub_options = Element::createList(); + option->set("sub-options", sub_options); + ElementPtr sub_option = Element::createMap(); + sub_options->add(sub_option); + ElementPtr space = Element::create(string("my-space")); + sub_option->set("space", space); + ElementPtr other_code = Element::create(4); + sub_option->set("code", other_code); + ElementPtr sub_sub_options = Element::createList(); + sub_option->set("sub-options", sub_sub_options); + ElementPtr sub_sub_option = Element::createMap(); + sub_sub_options->add(sub_sub_option); + ElementPtr other_space = Element::create(string("my-other-space")); + sub_sub_option->set("space", other_space); + ElementPtr remove = Element::create(string("'abc' == 'abc'")); + sub_sub_option->set("remove", remove); + ElementPtr name = Element::create(string("my-option")); + sub_sub_option->set("name", name); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + string response_txt = response->toText(); + OptionPtr container(new Option(Option::V4, 222)); + response->addOption(container); + OptionPtr other_container(new Option(Option::V4, 4)); + container->addOption(other_container); + EXPECT_TRUE(response->getOption(222)); + EXPECT_TRUE(response->getOption(222)->getOption(4)); + OptionStringPtr str(new OptionString(Option::V4, 1, "xyzt")); + other_container->addOption(str); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + EXPECT_FALSE(response->getOption(222)); +} + // Verify that REMOVE action removes an already existing sub-option but // leaves the container option when container-remove is false. TEST_F(FlexSubOptionTest, subProcessRemoveLeaveContainer) { @@ -2458,6 +2772,75 @@ TEST_F(FlexSubOptionTest, subProcessRemoveLeaveContainer) { EXPECT_TRUE(opt->getOptions().empty()); } +// Verify that REMOVE action removes an already existing sub-option in deep +// encapsulated space but leaves the container option when container-remove is +// false. +TEST_F(FlexSubOptionTest, subProcessRemoveComplexLeaveContainer) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-container", 4, + "my-space", "empty", + "my-other-space")); + defs.addItem(sdef); + OptionDefinitionPtr ssdef(new OptionDefinition("my-option", 1, "my-other-space", + "string")); + defs.addItem(ssdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(222); + option->set("code", code); + ElementPtr sub_options = Element::createList(); + option->set("sub-options", sub_options); + ElementPtr sub_option = Element::createMap(); + sub_options->add(sub_option); + ElementPtr space = Element::create(string("my-space")); + sub_option->set("space", space); + ElementPtr other_code = Element::create(4); + sub_option->set("code", other_code); + ElementPtr sub_sub_options = Element::createList(); + sub_option->set("sub-options", sub_sub_options); + ElementPtr sub_sub_option = Element::createMap(); + sub_sub_options->add(sub_sub_option); + ElementPtr other_space = Element::create(string("my-other-space")); + sub_sub_option->set("space", other_space); + ElementPtr remove = Element::create(string("'abc' == 'abc'")); + sub_sub_option->set("remove", remove); + ElementPtr name = Element::create(string("my-option")); + sub_sub_option->set("name", name); + sub_sub_option->set("container-remove", Element::create(false)); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + OptionPtr container(new Option(Option::V4, 222)); + response->addOption(container); + OptionPtr other_container(new Option(Option::V4, 4)); + container->addOption(other_container); + EXPECT_TRUE(response->getOption(222)); + EXPECT_TRUE(response->getOption(222)->getOption(4)); + string response_txt = response->toText(); + OptionStringPtr str(new OptionString(Option::V4, 1, "xyzt")); + other_container->addOption(str); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + OptionPtr opt = response->getOption(222); + ASSERT_TRUE(opt); + opt = opt->getOption(4); + ASSERT_TRUE(opt); + EXPECT_FALSE(opt->getOption(1)); + EXPECT_TRUE(opt->getOptions().empty()); +} + // Verify that REMOVE action removes an already existing sub-option but // leaves the container option when it is not empty. TEST_F(FlexSubOptionTest, subProcessRemoveContainerNotEmpty) { @@ -2561,6 +2944,66 @@ TEST_F(FlexSubOptionTest, subProcessRemoveContainerNoSubOption) { EXPECT_TRUE(response->getOption(222)); } +// Verify that REMOVE action does not removes the container option when the +// sub-option does not exist in deep encapsulated space. +TEST_F(FlexSubOptionTest, subProcessRemoveComplexContainerNoSubOption) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-container", 4, + "my-space", "empty", + "my-other-space")); + defs.addItem(sdef); + OptionDefinitionPtr ssdef(new OptionDefinition("my-option", 1, "my-other-space", + "string")); + defs.addItem(ssdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(222); + option->set("code", code); + ElementPtr sub_options = Element::createList(); + option->set("sub-options", sub_options); + ElementPtr sub_option = Element::createMap(); + sub_options->add(sub_option); + ElementPtr space = Element::create(string("my-space")); + sub_option->set("space", space); + ElementPtr other_code = Element::create(4); + sub_option->set("code", other_code); + ElementPtr sub_sub_options = Element::createList(); + sub_option->set("sub-options", sub_sub_options); + ElementPtr sub_sub_option = Element::createMap(); + sub_sub_options->add(sub_sub_option); + ElementPtr other_space = Element::create(string("my-other-space")); + sub_sub_option->set("space", other_space); + ElementPtr remove = Element::create(string("'abc' == 'abc'")); + sub_sub_option->set("remove", remove); + ElementPtr name = Element::create(string("my-option")); + sub_sub_option->set("name", name); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + OptionPtr container(new Option(Option::V4, 222)); + response->addOption(container); + OptionPtr other_container(new Option(Option::V4, 4)); + container->addOption(other_container); + EXPECT_TRUE(response->getOption(222)); + EXPECT_TRUE(response->getOption(222)->getOption(4)); + string response_txt = response->toText(); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + EXPECT_TRUE(response->getOption(222)); +} + // Verify that REMOVE action does nothing when the expression evaluates to false. TEST_F(FlexSubOptionTest, subProcessRemoveFalse) { OptionDefSpaceContainer defs; @@ -2969,9 +3412,9 @@ TEST_F(FlexSubOptionTest, subOptionConfigGuardValid) { EXPECT_NO_THROW(impl_->testConfigure(options)); EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); - auto map = impl_->getSubOptionConfigMap(); + auto map = impl_->getMutableOptionConfigMap(); EXPECT_EQ(1, map.count(109)); - auto smap = map[109]; + auto smap = map[109].front()->getSubOptionConfigMap(); FlexOptionImpl::SubOptionConfigPtr sub_cfg; ASSERT_NO_THROW(sub_cfg = smap.at(222)); ASSERT_TRUE(sub_cfg); diff --git a/src/hooks/dhcp/flex_option/tests/test_flex_option.h b/src/hooks/dhcp/flex_option/tests/test_flex_option.h index df0a9bf40cfce94bb113a1b9a144a7897eb277eb..969d3e6612087cc2f121c28a305c350a97b6a9a8 100644 --- a/src/hooks/dhcp/flex_option/tests/test_flex_option.h +++ b/src/hooks/dhcp/flex_option/tests/test_flex_option.h @@ -24,9 +24,6 @@ public: /// Export getMutableOptionConfigMap. using FlexOptionImpl::getMutableOptionConfigMap; - /// Export getMutableSubOptionConfigMap. - using FlexOptionImpl::getMutableSubOptionConfigMap; - /// @Brief Configure clone which records the error. /// /// @param options The element with option config list.