Commit 35c3fb16 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[2490] Support for creating options using a collection of string values.

parent ce7728b0
......@@ -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 {
......
......@@ -20,6 +20,7 @@
#include <dhcp/option6_int.h>
#include <dhcp/option6_int_array.h>
#include <dhcp/option_definition.h>
#include <util/encode/hex.h>
using namespace std;
using namespace isc::util;
......@@ -44,11 +45,6 @@ OptionDefinition::DataTypeUtil::DataTypeUtil() {
data_types_["record"] = OPT_RECORD_TYPE;
}
template<typename T>
T OptionDefinition::DataTypeUtil::dataTypeCast(const std::string& value_str) const {
return (T());
}
OptionDataType
OptionDefinition::DataTypeUtil::getOptionDataType(const std::string& data_type) {
std::map<std::string, OptionDataType>::const_iterator data_type_it =
......@@ -59,6 +55,135 @@ OptionDefinition::DataTypeUtil::getOptionDataType(const std::string& data_type)
return (OPT_UNKNOWN_TYPE);
}
template<typename T>
T OptionDefinition::DataTypeUtil::lexicalCastWithRangeCheck(const std::string& value_str) const {
if (!OptionDataTypeTraits<T>::integer_type &&
OptionDataTypeTraits<T>::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<int64_t>(value_str);
} catch (const boost::bad_lexical_cast& ex) {
std::string data_type_str = "boolean";
if (OptionDataTypeTraits<T>::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<T>::integer_type) {
if (result > numeric_limits<T>::max() ||
result < numeric_limits<T>::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<T>::min() << ".." << numeric_limits<T>::max());
}
}
return (static_cast<T>(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<bool>(value);
if (bool_value) {
buf.push_back(static_cast<uint8_t>(1));
} else {
buf.push_back(static_cast<uint8_t>(0));
}
return;
}
case OPT_INT8_TYPE:
{
buf.push_back(static_cast<uint8_t>(lexicalCastWithRangeCheck<int8_t>(value)));
return;
}
case OPT_INT16_TYPE:
{
int16_t int_value = lexicalCastWithRangeCheck<int16_t>(value);
buf.resize(buf.size() + 2);
writeUint16(static_cast<uint16_t>(int_value), &buf[buf.size() - 2]);
return;
}
case OPT_INT32_TYPE:
{
int32_t int_value = lexicalCastWithRangeCheck<int32_t>(value);
buf.resize(buf.size() + 4);
writeUint32(static_cast<uint32_t>(int_value), &buf[buf.size() - 4]);
return;
}
case OPT_UINT8_TYPE:
{
buf.push_back(lexicalCastWithRangeCheck<int8_t>(value));
return;
}
case OPT_UINT16_TYPE:
{
uint16_t uint_value = lexicalCastWithRangeCheck<uint16_t>(value);
buf.resize(buf.size() + 2);
writeUint16(uint_value, &buf[buf.size() - 2]);
return;
}
case OPT_UINT32_TYPE:
{
uint32_t uint_value = lexicalCastWithRangeCheck<uint32_t>(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<std::string>&) const {
return (OptionPtr());
OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
const std::vector<std::string>& 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
......
......@@ -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<typename T>
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<typename T>
T lexicalCastWithRangeCheck(const std::string& value_str) const;
void writeToBuffer(const std::string& value, uint16_t type,
OptionBuffer& buf);
private:
/// @brief Private constructor.
///
......
......@@ -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<asiolink::IOAddress> 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<asiolink::IOAddress> 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<std::string> addrs_str;
for (std::vector<asiolink::IOAddress>::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<Option4AddrLst> option_cast_v4 =
boost::static_pointer_cast<Option4AddrLst>(option_v4);
// Check that cast was successful.
ASSERT_TRUE(option_cast_v4);
// Get the list of parsed addresses from the option object.
std::vector<asiolink::IOAddress> 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<std::string> 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<std::string> 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<Option6IAAddr> option_cast_v6 =
boost::static_pointer_cast<Option6IAAddr>(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<std::string> 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<uint8_t>));
// Validate the value.
boost::shared_ptr<Option6Int<uint8_t> > option_cast_v6 =
boost::static_pointer_cast<Option6Int<uint8_t> >(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<std::string> 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<uint16_t>));
// Validate the value.
boost::shared_ptr<Option6Int<uint16_t> > option_cast_v6 =
boost::static_pointer_cast<Option6Int<uint16_t> >(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<std::string> 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<uint32_t>));
// Validate the value.
boost::shared_ptr<Option6Int<uint32_t> > option_cast_v6 =
boost::static_pointer_cast<Option6Int<uint32_t> >(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<std::string> 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<uint16_t>));
boost::shared_ptr<Option6IntArray<uint16_t> > option_cast_v6 =
boost::static_pointer_cast<Option6IntArray<uint16_t> >(option_v6);
// Get the values from the initiated options and validate.
std::vector<uint16_t> 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<std::string> 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<uint32_t>));
boost::shared_ptr<Option6IntArray<uint32_t> > option_cast_v6 =
boost::static_pointer_cast<Option6IntArray<uint32_t> >(option_v6);
// Get the values from the initiated options and validate.
std::vector<uint32_t> 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");
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment