From: Francis Dupont Date: Mon, 21 Mar 2022 22:36:10 +0000 (+0100) Subject: [#2314] Checkpoint: finished UTs, doing doc X-Git-Tag: Kea-2.1.4~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dbab9833609be6fe46f3ca86628f3ee1ee86ec39;p=thirdparty%2Fkea.git [#2314] Checkpoint: finished UTs, doing doc --- diff --git a/src/hooks/dhcp/flex_option/flex_option_messages.cc b/src/hooks/dhcp/flex_option/flex_option_messages.cc index 864e612f36..340bd7f6a4 100644 --- a/src/hooks/dhcp/flex_option/flex_option_messages.cc +++ b/src/hooks/dhcp/flex_option/flex_option_messages.cc @@ -26,7 +26,7 @@ const char* values[] = { "FLEX_OPTION_PROCESS_ERROR", "An error occurred processing query %1: %2", "FLEX_OPTION_PROCESS_REMOVE", "Removed option code %1", "FLEX_OPTION_PROCESS_SUB_ADD", "Added the sub-option code %1 in option code %2 value by %3", - "FLEX_OPTION_PROCESS_SUB_CLIENT_CLASS", "Skip processing of the sub-option code in option code %2 for class '%3'", + "FLEX_OPTION_PROCESS_SUB_CLIENT_CLASS", "Skip processing of the sub-option code %1 in option code %2 for class '%3'", "FLEX_OPTION_PROCESS_SUB_REMOVE", "Removed sub-option code %1 in option code %2", "FLEX_OPTION_PROCESS_SUB_SUPERSEDE", "Supersedes the value of sub-option code %1 in option code %2 by %3", "FLEX_OPTION_PROCESS_SUPERSEDE", "Supersedes the value of option code %1 by %2", diff --git a/src/hooks/dhcp/flex_option/flex_option_messages.mes b/src/hooks/dhcp/flex_option/flex_option_messages.mes index 9c125835f2..dd4acb9ac6 100644 --- a/src/hooks/dhcp/flex_option/flex_option_messages.mes +++ b/src/hooks/dhcp/flex_option/flex_option_messages.mes @@ -30,7 +30,7 @@ This debug message is printed when an sub-option was added into the response packet. The sub-option and container option codes, and the value (between quotes if printable, in hexadecimal is not) are provided. -% FLEX_OPTION_PROCESS_SUB_CLIENT_CLASS Skip processing of the sub-option code in option code %2 for class '%3' +% FLEX_OPTION_PROCESS_SUB_CLIENT_CLASS Skip processing of the sub-option code %1 in option code %2 for class '%3' This debug message is printed when the processing for a sub-option is skipped because the query does not belongs to the client class. The sub-option and container option codes, and the client class name are provided. 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 79deccb556..621b296d9a 100644 --- a/src/hooks/dhcp/flex_option/tests/sub_option_unittests.cc +++ b/src/hooks/dhcp/flex_option/tests/sub_option_unittests.cc @@ -26,6 +26,7 @@ using namespace std; using namespace isc; +using namespace isc::asiolink; using namespace isc::data; using namespace isc::dhcp; using namespace isc::eval; @@ -906,6 +907,7 @@ TEST_F(FlexSubOptionTest, subOptionConfigComplex) { sub_option2->set("code", sub_code); ElementPtr supersede = Element::create(string("'def'")); sub_option2->set("supersede", supersede); + sub_option2->set("container-add", Element::create(false)); ElementPtr sub_option3 = Element::createMap(); sub_options->add(sub_option3); @@ -915,6 +917,15 @@ TEST_F(FlexSubOptionTest, subOptionConfigComplex) { ElementPtr remove = Element::create(string("'a' == 'b'")); sub_option3->set("remove", remove); + ElementPtr sub_option4 = Element::createMap(); + sub_options->add(sub_option4); + sub_option4->set("space", space); + sub_code = Element::create(4); + sub_option4->set("code", sub_code); + remove = Element::create(string("'b' == 'a'")); + sub_option4->set("remove", remove); + sub_option4->set("container-remove", Element::create(false)); + EXPECT_NO_THROW(impl_->testConfigure(options)); EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); @@ -926,6 +937,7 @@ TEST_F(FlexSubOptionTest, subOptionConfigComplex) { ASSERT_TRUE(sub_cfg); EXPECT_EQ(1, sub_cfg->getCode()); EXPECT_EQ(FlexOptionImpl::ADD, sub_cfg->getAction()); + EXPECT_EQ(FlexOptionImpl::ADD, sub_cfg->getContainerAction()); EXPECT_EQ("'abc'", sub_cfg->getText()); EXPECT_EQ(109, sub_cfg->getContainerCode()); @@ -933,6 +945,7 @@ TEST_F(FlexSubOptionTest, subOptionConfigComplex) { ASSERT_TRUE(sub_cfg); EXPECT_EQ(2, sub_cfg->getCode()); EXPECT_EQ(FlexOptionImpl::SUPERSEDE, sub_cfg->getAction()); + EXPECT_EQ(FlexOptionImpl::NONE, sub_cfg->getContainerAction()); EXPECT_EQ("'def'", sub_cfg->getText()); EXPECT_EQ(109, sub_cfg->getContainerCode()); @@ -940,8 +953,17 @@ TEST_F(FlexSubOptionTest, subOptionConfigComplex) { ASSERT_TRUE(sub_cfg); EXPECT_EQ(3, sub_cfg->getCode()); EXPECT_EQ(FlexOptionImpl::REMOVE, sub_cfg->getAction()); + EXPECT_EQ(FlexOptionImpl::REMOVE, sub_cfg->getContainerAction()); EXPECT_EQ("'a' == 'b'", sub_cfg->getText()); EXPECT_EQ(109, sub_cfg->getContainerCode()); + + ASSERT_NO_THROW(sub_cfg = smap.at(4)); + ASSERT_TRUE(sub_cfg); + EXPECT_EQ(4, sub_cfg->getCode()); + EXPECT_EQ(FlexOptionImpl::REMOVE, sub_cfg->getAction()); + EXPECT_EQ(FlexOptionImpl::NONE, sub_cfg->getContainerAction()); + EXPECT_EQ("'b' == 'a'", sub_cfg->getText()); + EXPECT_EQ(109, sub_cfg->getContainerCode()); } // Empty sub-option config list doing nothing is the same as empty option list. @@ -1024,7 +1046,7 @@ TEST_F(FlexSubOptionTest, subProcessAddEnableCSVFormat) { EXPECT_EQ(0, buffer[12]); } -// Verify that ADD action does not when the container does not exist and +// Verify that ADD action does nothing when the container does not exist and // container-add is false. TEST_F(FlexSubOptionTest, subProcessAddNoContainer) { OptionDefSpaceContainer defs; @@ -1486,7 +1508,7 @@ TEST_F(FlexSubOptionTest, subProcessAddVivsoMismatch) { } // Verify that ADD action can handle the vendor-opts option. -TEST_F(FlexSubOptionTest, subProcessAddvendorOpts) { +TEST_F(FlexSubOptionTest, subProcessAddVendorOpts) { CfgMgr::instance().setFamily(AF_INET6); OptionDefSpaceContainer defs; @@ -1532,7 +1554,7 @@ TEST_F(FlexSubOptionTest, subProcessAddvendorOpts) { } // Verify that ADD action can handle the vendor-opts option with vendor mismatch. -TEST_F(FlexSubOptionTest, subProcessAddvendorOptsMismatch) { +TEST_F(FlexSubOptionTest, subProcessAddVendorOptsMismatch) { CfgMgr::instance().setFamily(AF_INET6); OptionDefSpaceContainer defs; @@ -1585,5 +1607,1422 @@ TEST_F(FlexSubOptionTest, subProcessAddvendorOptsMismatch) { EXPECT_EQ(0, memcmp(&buffer[0], "xyzt", 4)); } +// Verify that SUPERSEDE action adds the specified sub-option in csv format. +TEST_F(FlexSubOptionTest, subProcessSupersedeEnableCSVFormat) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, "my-space", + "fqdn", true)); + defs.addItem(sdef); + 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 supersede = Element::create(string("'example.com'")); + sub_option->set("supersede", supersede); + ElementPtr name = Element::create(string("my-option")); + sub_option->set("name", name); + 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(1); + ASSERT_TRUE(sub); + EXPECT_EQ(1, sub->getType()); + // The fqdn array is the most complex encoding of one element... + const OptionBuffer& buffer = 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) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, "my-space", + "string")); + defs.addItem(sdef); + 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 supersede = Element::create(string("'abc'")); + sub_option->set("supersede", supersede); + ElementPtr name = Element::create(string("my-option")); + sub_option->set("name", name); + 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; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, "my-space", + "fqdn", true)); + defs.addItem(sdef); + 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 supersede = Element::create(string("0x076578616d706c6503636f6d00")); + sub_option->set("supersede", supersede); + ElementPtr name = Element::create(string("my-option")); + sub_option->set("name", name); + // csv-format is disabled by default. + + 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(1); + ASSERT_TRUE(sub); + EXPECT_EQ(1, sub->getType()); + const OptionBuffer& buffer = 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 adds the specified sub-option in an already +// existing container option. +TEST_F(FlexSubOptionTest, subProcessSupersede) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, "my-space", + "string")); + defs.addItem(sdef); + 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 supersede = Element::create(string("'abc'")); + sub_option->set("supersede", supersede); + ElementPtr name = Element::create(string("my-option")); + 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); + EXPECT_TRUE(response->getOption(222)); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + // Only one option with code 222. + EXPECT_EQ(1, response->options_.count(222)); +} + +// Verify that SUPERSEDE action replaces an already existing sub-option. +TEST_F(FlexSubOptionTest, subProcessSupersedeExisting) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, "my-space", + "string")); + defs.addItem(sdef); + 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 supersede = Element::create(string("'abc'")); + sub_option->set("supersede", supersede); + ElementPtr name = Element::create(string("my-option")); + 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); + EXPECT_TRUE(response->getOption(222)); + OptionStringPtr str(new OptionString(Option::V4, 1, "xyzt")); + container->addOption(str); + + 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(1); + ASSERT_TRUE(sub); + EXPECT_EQ(1, sub->getType()); + const OptionBuffer& buffer = sub->getData(); + ASSERT_EQ(3, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "abc", 3)); + + // Only one sub-option. + auto const& opts = opt->getOptions(); + EXPECT_EQ(1, opts.size()); +} + +// Verify that SUPERSEDE action does not add an empty value. +TEST_F(FlexSubOptionTest, subProcessSupersedeEmpty) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, "my-space", + "string")); + defs.addItem(sdef); + 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 supersede = Element::create(string("''")); + sub_option->set("supersede", supersede); + ElementPtr name = Element::create(string("my-option")); + 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(); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + EXPECT_FALSE(response->getOption(222)); +} + +// Verify that SUPERSEDE action can handle vendor-encapsulated-options 43. +TEST_F(FlexSubOptionTest, subProcessSupersede43) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, + VENDOR_ENCAPSULATED_OPTION_SPACE, + "string")); + defs.addItem(sdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_VENDOR_ENCAPSULATED_OPTIONS); + 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 supersede = Element::create(string("'foobar'")); + sub_option->set("supersede", supersede); + ElementPtr name = Element::create(string("my-option")); + 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)); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + OptionPtr opt = response->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS); + ASSERT_TRUE(opt); + EXPECT_EQ(43, opt->getType()); + OptionPtr sub = opt->getOption(1); + ASSERT_TRUE(sub); + EXPECT_EQ(1, sub->getType()); + const OptionBuffer& buffer = sub->getData(); + ASSERT_EQ(6, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "foobar", 6)); +} + +// Verify that SUPERSEDE action can handle DocSIS Vivso. +TEST_F(FlexSubOptionTest, subProcessSupersedeDocSISVIVSO) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_VIVSO_SUBOPTIONS); + 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 supersede = Element::create(string("10.1.2.3")); + sub_option->set("supersede", supersede); + // DocSIS is 4491 + ElementPtr space = Element::create(string("vendor-4491")); + sub_option->set("space", space); + ElementPtr name = Element::create(string("tftp-servers")); + 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)); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + OptionPtr opt = response->getOption(DHO_VIVSO_SUBOPTIONS); + ASSERT_TRUE(opt); + OptionVendorPtr vendor = boost::dynamic_pointer_cast(opt); + ASSERT_TRUE(vendor); + EXPECT_EQ(VENDOR_ID_CABLE_LABS, vendor->getVendorId()); + OptionPtr sub = vendor->getOption(DOCSIS3_V4_TFTP_SERVERS); + ASSERT_TRUE(sub); + const OptionBuffer& buffer = sub->getData(); + ASSERT_EQ(4, buffer.size()); + uint8_t expected[] = { 10, 1, 2, 3 }; + EXPECT_EQ(0, memcmp(&buffer[0], expected, 4)); +} + +// Verify that SUPERSEDE action can handle DocSIS vendor-opts. +TEST_F(FlexSubOptionTest, subProcessSupersedeDocSISVendorOps) { + CfgMgr::instance().setFamily(AF_INET6); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(D6O_VENDOR_OPTS); + 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 supersede = Element::create(string("'foobar'")); + sub_option->set("supersede", supersede); + // DocSIS is 4491 + ElementPtr space = Element::create(string("vendor-4491")); + sub_option->set("space", space); + code = Element::create(DOCSIS3_V6_VENDOR_NAME); + sub_option->set("code", code); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345)); + Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345)); + + EXPECT_NO_THROW(impl_->process(Option::V6, query, response)); + + OptionPtr opt = response->getOption(D6O_VENDOR_OPTS); + ASSERT_TRUE(opt); + OptionVendorPtr vendor = boost::dynamic_pointer_cast(opt); + ASSERT_TRUE(vendor); + EXPECT_EQ(VENDOR_ID_CABLE_LABS, vendor->getVendorId()); + OptionPtr sub = vendor->getOption(DOCSIS3_V6_VENDOR_NAME); + ASSERT_TRUE(sub); + const OptionBuffer& buffer = sub->getData(); + ASSERT_EQ(6, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "foobar", 6)); +} + +// Verify that SUPERSEDE action can handle the Vivso option. +TEST_F(FlexSubOptionTest, subProcessSupersedeVivso) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, + "vendor-123456", "string")); + defs.addItem(sdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_VIVSO_SUBOPTIONS); + 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 supersede = Element::create(string("'foobar'")); + sub_option->set("supersede", supersede); + ElementPtr space = Element::create(string("vendor-123456")); + sub_option->set("space", space); + ElementPtr name = Element::create(string("my-option")); + 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)); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + OptionPtr opt = response->getOption(DHO_VIVSO_SUBOPTIONS); + ASSERT_TRUE(opt); + OptionVendorPtr vendor = boost::dynamic_pointer_cast(opt); + ASSERT_TRUE(vendor); + EXPECT_EQ(123456, vendor->getVendorId()); + OptionPtr sub = vendor->getOption(1); + ASSERT_TRUE(sub); + const OptionBuffer& buffer = sub->getData(); + ASSERT_EQ(6, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "foobar", 6)); +} + +// Verify that SUPERSEDE action can handle the Vivso option with vendor mismatch. +TEST_F(FlexSubOptionTest, subProcessSupersedeVivsoMismatch) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, + "vendor-123456", "string")); + defs.addItem(sdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_VIVSO_SUBOPTIONS); + 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 supersede = Element::create(string("'foobar'")); + sub_option->set("supersede", supersede); + ElementPtr space = Element::create(string("vendor-123456")); + sub_option->set("space", space); + ElementPtr name = Element::create(string("my-option")); + 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)); + OptionVendorPtr vendor(new OptionVendor(Option::V4, 67890)); + response->addOption(vendor); + OptionStringPtr str(new OptionString(Option::V4, 2, "xyzt")); + vendor->addOption(str); + string response_txt = response->toText(); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + OptionPtr opt = response->getOption(DHO_VIVSO_SUBOPTIONS); + ASSERT_TRUE(opt); + vendor = boost::dynamic_pointer_cast(opt); + ASSERT_TRUE(vendor); + EXPECT_EQ(67890, vendor->getVendorId()); + OptionPtr sub = vendor->getOption(1); + EXPECT_FALSE(sub); + sub = vendor->getOption(2); + ASSERT_TRUE(sub); + const OptionBuffer& buffer = sub->getData(); + ASSERT_EQ(4, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "xyzt", 4)); +} + +// Verify that SUPERSEDE action can handle the vendor-opts option. +TEST_F(FlexSubOptionTest, subProcessSupersedeVendorOpts) { + CfgMgr::instance().setFamily(AF_INET6); + + OptionDefSpaceContainer defs; + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, + "vendor-123456", "string")); + defs.addItem(sdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(D6O_VENDOR_OPTS); + 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 supersede = Element::create(string("'foobar'")); + sub_option->set("supersede", supersede); + ElementPtr space = Element::create(string("vendor-123456")); + sub_option->set("space", space); + ElementPtr name = Element::create(string("my-option")); + sub_option->set("name", name); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345)); + Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345)); + + EXPECT_NO_THROW(impl_->process(Option::V6, query, response)); + + OptionPtr opt = response->getOption(D6O_VENDOR_OPTS); + ASSERT_TRUE(opt); + OptionVendorPtr vendor = boost::dynamic_pointer_cast(opt); + ASSERT_TRUE(vendor); + EXPECT_EQ(123456, vendor->getVendorId()); + OptionPtr sub = vendor->getOption(1); + ASSERT_TRUE(sub); + const OptionBuffer& buffer = sub->getData(); + ASSERT_EQ(6, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "foobar", 6)); +} + +// Verify that SUPERSEDE action can handle the vendor-opts option with vendor mismatch. +TEST_F(FlexSubOptionTest, subProcessSupersedeVendorOptsMismatch) { + CfgMgr::instance().setFamily(AF_INET6); + + OptionDefSpaceContainer defs; + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, + "vendor-123456", "string")); + defs.addItem(sdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(D6O_VENDOR_OPTS); + 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 supersede = Element::create(string("'foobar'")); + sub_option->set("supersede", supersede); + ElementPtr space = Element::create(string("vendor-123456")); + sub_option->set("space", space); + ElementPtr name = Element::create(string("my-option")); + sub_option->set("name", name); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345)); + Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345)); + OptionVendorPtr vendor(new OptionVendor(Option::V6, 67890)); + response->addOption(vendor); + OptionStringPtr str(new OptionString(Option::V6, 2, "xyzt")); + vendor->addOption(str); + string response_txt = response->toText(); + + EXPECT_NO_THROW(impl_->process(Option::V6, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + OptionPtr opt = response->getOption(D6O_VENDOR_OPTS); + ASSERT_TRUE(opt); + vendor = boost::dynamic_pointer_cast(opt); + ASSERT_TRUE(vendor); + EXPECT_EQ(67890, vendor->getVendorId()); + OptionPtr sub = vendor->getOption(1); + EXPECT_FALSE(sub); + sub = vendor->getOption(2); + ASSERT_TRUE(sub); + const OptionBuffer& buffer = sub->getData(); + ASSERT_EQ(4, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "xyzt", 4)); +} + +// Verify that REMOVE action removes an already existing sub-option. +TEST_F(FlexSubOptionTest, subProcessRemove) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, "my-space", + "string")); + defs.addItem(sdef); + 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 remove = Element::create(string("'abc' == 'abc'")); + sub_option->set("remove", remove); + ElementPtr name = Element::create(string("my-option")); + 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); + EXPECT_TRUE(response->getOption(222)); + OptionStringPtr str(new OptionString(Option::V4, 1, "xyzt")); + 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) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, "my-space", + "string")); + defs.addItem(sdef); + 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 remove = Element::create(string("'abc' == 'abc'")); + sub_option->set("remove", remove); + ElementPtr name = Element::create(string("my-option")); + sub_option->set("name", name); + 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); + string response_txt = response->toText(); + EXPECT_TRUE(response->getOption(222)); + OptionStringPtr str(new OptionString(Option::V4, 1, "xyzt")); + 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); + EXPECT_FALSE(opt->getOption(1)); +} + +// Verify that REMOVE action removes an already existing sub-option but +// leaves the container option when it is not empty. +TEST_F(FlexSubOptionTest, subProcessRemoveContainerNotEmpty) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 2, "my-space", + "string")); + defs.addItem(sdef); + 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 remove = Element::create(string("'abc' == 'abc'")); + sub_option->set("remove", remove); + ElementPtr name = Element::create(string("my-option")); + 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); + EXPECT_TRUE(response->getOption(222)); + OptionStringPtr str(new OptionString(Option::V4, 1, "abc")); + container->addOption(str); + string response_txt = response->toText(); + str.reset(new OptionString(Option::V4, 2, "xyzt")); + 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); + EXPECT_FALSE(opt->getOption(2)); + OptionPtr sub = opt->getOption(1); + ASSERT_TRUE(sub); + const OptionBuffer& buffer = sub->getData(); + ASSERT_EQ(3, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "abc", 3)); +} + +// Verify that REMOVE action does not removes the container option when the +// sub-option does not exist. +TEST_F(FlexSubOptionTest, subProcessRemoveContainerNoSubOption) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, "my-space", + "string")); + defs.addItem(sdef); + 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 remove = Element::create(string("'abc' == 'abc'")); + sub_option->set("remove", remove); + ElementPtr name = Element::create(string("my-option")); + sub_option->set("name", name); + 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); + EXPECT_TRUE(response->getOption(222)); + 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; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, "my-space", + "string")); + defs.addItem(sdef); + 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 remove = Element::create(string("'abc' == 'xyz'")); + sub_option->set("remove", remove); + ElementPtr name = Element::create(string("my-option")); + 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); + EXPECT_TRUE(response->getOption(222)); + OptionStringPtr str(new OptionString(Option::V4, 1, "abc")); + container->addOption(str); + string response_txt = response->toText(); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + OptionPtr opt = response->getOption(222); + ASSERT_TRUE(opt); + EXPECT_EQ(222, opt->getType()); + OptionPtr sub = opt->getOption(1); + ASSERT_TRUE(sub); + EXPECT_EQ(1, sub->getType()); + const OptionBuffer& buffer = sub->getData(); + ASSERT_EQ(3, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "abc", 3)); +} + +// Verify that REMOVE action can handle vendor-encapsulated-options 43. +TEST_F(FlexSubOptionTest, subProcessRemove43) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, + VENDOR_ENCAPSULATED_OPTION_SPACE, + "string")); + defs.addItem(sdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_VENDOR_ENCAPSULATED_OPTIONS); + 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 remove = Element::create(string("'abc' == 'abc'")); + sub_option->set("remove", remove); + ElementPtr name = Element::create(string("my-option")); + 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, DHO_VENDOR_ENCAPSULATED_OPTIONS)); + response->addOption(container); + EXPECT_TRUE(response->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS)); + OptionStringPtr str(new OptionString(Option::V4, 1, "abc")); + container->addOption(str); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + EXPECT_FALSE(response->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS)); +} + +// Verify that REMOVE action can handle DocSIS Vivso. +TEST_F(FlexSubOptionTest, subProcessRemoveDocSISVIVSO) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_VIVSO_SUBOPTIONS); + 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 remove = Element::create(string("member('ALL')")); + sub_option->set("remove", remove); + // DocSIS is 4491 + ElementPtr space = Element::create(string("vendor-4491")); + sub_option->set("space", space); + ElementPtr name = Element::create(string("tftp-servers")); + sub_option->set("name", name); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + query->addClass("ALL"); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + string response_txt = response->toText(); + OptionVendorPtr vendor(new OptionVendor(Option::V4, 4491)); + response->addOption(vendor); + Option4AddrLstPtr tftp(new Option4AddrLst(DOCSIS3_V4_TFTP_SERVERS, + IOAddress("10.1.2.3"))); + vendor->addOption(tftp); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + EXPECT_FALSE(response->getOption(DHO_VIVSO_SUBOPTIONS)); +} + +// Verify that REMOVE action can handle DocSIS vendor-opts. +TEST_F(FlexSubOptionTest, subProcessRemoveDocSISVendorOps) { + CfgMgr::instance().setFamily(AF_INET6); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(D6O_VENDOR_OPTS); + 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 remove = Element::create(string("'abc' == 'abc'")); + sub_option->set("remove", remove); + // DocSIS is 4491 + ElementPtr space = Element::create(string("vendor-4491")); + sub_option->set("space", space); + code = Element::create(DOCSIS3_V6_VENDOR_NAME); + sub_option->set("code", code); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345)); + Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345)); + string response_txt = response->toText(); + OptionVendorPtr vendor(new OptionVendor(Option::V6, 4491)); + response->addOption(vendor); + OptionStringPtr str(new OptionString(Option::V6, DOCSIS3_V6_VENDOR_NAME, + "foobar")); + vendor->addOption(str); + EXPECT_TRUE(response->getOption(D6O_VENDOR_OPTS)); + + EXPECT_NO_THROW(impl_->process(Option::V6, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + EXPECT_FALSE(response->getOption(D6O_VENDOR_OPTS)); +} + +// Verify that REMOVE action can handle the Vivso option. +TEST_F(FlexSubOptionTest, subProcessRemoveVivso) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, + "vendor-123456", "string")); + defs.addItem(sdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_VIVSO_SUBOPTIONS); + 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 remove = Element::create(string("'abc' == 'abc'")); + sub_option->set("remove", remove); + ElementPtr space = Element::create(string("vendor-123456")); + sub_option->set("space", space); + ElementPtr name = Element::create(string("my-option")); + 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(); + OptionVendorPtr vendor(new OptionVendor(Option::V4, 123456)); + response->addOption(vendor); + OptionStringPtr str(new OptionString(Option::V4, 1, "foobar")); + vendor->addOption(str); + EXPECT_TRUE(response->getOption(DHO_VIVSO_SUBOPTIONS)); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + EXPECT_FALSE(response->getOption(DHO_VIVSO_SUBOPTIONS)); +} + +// Verify that REMOVE action can handle the Vivso option with vendor mismatch. +TEST_F(FlexSubOptionTest, subProcessRemoveVivsoMismatch) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, + "vendor-123456", "string")); + defs.addItem(sdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(DHO_VIVSO_SUBOPTIONS); + 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 remove = Element::create(string("member('ALL')")); + sub_option->set("remove", remove); + ElementPtr space = Element::create(string("vendor-123456")); + sub_option->set("space", space); + ElementPtr name = Element::create(string("my-option")); + sub_option->set("name", name); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + query->addClass("ALL"); + Pkt4Ptr response(new Pkt4(DHCPOFFER, 12345)); + OptionVendorPtr vendor(new OptionVendor(Option::V4, 67890)); + response->addOption(vendor); + OptionStringPtr str(new OptionString(Option::V4, 2, "xyzt")); + vendor->addOption(str); + string response_txt = response->toText(); + + EXPECT_NO_THROW(impl_->process(Option::V4, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + OptionPtr opt = response->getOption(DHO_VIVSO_SUBOPTIONS); + ASSERT_TRUE(opt); + vendor = boost::dynamic_pointer_cast(opt); + ASSERT_TRUE(vendor); + EXPECT_EQ(67890, vendor->getVendorId()); + OptionPtr sub = vendor->getOption(1); + EXPECT_FALSE(sub); + sub = vendor->getOption(2); + ASSERT_TRUE(sub); + const OptionBuffer& buffer = sub->getData(); + ASSERT_EQ(4, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "xyzt", 4)); +} + +// Verify that REMOVE action can handle the vendor-opts option. +TEST_F(FlexSubOptionTest, subProcessRemoveVendorOpts) { + CfgMgr::instance().setFamily(AF_INET6); + + OptionDefSpaceContainer defs; + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, + "vendor-123456", "string")); + defs.addItem(sdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(D6O_VENDOR_OPTS); + 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 remove = Element::create(string("'abc' == 'abc'")); + sub_option->set("remove", remove); + ElementPtr space = Element::create(string("vendor-123456")); + sub_option->set("space", space); + ElementPtr name = Element::create(string("my-option")); + sub_option->set("name", name); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345)); + Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345)); + string response_txt = response->toText(); + OptionVendorPtr vendor(new OptionVendor(Option::V6, 123456)); + response->addOption(vendor); + OptionStringPtr str(new OptionString(Option::V6, 1, "foobar")); + vendor->addOption(str); + EXPECT_TRUE(response->getOption(D6O_VENDOR_OPTS)); + + EXPECT_NO_THROW(impl_->process(Option::V6, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + EXPECT_FALSE(response->getOption(D6O_VENDOR_OPTS)); +} + +// Verify that REMOVE action can handle the vendor-opts option with vendor mismatch. +TEST_F(FlexSubOptionTest, subProcessRemoveVendorOptsMismatch) { + CfgMgr::instance().setFamily(AF_INET6); + + OptionDefSpaceContainer defs; + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, + "vendor-123456", "string")); + defs.addItem(sdef); + EXPECT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs)); + + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(D6O_VENDOR_OPTS); + 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 remove = Element::create(string("'abc' == 'abc'")); + sub_option->set("remove", remove); + ElementPtr space = Element::create(string("vendor-123456")); + sub_option->set("space", space); + ElementPtr name = Element::create(string("my-option")); + sub_option->set("name", name); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345)); + Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345)); + OptionVendorPtr vendor(new OptionVendor(Option::V6, 67890)); + response->addOption(vendor); + OptionStringPtr str(new OptionString(Option::V6, 2, "xyzt")); + vendor->addOption(str); + string response_txt = response->toText(); + + EXPECT_NO_THROW(impl_->process(Option::V6, query, response)); + + EXPECT_EQ(response_txt, response->toText()); + OptionPtr opt = response->getOption(D6O_VENDOR_OPTS); + ASSERT_TRUE(opt); + vendor = boost::dynamic_pointer_cast(opt); + ASSERT_TRUE(vendor); + EXPECT_EQ(67890, vendor->getVendorId()); + OptionPtr sub = vendor->getOption(1); + EXPECT_FALSE(sub); + sub = vendor->getOption(2); + ASSERT_TRUE(sub); + const OptionBuffer& buffer = sub->getData(); + ASSERT_EQ(4, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "xyzt", 4)); +} + +// Verify that the client class must be a string. +TEST_F(FlexSubOptionTest, subOptionConfigBadClass) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(109); + 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(true); + sub_option->set("space", space); + ElementPtr add = Element::create(string("'ab'")); + sub_option->set("add", add); + sub_option->set("code", code); + sub_option->set("client-class", Element::create(true)); + EXPECT_THROW(impl_->testConfigure(options), BadValue); + EXPECT_EQ("'client-class' must be a string: true", impl_->getErrMsg()); +} + +// Verify that a valid client class is accepted. +TEST_F(FlexSubOptionTest, subOptionConfigGuardValid) { + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + options->add(option); + ElementPtr code = Element::create(109); + 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 add = Element::create(string("'ab'")); + sub_option->set("add", add); + ElementPtr sub_code = Element::create(222); + sub_option->set("code", sub_code); + sub_option->set("client-class", Element::create(string("foobar"))); + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + auto map = impl_->getSubOptionConfigMap(); + EXPECT_EQ(1, map.count(109)); + auto smap = map[109]; + FlexOptionImpl::SubOptionConfigPtr sub_cfg; + ASSERT_NO_THROW(sub_cfg = smap.at(222)); + ASSERT_TRUE(sub_cfg); + EXPECT_EQ(222, sub_cfg->getCode()); + EXPECT_EQ(109, sub_cfg->getContainerCode()); + EXPECT_EQ("foobar", sub_cfg->getClass()); +} + +// Verify that a guarded action is skipped when query does not belong to the +// client class of the container option. +TEST_F(FlexSubOptionTest, subOptionConfigGuardOptiondNoMatch) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, "my-space", + "string")); + defs.addItem(sdef); + 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); + option->set("client-class", Element::create(string("foobar"))); + 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 add = Element::create(string("'abc'")); + sub_option->set("add", add); + ElementPtr name = Element::create(string("my-option")); + 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(); + 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 a guarded action is applied when query belongs to the class +// class of the container option. +TEST_F(FlexSubOptionTest, subOptionConfigGuardOptiondMatch) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, "my-space", + "string")); + defs.addItem(sdef); + 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); + option->set("client-class", Element::create(string("foobar"))); + 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 add = Element::create(string("'abc'")); + sub_option->set("add", add); + ElementPtr name = Element::create(string("my-option")); + sub_option->set("name", name); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + query->addClass("foobar"); + 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(1); + ASSERT_TRUE(sub); + EXPECT_EQ(1, sub->getType()); + const OptionBuffer& buffer = sub->getData(); + ASSERT_EQ(3, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "abc", 3)); +} + +// Verify that a guarded action is skipped when query does not belong to the +// client class of the sub-option. +TEST_F(FlexSubOptionTest, subOptionConfigGuardSubOptiondNoMatch) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, "my-space", + "string")); + defs.addItem(sdef); + 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 add = Element::create(string("'abc'")); + sub_option->set("add", add); + ElementPtr name = Element::create(string("my-option")); + sub_option->set("name", name); + sub_option->set("client-class", Element::create(string("foobar"))); + + 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 a guarded action is applied when query belongs to the class +// class of the sub-option. +TEST_F(FlexSubOptionTest, subOptionConfigGuardSubOptiondMatch) { + OptionDefSpaceContainer defs; + OptionDefinitionPtr def(new OptionDefinition("my-container", 222, + DHCP4_OPTION_SPACE, "empty", + "my-space")); + defs.addItem(def); + OptionDefinitionPtr sdef(new OptionDefinition("my-option", 1, "my-space", + "string")); + defs.addItem(sdef); + 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 add = Element::create(string("'abc'")); + sub_option->set("add", add); + ElementPtr name = Element::create(string("my-option")); + sub_option->set("name", name); + sub_option->set("client-class", Element::create(string("foobar"))); + + EXPECT_NO_THROW(impl_->testConfigure(options)); + EXPECT_TRUE(impl_->getErrMsg().empty()) << impl_->getErrMsg(); + + Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 12345)); + query->addClass("foobar"); + 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(1); + ASSERT_TRUE(sub); + EXPECT_EQ(1, sub->getType()); + const OptionBuffer& buffer = sub->getData(); + ASSERT_EQ(3, buffer.size()); + EXPECT_EQ(0, memcmp(&buffer[0], "abc", 3)); +} + } // end of anonymous namespace -// TEST_F(FlexSubOptionTest, subP