Commit ff216738 authored by Marcin Siodelski's avatar Marcin Siodelski

[2312] Implemented OptionCustom::toText() function.

parent 2dd98a11
......@@ -242,20 +242,30 @@ OptionCustom::len() {
return (length);
}
std::string OptionCustom::toText(int /* =0 */ ) {
std::string OptionCustom::toText(int indent /* = 0 */ ) {
std::stringstream tmp;
/* for (int i = 0; i < indent; i++)
for (int i = 0; i < indent; ++i)
tmp << " ";
tmp << "type=" << type_ << ", len=" << len()-getHeaderLen() << ": ";
tmp << "type=" << type_ << ", len=" << len()-getHeaderLen()
<< ", data fields:" << std::endl;
for (unsigned int i = 0; i < data_.size(); i++) {
if (i) {
tmp << ":";
OptionDataType data_type = definition_.getType();
for (unsigned int i = 0; i < getDataFieldsNum(); ++i) {
if (data_type == OPT_RECORD_TYPE) {
const OptionDefinition::RecordFieldsCollection& fields =
definition_.getRecordFields();
for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
field != fields.end(); ++field) {
for (int j = 0; j < indent + 2; ++j) {
tmp << " ";
}
tmp << OptionDataTypeUtil::getDataTypeName(*field)
<< ", value = " << "here is a value" << std::endl;
}
}
tmp << setfill('0') << setw(2) << hex
<< static_cast<unsigned short>(data_[i]);
}
// print suboptions
......@@ -263,7 +273,7 @@ std::string OptionCustom::toText(int /* =0 */ ) {
opt != options_.end();
++opt) {
tmp << (*opt).second->toText(indent+2);
} */
}
return tmp.str();
}
......
......@@ -18,6 +18,54 @@
namespace isc {
namespace dhcp {
OptionDataTypeUtil::OptionDataTypeUtil() {
data_types_["empty"] = OPT_EMPTY_TYPE;
data_types_["binary"] = OPT_BINARY_TYPE;
data_types_["boolean"] = OPT_BOOLEAN_TYPE;
data_types_["int8"] = OPT_INT8_TYPE;
data_types_["int16"] = OPT_INT16_TYPE;
data_types_["int32"] = OPT_INT32_TYPE;
data_types_["uint8"] = OPT_UINT8_TYPE;
data_types_["uint16"] = OPT_UINT16_TYPE;
data_types_["uint32"] = OPT_UINT32_TYPE;
data_types_["ipv4-address"] = OPT_IPV4_ADDRESS_TYPE;
data_types_["ipv6-address"] = OPT_IPV6_ADDRESS_TYPE;
data_types_["string"] = OPT_STRING_TYPE;
data_types_["fqdn"] = OPT_FQDN_TYPE;
data_types_["record"] = OPT_RECORD_TYPE;
data_type_names_[OPT_EMPTY_TYPE] = "empty";
data_type_names_[OPT_BINARY_TYPE] = "binary";
data_type_names_[OPT_BOOLEAN_TYPE] = "boolean";
data_type_names_[OPT_INT8_TYPE] = "int8";
data_type_names_[OPT_INT16_TYPE] = "int16";
data_type_names_[OPT_INT32_TYPE] = "int32";
data_type_names_[OPT_UINT8_TYPE] = "uint8";
data_type_names_[OPT_UINT16_TYPE] = "uint16";
data_type_names_[OPT_UINT32_TYPE] = "uint32";
data_type_names_[OPT_IPV4_ADDRESS_TYPE] = "ipv4-address";
data_type_names_[OPT_IPV6_ADDRESS_TYPE] = "ipv4-address";
data_type_names_[OPT_STRING_TYPE] = "string";
data_type_names_[OPT_FQDN_TYPE] = "fqdn";
data_type_names_[OPT_RECORD_TYPE] = "record";
data_type_names_[OPT_UNKNOWN_TYPE] = "unknown";
}
OptionDataType
OptionDataTypeUtil::getDataType(const std::string& data_type) {
return (OptionDataTypeUtil::instance().getDataTypeImpl(data_type));
}
OptionDataType
OptionDataTypeUtil::getDataTypeImpl(const std::string& data_type) const {
std::map<std::string, OptionDataType>::const_iterator data_type_it =
data_types_.find(data_type);
if (data_type_it != data_types_.end()) {
return (data_type_it->second);
}
return (OPT_UNKNOWN_TYPE);
}
int
OptionDataTypeUtil::getDataTypeLen(const OptionDataType data_type) {
switch (data_type) {
......@@ -41,6 +89,27 @@ OptionDataTypeUtil::getDataTypeLen(const OptionDataType data_type) {
return (0);
}
const std::string&
OptionDataTypeUtil::getDataTypeName(const OptionDataType data_type) {
return (OptionDataTypeUtil::instance().getDataTypeNameImpl(data_type));
}
const std::string&
OptionDataTypeUtil::getDataTypeNameImpl(const OptionDataType data_type) const {
std::map<OptionDataType, std::string>::const_iterator data_type_it =
data_type_names_.find(data_type);
if (data_type_it != data_type_names_.end()) {
return (data_type_it->second);
}
return (data_type_names_.find(OPT_UNKNOWN_TYPE)->second);
}
OptionDataTypeUtil&
OptionDataTypeUtil::instance() {
static OptionDataTypeUtil instance;
return (instance);
}
void
OptionDataTypeUtil::readAddress(const std::vector<uint8_t>& buf,
const short family,
......
......@@ -174,10 +174,22 @@ struct OptionDataTypeTraits<std::string> {
static const OptionDataType type = OPT_STRING_TYPE;
};
/// @brief Utility class to write/read data to/from a buffer.
/// @brief Utility class for option data types.
class OptionDataTypeUtil {
public:
/// @brief Return option data type from its name.
///
/// @param data_type data type name.
/// @return option data type.
static OptionDataType getDataType(const std::string& data_type);
/// @brief Return option data type name from the data type enumerator.
///
/// @param data_type option data type.
/// @return option data type name.
static const std::string& getDataTypeName(const OptionDataType data_type);
/// @brief Get data type buffer length.
///
/// This functionm returs the size of a particular data type.
......@@ -312,7 +324,43 @@ public:
/// @param [out] buf output buffer.
static void writeString(const std::string& value,
std::vector<uint8_t>& buf);
private:
/// The container holding mapping of data type names to
/// data types enumerator.
std::map<std::string, OptionDataType> data_types_;
/// The container holding mapping of data types to data
/// type names.
std::map<OptionDataType, std::string> data_type_names_;
/// @brief Private constructor.
///
/// This constructor is private because this class should
/// be used as singleton (through static public functions).
OptionDataTypeUtil();
/// @brief Return instance of OptionDataTypeUtil
///
/// This function is used by some of the public static functions
/// to create an instance of OptionDataTypeUtil class.
/// When instance is called it calls the class'es constructor
/// and initializes some of the private data members.
///
/// @return instance of OptionDataTypeUtil singleton.
static OptionDataTypeUtil& instance();
/// @brief Return option data type from its name.
///
/// @param data_type data type name.
/// @return option data type.
OptionDataType getDataTypeImpl(const std::string& data_type) const;
/// @brief Return option data type name from the data type enumerator.
///
/// @param data_type option data type.
/// @return option data type name.
const std::string& getDataTypeNameImpl(const OptionDataType data_type) const;
};
......
......@@ -28,150 +28,6 @@ using namespace isc::util;
namespace isc {
namespace dhcp {
OptionDefinition::DataTypeUtil::DataTypeUtil() {
data_types_["empty"] = OPT_EMPTY_TYPE;
data_types_["binary"] = OPT_BINARY_TYPE;
data_types_["boolean"] = OPT_BOOLEAN_TYPE;
data_types_["int8"] = OPT_INT8_TYPE;
data_types_["int16"] = OPT_INT16_TYPE;
data_types_["int32"] = OPT_INT32_TYPE;
data_types_["uint8"] = OPT_UINT8_TYPE;
data_types_["uint16"] = OPT_UINT16_TYPE;
data_types_["uint32"] = OPT_UINT32_TYPE;
data_types_["ipv4-address"] = OPT_IPV4_ADDRESS_TYPE;
data_types_["ipv6-address"] = OPT_IPV6_ADDRESS_TYPE;
data_types_["string"] = OPT_STRING_TYPE;
data_types_["fqdn"] = OPT_FQDN_TYPE;
data_types_["record"] = OPT_RECORD_TYPE;
}
OptionDataType
OptionDefinition::DataTypeUtil::getOptionDataType(const std::string& data_type) {
std::map<std::string, OptionDataType>::const_iterator data_type_it =
data_types_.find(data_type);
if (data_type_it != data_types_.end()) {
return (data_type_it->second);
}
return (OPT_UNKNOWN_TYPE);
}
template<typename T>
T OptionDefinition::DataTypeUtil::lexicalCastWithRangeCheck(const std::string& value_str) const {
// Lexical cast in case of our data types make sense only
// for uintX_t, intX_t and bool type.
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");
}
// We use the 64-bit value here because it has wider range than
// any other type we use here and it allows to detect out of
// bounds conditions e.g. negative value specified for uintX_t
// data type. Obviously if the value exceeds the limits of int64
// this function will not handle that properly.
int64_t result = 0;
try {
result = boost::lexical_cast<int64_t>(value_str);
} catch (const boost::bad_lexical_cast& ex) {
// Prepare error message here.
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());
}
// Perform range checks for integer values only (exclude bool values).
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,
const OptionDataType type,
OptionBuffer& buf) {
// We are going to write value given by value argument to the buffer.
// The actual type of the value is given by second argument. Check
// this argument to determine how to write this value to the buffer.
switch (type) {
case OPT_BINARY_TYPE:
OptionDataTypeUtil::writeBinary(value, buf);
return;
case OPT_BOOLEAN_TYPE:
// We encode the true value as 1 and false as 0 on 8 bits.
// That way we actually waste 7 bits but it seems to be the
// simpler way to encode boolean.
// @todo Consider if any other encode methods can be used.
OptionDataTypeUtil::writeBool(lexicalCastWithRangeCheck<bool>(value), buf);
return;
case OPT_INT8_TYPE:
OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<int8_t>(value),
buf);
return;
case OPT_INT16_TYPE:
OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<int16_t>(value),
buf);
return;
case OPT_INT32_TYPE:
OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<int32_t>(value),
buf);
return;
case OPT_UINT8_TYPE:
OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<uint8_t>(value),
buf);
return;
case OPT_UINT16_TYPE:
OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<uint16_t>(value),
buf);
return;
case OPT_UINT32_TYPE:
OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<uint32_t>(value),
buf);
return;
case OPT_IPV4_ADDRESS_TYPE:
case OPT_IPV6_ADDRESS_TYPE:
{
asiolink::IOAddress address(value);
if (!address.getAddress().is_v4() &&
!address.getAddress().is_v6()) {
isc_throw(BadDataTypeCast, "provided address " << address.toText()
<< " is not a valid "
<< (address.getAddress().is_v4() ? "IPv4" : "IPv6")
<< " address");
}
OptionDataTypeUtil::writeAddress(address, buf);
return;
}
case OPT_STRING_TYPE:
OptionDataTypeUtil::writeString(value, buf);
return;
case OPT_FQDN_TYPE:
{
// FQDN implementation is not terribly complicated but will require
// creation of some additional logic (maybe object) that will parse
// the fqdn into labels.
isc_throw(isc::NotImplemented, "write of FQDN record into option buffer"
" is not supported yet");
return;
}
default:
// We hit this point because invalid option data type has been specified
// This may be the case because 'empty' or 'record' data type has been
// specified. We don't throw exception here because it will be thrown
// at the exit point from this function.
;
}
isc_throw(isc::BadValue, "attempt to write invalid option data field type"
" into the option buffer: " << type);
}
OptionDefinition::OptionDefinition(const std::string& name,
const uint16_t code,
......@@ -184,7 +40,7 @@ OptionDefinition::OptionDefinition(const std::string& name,
// Data type is held as enum value by this class.
// Use the provided option type string to get the
// corresponding enum value.
type_ = DataTypeUtil::instance().getOptionDataType(type);
type_ = OptionDataTypeUtil::getDataType(type);
}
OptionDefinition::OptionDefinition(const std::string& name,
......@@ -199,7 +55,7 @@ OptionDefinition::OptionDefinition(const std::string& name,
void
OptionDefinition::addRecordField(const std::string& data_type_name) {
OptionDataType data_type = DataTypeUtil::instance().getOptionDataType(data_type_name);
OptionDataType data_type = OptionDataTypeUtil::getDataType(data_type_name);
addRecordField(data_type);
}
......@@ -291,10 +147,10 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
if (values.size() == 0) {
isc_throw(InvalidOptionValue, "no option value specified");
}
DataTypeUtil::instance().writeToBuffer(values[0], type_, buf);
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);
writeToBuffer(values[i], type_, buf);
}
} else if (type_ == OPT_RECORD_TYPE) {
const RecordFieldsCollection& records = getRecordFields();
......@@ -304,7 +160,7 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
<< " provided.");
}
for (size_t i = 0; i < records.size(); ++i) {
DataTypeUtil::instance().writeToBuffer(values[i], records[i], buf);
writeToBuffer(values[i], records[i], buf);
}
}
return (optionFactory(u, type, buf.begin(), buf.end()));
......@@ -410,6 +266,124 @@ OptionDefinition::haveIAAddr6Format() const {
return (haveIAx6Format(OPT_IPV6_ADDRESS_TYPE));
}
template<typename T>
T OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str) const {
// Lexical cast in case of our data types make sense only
// for uintX_t, intX_t and bool type.
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");
}
// We use the 64-bit value here because it has wider range than
// any other type we use here and it allows to detect out of
// bounds conditions e.g. negative value specified for uintX_t
// data type. Obviously if the value exceeds the limits of int64
// this function will not handle that properly.
int64_t result = 0;
try {
result = boost::lexical_cast<int64_t>(value_str);
} catch (const boost::bad_lexical_cast& ex) {
// Prepare error message here.
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());
}
// Perform range checks for integer values only (exclude bool values).
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::writeToBuffer(const std::string& value,
const OptionDataType type,
OptionBuffer& buf) const {
// We are going to write value given by value argument to the buffer.
// The actual type of the value is given by second argument. Check
// this argument to determine how to write this value to the buffer.
switch (type) {
case OPT_BINARY_TYPE:
OptionDataTypeUtil::writeBinary(value, buf);
return;
case OPT_BOOLEAN_TYPE:
// We encode the true value as 1 and false as 0 on 8 bits.
// That way we actually waste 7 bits but it seems to be the
// simpler way to encode boolean.
// @todo Consider if any other encode methods can be used.
OptionDataTypeUtil::writeBool(lexicalCastWithRangeCheck<bool>(value), buf);
return;
case OPT_INT8_TYPE:
OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<int8_t>(value),
buf);
return;
case OPT_INT16_TYPE:
OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<int16_t>(value),
buf);
return;
case OPT_INT32_TYPE:
OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<int32_t>(value),
buf);
return;
case OPT_UINT8_TYPE:
OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<uint8_t>(value),
buf);
return;
case OPT_UINT16_TYPE:
OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<uint16_t>(value),
buf);
return;
case OPT_UINT32_TYPE:
OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<uint32_t>(value),
buf);
return;
case OPT_IPV4_ADDRESS_TYPE:
case OPT_IPV6_ADDRESS_TYPE:
{
asiolink::IOAddress address(value);
if (!address.getAddress().is_v4() &&
!address.getAddress().is_v6()) {
isc_throw(BadDataTypeCast, "provided address " << address.toText()
<< " is not a valid "
<< (address.getAddress().is_v4() ? "IPv4" : "IPv6")
<< " address");
}
OptionDataTypeUtil::writeAddress(address, buf);
return;
}
case OPT_STRING_TYPE:
OptionDataTypeUtil::writeString(value, buf);
return;
case OPT_FQDN_TYPE:
{
// FQDN implementation is not terribly complicated but will require
// creation of some additional logic (maybe object) that will parse
// the fqdn into labels.
isc_throw(isc::NotImplemented, "write of FQDN record into option buffer"
" is not supported yet");
return;
}
default:
// We hit this point because invalid option data type has been specified
// This may be the case because 'empty' or 'record' data type has been
// specified. We don't throw exception here because it will be thrown
// at the exit point from this function.
;
}
isc_throw(isc::BadValue, "attempt to write invalid option data field type"
" into the option buffer: " << type);
}
OptionPtr
OptionDefinition::factoryAddrList4(uint16_t type,
OptionBufferConstIter begin,
......
......@@ -131,83 +131,6 @@ public:
/// Const iterator for record data fields.
typedef std::vector<OptionDataType>::const_iterator RecordFieldsConstIter;
private:
/// @brief Utility class for operations on OptionDataTypes.
///
/// This class is implemented as the singleton because the list of
/// supported data types need only be loaded only once into memory as it
/// can persist for all option definitions.
///
/// @todo This class can be extended to return the string value
/// representing the data type from the enum value.
class DataTypeUtil {
public:
/// @brief Return the sole instance of this class.
///
/// @return instance of this class.
static DataTypeUtil& instance() {
static DataTypeUtil instance;
return (instance);
}
/// @brief Convert type given as string value to option data type.
///
/// @param data_type_name data type string.
///
/// @return option data type.
OptionDataType getOptionDataType(const std::string& data_type_name);
/// @brief Perform lexical cast of the value and validate its range.
///
/// This function performs lexical cast of a string value to integer
/// or boolean value and checks if the resulting value is within a
/// range of a target type. Note that range checks are not performed
/// on boolean values. The target type should be one of the supported
/// integer types or bool.
///
/// @param value_str input value given as string.
/// @tparam T target type for lexical cast.
///
/// @return cast value.
/// @throw BadDataTypeCast if cast was not successful.
template<typename T>
T lexicalCastWithRangeCheck(const std::string& value_str) const;
/// @brief Write the string value into the provided buffer.
///
/// This method writes the given value to the specified buffer.
/// The provided string value may represent data of different types.
/// The actual data type is specified with the second argument.
/// Based on a value of this argument, this function will first
/// try to cast the string value to the particular data type and
/// if it is successful it will store the data in the buffer
/// in a binary format.
///
/// @param value string representation of the value to be written.
/// @param type the actual data type to be stored.
/// @param [in, out] buf buffer where the value is to be stored.
///
/// @throw BadDataTypeCast if data write was unsuccessful.
void writeToBuffer(const std::string& value, const OptionDataType type,
OptionBuffer& buf);
private:
/// @brief Private constructor.
///
/// Constructor initializes the internal data structures, e.g.
/// mapping between data type name and the corresponding enum.
/// This constructor is private to ensure that exactly one
/// instance of this class can be created using \ref instance
/// function.
DataTypeUtil();
/// Map of data types, maps name of the type to enum value.
std::map<std::string, OptionDataType> data_types_;
};
public:
/// @brief Constructor.
///
/// @param name option name.
......@@ -471,6 +394,40 @@ private:
return (type == type_);
}
/// @brief Perform lexical cast of the value and validate its range.
///
/// This function performs lexical cast of a string value to integer
/// or boolean value and checks if the resulting value is within a
/// range of a target type. Note that range checks are not performed
/// on boolean values. The target type should be one of the supported
/// integer types or bool.
///
/// @param value_str input value given as string.
/// @tparam T target type for lexical cast.
///
/// @return cast value.
/// @throw BadDataTypeCast if cast was not successful.
template<typename T>
T lexicalCastWithRangeCheck(const std::string& value_str) const;
/// @brief Write the string value into the provided buffer.
///
/// This method writes the given value to the specified buffer.
/// The provided string value may represent data of different types.
/// The actual data type is specified with the second argument.
/// Based on a value of this argument, this function will first
/// try to cast the string value to the particular data type and
/// if it is successful it will store the data in the buffer
/// in a binary format.
///
/// @param value string representation of the value to be written.
/// @param type the actual data type to be stored.
/// @param [in, out] buf buffer where the value is to be stored.
///
/// @throw BadDataTypeCast if data write was unsuccessful.
void writeToBuffer(const std::string& value, const OptionDataType type,
OptionBuffer& buf) const;
/// @brief Sanity check universe value.