From: Marcin Siodelski Date: Tue, 20 Nov 2012 17:32:29 +0000 (+0100) Subject: [2490] Support for creating options using a collection of string values. X-Git-Tag: bind10-1.0.0-beta-release~84^2^2~6 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=35c3fb162500afbfaab41533d160629a3f48017f;p=thirdparty%2Fkea.git [2490] Support for creating options using a collection of string values. --- diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h index c4e1a9c39d..2d5d64e6aa 100644 --- a/src/lib/dhcp/option_data_types.h +++ b/src/lib/dhcp/option_data_types.h @@ -30,6 +30,13 @@ public: isc::Exception(file, line, what) { }; }; +/// @brief Exception to be thrown when cast to the data type was unsuccessful. +class BadDataTypeCast : public Exception { +public: + BadDataTypeCast(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + /// @brief Data types of DHCP option fields. enum OptionDataType { diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc index 1d672f92dc..2a214fd50b 100644 --- a/src/lib/dhcp/option_definition.cc +++ b/src/lib/dhcp/option_definition.cc @@ -20,6 +20,7 @@ #include #include #include +#include using namespace std; using namespace isc::util; @@ -44,11 +45,6 @@ OptionDefinition::DataTypeUtil::DataTypeUtil() { data_types_["record"] = OPT_RECORD_TYPE; } -template -T OptionDefinition::DataTypeUtil::dataTypeCast(const std::string& value_str) const { - return (T()); -} - OptionDataType OptionDefinition::DataTypeUtil::getOptionDataType(const std::string& data_type) { std::map::const_iterator data_type_it = @@ -59,6 +55,135 @@ OptionDefinition::DataTypeUtil::getOptionDataType(const std::string& data_type) return (OPT_UNKNOWN_TYPE); } +template +T OptionDefinition::DataTypeUtil::lexicalCastWithRangeCheck(const std::string& value_str) const { + if (!OptionDataTypeTraits::integer_type && + OptionDataTypeTraits::type != OPT_BOOLEAN_TYPE) { + isc_throw(BadDataTypeCast, "unable to do lexical cast to non-integer and" + << " non-boolean data type"); + } + int64_t result = 0; + try { + result = boost::lexical_cast(value_str); + } catch (const boost::bad_lexical_cast& ex) { + std::string data_type_str = "boolean"; + if (OptionDataTypeTraits::integer_type) { + data_type_str = "integer"; + } + isc_throw(BadDataTypeCast, "unable to do lexical cast to " << data_type_str + << " data type for value " << value_str << ": " << ex.what()); + } + if (OptionDataTypeTraits::integer_type) { + if (result > numeric_limits::max() || + result < numeric_limits::min()) { + isc_throw(BadDataTypeCast, "unable to do lexical cast for value " + << value_str << ". This value is expected to be in the range of " + << numeric_limits::min() << ".." << numeric_limits::max()); + } + } + return (static_cast(result)); +} + +void +OptionDefinition::DataTypeUtil::writeToBuffer(const std::string& value, uint16_t type, + OptionBuffer& buf) { + switch (type) { + case OPT_BINARY_TYPE: + { + OptionBuffer binary; + try { + util::encode::decodeHex(value, binary); + } catch (const Exception& ex) { + isc_throw(BadDataTypeCast, "unable to cast " << value + << " to binary data type: " << ex.what()); + } + buf.insert(buf.end(), binary.begin(), binary.end()); + return; + } + case OPT_BOOLEAN_TYPE: + { + bool bool_value = lexicalCastWithRangeCheck(value); + if (bool_value) { + buf.push_back(static_cast(1)); + } else { + buf.push_back(static_cast(0)); + } + return; + } + case OPT_INT8_TYPE: + { + buf.push_back(static_cast(lexicalCastWithRangeCheck(value))); + return; + } + case OPT_INT16_TYPE: + { + int16_t int_value = lexicalCastWithRangeCheck(value); + buf.resize(buf.size() + 2); + writeUint16(static_cast(int_value), &buf[buf.size() - 2]); + return; + } + case OPT_INT32_TYPE: + { + int32_t int_value = lexicalCastWithRangeCheck(value); + buf.resize(buf.size() + 4); + writeUint32(static_cast(int_value), &buf[buf.size() - 4]); + return; + } + case OPT_UINT8_TYPE: + { + buf.push_back(lexicalCastWithRangeCheck(value)); + return; + } + case OPT_UINT16_TYPE: + { + uint16_t uint_value = lexicalCastWithRangeCheck(value); + buf.resize(buf.size() + 2); + writeUint16(uint_value, &buf[buf.size() - 2]); + return; + } + case OPT_UINT32_TYPE: + { + uint32_t uint_value = lexicalCastWithRangeCheck(value); + buf.resize(buf.size() + 4); + writeUint32(uint_value, &buf[buf.size() - 4]); + return; + } + case OPT_IPV4_ADDRESS_TYPE: + { + asiolink::IOAddress address(value); + if (!address.getAddress().is_v4()) { + isc_throw(BadDataTypeCast, "provided address " << address.toText() + << " is not a valid IPV4 address"); + } + asio::ip::address_v4::bytes_type addr_bytes = + address.getAddress().to_v4().to_bytes(); + buf.resize(buf.size() + addr_bytes.size()); + std::copy_backward(addr_bytes.begin(), addr_bytes.end(), + buf.end()); + return; + } + case OPT_IPV6_ADDRESS_TYPE: + { + asiolink::IOAddress address(value); + if (!address.getAddress().is_v6()) { + isc_throw(BadDataTypeCast, "provided address " << address.toText() + << " is not a valid IPV6 address"); + } + asio::ip::address_v6::bytes_type addr_bytes = + address.getAddress().to_v6().to_bytes(); + buf.resize(buf.size() + addr_bytes.size()); + std::copy_backward(addr_bytes.begin(), addr_bytes.end(), + buf.end()); + return; + } + default: + ; + } + isc_throw(isc::NotImplemented, "write of string, FQDN record into option buffer" + " is not supported yet"); + +} + OptionDefinition::OptionDefinition(const std::string& name, const uint16_t code, const std::string& type, @@ -148,9 +273,32 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type, } OptionPtr -OptionDefinition::optionFactory(Option::Universe, uint16_t, - const std::vector&) const { - return (OptionPtr()); +OptionDefinition::optionFactory(Option::Universe u, uint16_t type, + const std::vector& values) const { + validate(); + + OptionBuffer buf; + if (!array_type_ && type_ != OPT_RECORD_TYPE) { + if (values.size() == 0) { + isc_throw(InvalidOptionValue, "no option value specified"); + } + DataTypeUtil::instance().writeToBuffer(values[0], type_, buf); + } else if (array_type_ && type_ != OPT_RECORD_TYPE) { + for (size_t i = 0; i < values.size(); ++i) { + DataTypeUtil::instance().writeToBuffer(values[i], type_, buf); + } + } else if (type_ == OPT_RECORD_TYPE) { + const RecordFieldsCollection& records = getRecordFields(); + if (records.size() > values.size()) { + isc_throw(InvalidOptionValue, "number of data fields for the option" + << " type " << type_ << " is greater than number of values" + << " provided."); + } + for (size_t i = 0; i < records.size(); ++i) { + DataTypeUtil::instance().writeToBuffer(values[i], records[i], buf); + } + } + return (optionFactory(u, type, buf.begin(), buf.end())); } void diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h index 39840284e9..5224499d5a 100644 --- a/src/lib/dhcp/option_definition.h +++ b/src/lib/dhcp/option_definition.h @@ -27,6 +27,14 @@ namespace isc { namespace dhcp { +/// @brief Exception to be thrown when invalid option value has been +/// specified for a particular option definition. +class InvalidOptionValue : public Exception { +public: + InvalidOptionValue(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + /// @brief Forward declaration to OptionDefinition. class OptionDefinition; @@ -137,9 +145,6 @@ private: return (instance); } - template - T dataTypeCast(const std::string& value_str) const; - /// @brief Convert type given as string value to option data type. /// /// @param data_type_name data type string. @@ -147,6 +152,12 @@ private: /// @return option data type. OptionDataType getOptionDataType(const std::string& data_type_name); + template + T lexicalCastWithRangeCheck(const std::string& value_str) const; + + void writeToBuffer(const std::string& value, uint16_t type, + OptionBuffer& buf); + private: /// @brief Private constructor. /// diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc index 18b46bfd62..0f7191a16e 100644 --- a/src/lib/dhcp/tests/option_definition_unittest.cc +++ b/src/lib/dhcp/tests/option_definition_unittest.cc @@ -202,7 +202,7 @@ TEST_F(OptionDefinitionTest, factoryAddrList6) { // can be used to create option instance with the optionFactory function. TEST_F(OptionDefinitionTest, factoryTokenizedAddrList6) { OptionDefinition opt_def("OPTION_NIS_SERVERS", D6O_NIS_SERVERS, - "ipv6_address", true); + "ipv6-address", true); // Create a vector of some V6 addresses. std::vector addrs; @@ -225,11 +225,7 @@ TEST_F(OptionDefinitionTest, factoryTokenizedAddrList6) { option_v6 = opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, addrs_str); ); - // This is temporary check to make this test pass until factory function is - // implemented and returns the pointer rather than NULL. - ASSERT_FALSE(option_v6); - - /* // Non-null pointer option is supposed to be returned and it + // Non-null pointer option is supposed to be returned and it // should have Option6AddrLst type. ASSERT_TRUE(option_v6); ASSERT_TRUE(typeid(*option_v6) == typeid(Option6AddrLst)); @@ -243,7 +239,7 @@ TEST_F(OptionDefinitionTest, factoryTokenizedAddrList6) { option_cast_v6->getAddresses(); // Returned addresses must match the addresses that have been used to create // the option instance. - EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin())); */ + EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin())); } TEST_F(OptionDefinitionTest, factoryAddrList4) { @@ -292,6 +288,50 @@ TEST_F(OptionDefinitionTest, factoryAddrList4) { isc::OutOfRange); } +// This test checks that a vector of strings, holding IPv4 addresses, +// can be used to create option instance with the optionFactory function. +TEST_F(OptionDefinitionTest, factoryTokenizedAddrList4) { + OptionDefinition opt_def("OPTION_NIS_SERVERS", DHO_NIS_SERVERS, + "ipv4-address", true); + + // Create a vector of some V6 addresses. + std::vector addrs; + addrs.push_back(asiolink::IOAddress("192.168.0.1")); + addrs.push_back(asiolink::IOAddress("172.16.1.1")); + addrs.push_back(asiolink::IOAddress("127.0.0.1")); + addrs.push_back(asiolink::IOAddress("213.41.23.12")); + + // Create a vector of strings representing addresses given above. + std::vector addrs_str; + for (std::vector::const_iterator it = addrs.begin(); + it != addrs.end(); ++it) { + addrs_str.push_back(it->toText()); + } + + // Create DHCPv4 option using the list of IPv4 addresses given in the + // string form. + OptionPtr option_v4; + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_NIS_SERVERS, + addrs_str); + ); + // Non-null pointer option is supposed to be returned and it + // should have Option6AddrLst type. + ASSERT_TRUE(option_v4); + ASSERT_TRUE(typeid(*option_v4) == typeid(Option4AddrLst)); + // Cast to the actual option type to get IPv4 addresses from it. + boost::shared_ptr option_cast_v4 = + boost::static_pointer_cast(option_v4); + // Check that cast was successful. + ASSERT_TRUE(option_cast_v4); + // Get the list of parsed addresses from the option object. + std::vector addrs_returned = + option_cast_v4->getAddresses(); + // Returned addresses must match the addresses that have been used to create + // the option instance. + EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin())); +} + TEST_F(OptionDefinitionTest, factoryEmpty) { OptionDefinition opt_def("OPTION_RAPID_COMMIT", D6O_RAPID_COMMIT, "empty"); @@ -362,6 +402,53 @@ TEST_F(OptionDefinitionTest, factoryBinary) { buf.begin())); } +TEST_F(OptionDefinitionTest, factoryTokenizedBinary) { + OptionDefinition opt_def("OPTION_FOO", 1000, "binary", true); + + // Prepare some dummy data (serverid): 0, 1, 2 etc. + OptionBuffer buf(16); + for (int i = 0; i < buf.size(); ++i) { + buf[i] = i; + } + std::vector hex_data; + hex_data.push_back("00010203"); + hex_data.push_back("04050607"); + hex_data.push_back("08090A0B0C0D0E0F"); + + // Create option instance with the factory function. + // If the OptionDefinition code works properly than + // object of the type Option should be returned. + OptionPtr option_v6; + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, 1000, hex_data); + ); + // Expect base option type returned. + ASSERT_TRUE(typeid(*option_v6) == typeid(Option)); + // Sanity check on universe, length and size. These are + // the basic parameters identifying any option. + EXPECT_EQ(Option::V6, option_v6->getUniverse()); + EXPECT_EQ(4, option_v6->getHeaderLen()); + ASSERT_EQ(buf.size(), option_v6->getData().size()); + + // Get data from the option and compare against reference buffer. + // They are expected to match. + EXPECT_TRUE(std::equal(option_v6->getData().begin(), + option_v6->getData().end(), + buf.begin())); + + // Repeat the same test scenario for DHCPv4 option. + OptionPtr option_v4; + ASSERT_NO_THROW(option_v4 = opt_def.optionFactory(Option::V4, 214, hex_data)); + EXPECT_EQ(Option::V4, option_v4->getUniverse()); + EXPECT_EQ(2, option_v4->getHeaderLen()); + ASSERT_EQ(buf.size(), option_v4->getData().size()); + + EXPECT_TRUE(std::equal(option_v6->getData().begin(), + option_v6->getData().end(), + buf.begin())); +} + + TEST_F(OptionDefinitionTest, factoryIA6) { // This option consists of IAID, T1 and T2 fields (each 4 bytes long). const int option6_ia_len = 12; @@ -444,6 +531,37 @@ TEST_F(OptionDefinitionTest, factoryIAAddr6) { ); } +TEST_F(OptionDefinitionTest, factoryTokenizedIAAddr6) { + // This option consists of IPV6 Address (16 bytes) and preferred-lifetime and + // valid-lifetime fields (each 4 bytes long). + OptionDefinition opt_def("OPTION_IAADDR", D6O_IAADDR, "record"); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + ASSERT_NO_THROW(opt_def.addRecordField("uint32")); + + // Check the positive scenario. + std::vector data_field_values; + data_field_values.push_back("2001:0db8::ff00:0042:8329"); + data_field_values.push_back("1234"); + data_field_values.push_back("5678"); + + OptionPtr option_v6; + ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IAADDR, + data_field_values)); + ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IAAddr)); + boost::shared_ptr option_cast_v6 = + boost::static_pointer_cast(option_v6); + EXPECT_EQ("2001:db8::ff00:42:8329", option_cast_v6->getAddress().toText()); + EXPECT_EQ(1234, option_cast_v6->getPreferred()); + EXPECT_EQ(5678, option_cast_v6->getValid()); + + // This should work for DHCPv6 only, try passing in\valid universe value. + EXPECT_THROW( + opt_def.optionFactory(Option::V4, D6O_IAADDR, data_field_values), + isc::BadValue + ); +} + TEST_F(OptionDefinitionTest, factoryIntegerInvalidType) { // The template function factoryInteger<> accepts integer values only // as template typename. Here we try passing different type and @@ -479,6 +597,31 @@ TEST_F(OptionDefinitionTest, factoryUint8) { // @todo Add more cases for DHCPv4 } +TEST_F(OptionDefinitionTest, factoryTokenizedUint8) { + OptionDefinition opt_def("OPTION_PREFERENCE", D6O_PREFERENCE, "uint8"); + + OptionPtr option_v6; + std::vector values; + values.push_back("123"); + values.push_back("456"); + try { + option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, values); + } catch (std::exception& ex) { + std::cout << ex.what() << std::endl; + } + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, values); + ); + ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int)); + // Validate the value. + boost::shared_ptr > option_cast_v6 = + boost::static_pointer_cast >(option_v6); + EXPECT_EQ(123, option_cast_v6->getValue()); + + // @todo Add more cases for DHCPv4 +} + + TEST_F(OptionDefinitionTest, factoryUint16) { OptionDefinition opt_def("OPTION_ELAPSED_TIME", D6O_ELAPSED_TIME, "uint16"); @@ -505,6 +648,27 @@ TEST_F(OptionDefinitionTest, factoryUint16) { // @todo Add more cases for DHCPv4 } +TEST_F(OptionDefinitionTest, factoryTokenizedUint16) { + OptionDefinition opt_def("OPTION_ELAPSED_TIME", D6O_ELAPSED_TIME, "uint16"); + + OptionPtr option_v6; + + std::vector values; + values.push_back("1234"); + values.push_back("5678"); + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, values); + ); + ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int)); + // Validate the value. + boost::shared_ptr > option_cast_v6 = + boost::static_pointer_cast >(option_v6); + EXPECT_EQ(1234, option_cast_v6->getValue()); + + // @todo Add more cases for DHCPv4 + +} + TEST_F(OptionDefinitionTest, factoryUint32) { OptionDefinition opt_def("OPTION_CLT_TIME", D6O_CLT_TIME, "uint32"); @@ -532,6 +696,26 @@ TEST_F(OptionDefinitionTest, factoryUint32) { // @todo Add more cases for DHCPv4 } +TEST_F(OptionDefinitionTest, factoryTokenizedUint32) { + OptionDefinition opt_def("OPTION_CLT_TIME", D6O_CLT_TIME, "uint32"); + + OptionPtr option_v6; + std::vector values; + values.push_back("123456"); + values.push_back("789"); + ASSERT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, values); + ); + ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int)); + // Validate the value. + boost::shared_ptr > option_cast_v6 = + boost::static_pointer_cast >(option_v6); + EXPECT_EQ(123456, option_cast_v6->getValue()); + + // @todo Add more cases for DHCPv4 +} + + TEST_F(OptionDefinitionTest, factoryUint16Array) { // Let's define some dummy option. const uint16_t opt_code = 79; @@ -575,6 +759,30 @@ TEST_F(OptionDefinitionTest, factoryUint16Array) { ); } +TEST_F(OptionDefinitionTest, factoryTokenizedUint16Array) { + // Let's define some dummy option. + const uint16_t opt_code = 79; + OptionDefinition opt_def("OPTION_UINT16_ARRAY", opt_code, "uint16", true); + + OptionPtr option_v6; + std::vector str_values; + str_values.push_back("12345"); + str_values.push_back("5679"); + str_values.push_back("12"); + EXPECT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, str_values); + ); + ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IntArray)); + boost::shared_ptr > option_cast_v6 = + boost::static_pointer_cast >(option_v6); + // Get the values from the initiated options and validate. + std::vector values = option_cast_v6->getValues(); + EXPECT_EQ(12345, values[0]); + EXPECT_EQ(5679, values[1]); + EXPECT_EQ(12, values[2]); +} + + TEST_F(OptionDefinitionTest, factoryUint32Array) { // Let's define some dummy option. const uint16_t opt_code = 80; @@ -619,6 +827,33 @@ TEST_F(OptionDefinitionTest, factoryUint32Array) { ); } +TEST_F(OptionDefinitionTest, factoryTokenizedUint32Array) { + // Let's define some dummy option. + const uint16_t opt_code = 80; + + OptionDefinition opt_def("OPTION_UINT32_ARRAY", opt_code, "uint32", true); + + OptionPtr option_v6; + std::vector str_values; + str_values.push_back("123456"); + str_values.push_back("7"); + str_values.push_back("256"); + str_values.push_back("1111"); + + EXPECT_NO_THROW( + option_v6 = opt_def.optionFactory(Option::V6, opt_code, str_values); + ); + ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IntArray)); + boost::shared_ptr > option_cast_v6 = + boost::static_pointer_cast >(option_v6); + // Get the values from the initiated options and validate. + std::vector values = option_cast_v6->getValues(); + EXPECT_EQ(123456, values[0]); + EXPECT_EQ(7, values[1]); + EXPECT_EQ(256, values[2]); + EXPECT_EQ(1111, values[3]); +} + TEST_F(OptionDefinitionTest, recognizeFormat) { // IA_NA option format. OptionDefinition opt_def1("OPTION_IA_NA", D6O_IA_NA, "record");