From a2329d1743d4be61b40c0b8c6da24253c4f57d7d Mon Sep 17 00:00:00 2001 From: Marcin Siodelski Date: Tue, 17 Nov 2015 13:30:52 +0100 Subject: [PATCH] [3874] Added DUID configuration parser. --- src/lib/dhcpsrv/Makefile.am | 2 + src/lib/dhcpsrv/parsers/duid_config_parser.cc | 133 +++++++++++ src/lib/dhcpsrv/parsers/duid_config_parser.h | 87 +++++++ src/lib/dhcpsrv/tests/Makefile.am | 1 + src/lib/dhcpsrv/tests/cfg_duid_unittest.cc | 2 + .../tests/duid_config_parser_unittest.cc | 214 ++++++++++++++++++ 6 files changed, 439 insertions(+) create mode 100644 src/lib/dhcpsrv/parsers/duid_config_parser.cc create mode 100644 src/lib/dhcpsrv/parsers/duid_config_parser.h create mode 100644 src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index fbf237fefc..82b0eb03f8 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -139,6 +139,8 @@ libkea_dhcpsrv_la_SOURCES += parsers/dbaccess_parser.cc libkea_dhcpsrv_la_SOURCES += parsers/dbaccess_parser.h libkea_dhcpsrv_la_SOURCES += parsers/dhcp_parsers.cc libkea_dhcpsrv_la_SOURCES += parsers/dhcp_parsers.h +libkea_dhcpsrv_la_SOURCES += parsers/duid_config_parser.cc +libkea_dhcpsrv_la_SOURCES += parsers/duid_config_parser.h libkea_dhcpsrv_la_SOURCES += parsers/expiration_config_parser.cc libkea_dhcpsrv_la_SOURCES += parsers/expiration_config_parser.h libkea_dhcpsrv_la_SOURCES += parsers/host_reservation_parser.cc diff --git a/src/lib/dhcpsrv/parsers/duid_config_parser.cc b/src/lib/dhcpsrv/parsers/duid_config_parser.cc new file mode 100644 index 0000000000..5afb9bf605 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/duid_config_parser.cc @@ -0,0 +1,133 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace isc::data; + +namespace isc { +namespace dhcp { + +DUIDConfigParser::DUIDConfigParser() + : DhcpConfigParser() { +} + +void +DUIDConfigParser::build(isc::data::ConstElementPtr duid_configuration) { + bool type_present = false; + BOOST_FOREACH(ConfigPair element, duid_configuration->mapValue()) { + try { + if (element.first == "type") { + type_present = true; + setType(element.second->stringValue()); + } else if (element.first == "identifier") { + setIdentifier(element.second->stringValue()); + } else if (element.first == "htype") { + setHType(element.second->intValue()); + } else if (element.first == "time") { + setTime(element.second->intValue()); + } else if (element.first == "enterprise-id") { + setEnterpriseId(element.second->intValue()); + } else { + isc_throw(DhcpConfigError, "unsupported configuration " + "parameter '" << element.first << "'"); + } + } catch (const std::exception& ex) { + // Append position. + isc_throw(DhcpConfigError, ex.what() << " (" + << element.second->getPosition() << ")"); + } + } + + // "type" is mandatory + if (!type_present) { + isc_throw(DhcpConfigError, "mandatory parameter \"type\" not specified" + " for the DUID configuration (" + << duid_configuration->getPosition() << ")"); + } +} + +void +DUIDConfigParser::setType(const std::string& duid_type) const { + // Map DUID type represented as text into numeric value. + DUID::DUIDType numeric_type = DUID::DUID_UNKNOWN; + if (duid_type == "LLT") { + numeric_type = DUID::DUID_LLT; + } else if (duid_type == "EN") { + numeric_type = DUID::DUID_EN; + } else if (duid_type == "LL") { + numeric_type = DUID::DUID_LL; + } else { + isc_throw(DhcpConfigError, "unsupported DUID type '" + << duid_type << "'. Expected: LLT, EN or LL"); + } + + const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID(); + cfg->setType(static_cast(numeric_type)); +} + +void +DUIDConfigParser::setIdentifier(const std::string& identifier) const { + const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID(); + cfg->setIdentifier(identifier); +} + +void +DUIDConfigParser::setHType(const int64_t htype) const { + const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID(); + checkRange("htype", htype); + cfg->setHType(static_cast(htype)); + +} + +void +DUIDConfigParser::setTime(const int64_t new_time) const { + const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID(); + checkRange("time", new_time); + cfg->setTime(static_cast(new_time)); +} + +void +DUIDConfigParser::setEnterpriseId(const int64_t enterprise_id) const { + const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID(); + checkRange("enterprise-id", enterprise_id); + cfg->setEnterpriseId(static_cast(enterprise_id)); +} + +template +void +DUIDConfigParser::checkRange(const std::string& parameter_name, + const int64_t parameter_value) const { + if ((parameter_value < 0) || + (parameter_value > std::numeric_limits::max())) { + isc_throw(DhcpConfigError, "out of range value '" << parameter_value + << "' specified for parameter '" << parameter_name + << "'; expected value in range of [0.." + << std::numeric_limits::max() << "]"); + } +} + + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/parsers/duid_config_parser.h b/src/lib/dhcpsrv/parsers/duid_config_parser.h new file mode 100644 index 0000000000..05639c50d5 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/duid_config_parser.h @@ -0,0 +1,87 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef DUID_CONFIG_PARSER_H +#define DUID_CONFIG_PARSER_H + +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Parser for a single host reservation entry. +class DUIDConfigParser : public DhcpConfigParser { +public: + + /// @brief Constructor. + DUIDConfigParser(); + + /// @brief Parses DUID configuration. + /// + /// @param duid_configuration Data element holding a map representing + /// DUID configuration. + /// + /// @throw DhcpConfigError If the configuration is invalid. + virtual void build(isc::data::ConstElementPtr duid_configuration); + + /// @brief Commit, unused. + virtual void commit() { } + +private: + + /// @brief Validate and set DUID type. + /// + /// @param duid_type DUID type in textfual format. + void setType(const std::string& duid_type) const; + + /// @brief Validate and set identifier. + /// + /// @param identifier Identifier. + void setIdentifier(const std::string& identifier) const; + + /// @brief Validate and set hardware type. + /// + /// @param htype Hardware type. + void setHType(const int64_t htype) const; + + /// @brief Validate and set time value. + /// + /// @param new_time Time value to be used for DUID. + void setTime(const int64_t new_time) const; + + /// @brief Validate and set enterprise id. + /// + /// @param enterprise_id Enterprise id. + void setEnterpriseId(const int64_t enterprise_id) const; + + /// @brief Verifies if the specified parameter is in range. + /// + /// Each numeric value must be in range of [0 .. max_value], where + /// max_value is a maximum value for the numeric type used for this + /// parameter. + /// + /// @param parameter_name Parameter name. + /// @tparam Numeric type of the specified parameter. + template + void checkRange(const std::string& parameter_name, + const int64_t parameter_value) const; +}; + +} +} // end of namespace isc + +#endif // DUID_CONFIG_PARSER_H diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index 69a91079a2..3003747062 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -84,6 +84,7 @@ libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc libdhcpsrv_unittests_SOURCES += daemon_unittest.cc libdhcpsrv_unittests_SOURCES += database_connection_unittest.cc libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc +libdhcpsrv_unittests_SOURCES += duid_config_parser_unittest.cc libdhcpsrv_unittests_SOURCES += expiration_config_parser_unittest.cc libdhcpsrv_unittests_SOURCES += host_mgr_unittest.cc libdhcpsrv_unittests_SOURCES += host_unittest.cc diff --git a/src/lib/dhcpsrv/tests/cfg_duid_unittest.cc b/src/lib/dhcpsrv/tests/cfg_duid_unittest.cc index 31d62177bd..e1da7655e5 100644 --- a/src/lib/dhcpsrv/tests/cfg_duid_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_duid_unittest.cc @@ -93,6 +93,8 @@ TEST(CfgDUIDTest, setIdentifier) { << toString(cfg_duid.getIdentifier()); } +// This test verifies that the invalid identifier is rejected and +// exception is thrown. TEST(CfgDUIDTest, setInvalidIdentifier) { CfgDUID cfg_duid; // Check that hexadecimal characters may be lower case. diff --git a/src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc b/src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc new file mode 100644 index 0000000000..8c5c641567 --- /dev/null +++ b/src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc @@ -0,0 +1,214 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace isc; +using namespace isc::data; +using namespace isc::dhcp; + +namespace { + +/// @brief Test fixture class for @c DUIDConfigParser +class DUIDConfigParserTest : public ::testing::Test { +public: + + /// @brief Creates simple configuration with DUID type only. + /// + /// @param duid_type DUID type in the textual format. + std::string createConfigWithType(const std::string& duid_type) const; + + /// @brief Creates simple configuration with DUID type and one + /// numeric parameter. + /// + /// @param name Parameter name. + /// @param value Parameter value. + std::string createConfigWithInteger(const std::string& name, + const int64_t value) const; + + /// @brief Parse configuration. + /// + /// @param config String representing DUID configuration. + void build(const std::string& config) const; + + /// @brief Test that only a DUID type can be specified. + /// + /// @param duid_type DUID type in numeric format. + /// @param duid_type_text DUID type in textual format. + void testTypeOnly(const DUID::DUIDType& duid_type, + const std::string duid_type_text) const; + + /// @brief Test that invalid configuration is rejected. + /// + /// @param config Holds JSON configuration to be used. + void testInvalidConfig(const std::string& config) const; + + /// @brief Test out of range numeric values. + /// + /// @param param_name Parameter name. + /// @tparam Type of the numeric parameter. + template + void testOutOfRange(const std::string& param_name) { + // Obtain maximum value for the specified numeric type. + const uint64_t max_value = std::numeric_limits::max(); + + // Negative values are not allowed. + EXPECT_THROW(build(createConfigWithInteger(param_name, -1)), + DhcpConfigError); + // Zero is allowed. + EXPECT_NO_THROW(build(createConfigWithInteger(param_name, 0))); + // Maximum value. + EXPECT_NO_THROW(build(createConfigWithInteger(param_name, max_value))); + // Value greater than maximum should result in exception. + EXPECT_THROW(build(createConfigWithInteger(param_name, max_value + 1)), + DhcpConfigError); + } + + /// @brief Converts vector to string of hexadecimal digits. + /// + /// @param vec Input vector. + /// @return String of hexadecimal digits converted from vector. + std::string toString(const std::vector& vec) const; +}; + +std::string +DUIDConfigParserTest::createConfigWithType(const std::string& duid_type) const { + std::ostringstream s; + s << "{ \"type\": \"" << duid_type << "\" }"; + return (s.str()); +} + +std::string +DUIDConfigParserTest::createConfigWithInteger(const std::string& name, + const int64_t value) const { + std::ostringstream s; + s << "{ \"type\": \"LLT\", \"" << name << "\": " << value << " }"; + return (s.str()); +} + +void +DUIDConfigParserTest::build(const std::string& config) const { + ElementPtr config_element = Element::fromJSON(config); + DUIDConfigParser parser; + parser.build(config_element); +} + + +void +DUIDConfigParserTest::testTypeOnly(const DUID::DUIDType& duid_type, + const std::string duid_type_text) const { + // Use DUID configuration with only a "type". + ASSERT_NO_THROW(build(createConfigWithType(duid_type_text))); + + // Make sure that the type is correct and that other parameters are set + // to their defaults. + CfgDUIDPtr cfg_duid = CfgMgr::instance().getStagingCfg()->getCfgDUID(); + EXPECT_EQ(duid_type, cfg_duid->getType()); + EXPECT_TRUE(cfg_duid->getIdentifier().empty()); + EXPECT_EQ(0, cfg_duid->getHType()); + EXPECT_EQ(0, cfg_duid->getTime()); + EXPECT_EQ(0, cfg_duid->getEnterpriseId()); +} + +void +DUIDConfigParserTest::testInvalidConfig(const std::string& config) const { + EXPECT_THROW(build(config), DhcpConfigError); +} + +std::string +DUIDConfigParserTest::toString(const std::vector& vec) const { + try { + return (util::encode::encodeHex(vec)); + } catch (...) { + ADD_FAILURE() << "toString: unable to encode vector to" + " hexadecimal string"; + } + return (""); +} + +// This test verifies that it is allowed to specify a DUID-LLT type. +TEST_F(DUIDConfigParserTest, typeOnlyLLT) { + testTypeOnly(DUID::DUID_LLT, "LLT"); +} + +// This test verifies that it is allowed to specify a DUID-EN type. +TEST_F(DUIDConfigParserTest, typeOnlyEN) { + testTypeOnly(DUID::DUID_EN, "EN"); +} + +// This test verifies that it is allowed to specify a DUID-LL type. +TEST_F(DUIDConfigParserTest, typeOnlyLL) { + testTypeOnly(DUID::DUID_LL, "LL"); +} + +// This test verifies that using unsupported DUID type will result in +// configuration error. +TEST_F(DUIDConfigParserTest, typeInvalid) { + testInvalidConfig(createConfigWithType("WRONG")); +} + +// This test verifies that DUID type is required. +TEST_F(DUIDConfigParserTest, noType) { + // First check that the configuration with DUID type specified is + // accepted. + ASSERT_NO_THROW(build("{ \"type\": \"LLT\", \"time\": 1 }")); + // Now remove the type and expect an error. + testInvalidConfig("{ \"time\": 1 }"); +} + +// This test verifies that all parameters can be set. +TEST_F(DUIDConfigParserTest, allParameters) { + // Set all parameters. + ASSERT_NO_THROW(build("{ \"type\": \"EN\"," + " \"identifier\": \"ABCDEF\"," + " \"time\": 100," + " \"htype\": 8," + " \"enterprise-id\": 2024" + "}")); + + // Verify that parameters have been set correctly. + CfgDUIDPtr cfg_duid = CfgMgr::instance().getStagingCfg()->getCfgDUID(); + EXPECT_EQ(DUID::DUID_EN, cfg_duid->getType()); + EXPECT_EQ("ABCDEF", toString(cfg_duid->getIdentifier())); + EXPECT_EQ(8, cfg_duid->getHType()); + EXPECT_EQ(100, cfg_duid->getTime()); + EXPECT_EQ(2024, cfg_duid->getEnterpriseId()); +} + +// Test out of range values for time. +TEST_F(DUIDConfigParserTest, timeOutOfRange) { + testOutOfRange("time"); +} + +// Test out of range values for hardware type. +TEST_F(DUIDConfigParserTest, htypeOutOfRange) { + testOutOfRange("htype"); +} + +// Test out of range values for enterprise id. +TEST_F(DUIDConfigParserTest, enterpriseIdOutOfRange) { + testOutOfRange("enterprise-id"); +} + +} // end of anonymous namespace