// Copyright (C) 2012 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 namespace isc { namespace dhcp { OptionCustom::OptionCustom(const OptionDefinition& def, Universe u, const OptionBuffer& data) : Option(u, def.getCode(), data.begin(), data.end()), definition_(def) { createBuffers(); } OptionCustom::OptionCustom(const OptionDefinition& def, Universe u, OptionBufferConstIter first, OptionBufferConstIter last) : Option(u, def.getCode(), first, last), definition_(def) { createBuffers(); } void OptionCustom::checkIndex(const uint32_t index) const { if (index >= buffers_.size()) { isc_throw(isc::OutOfRange, "specified data field index " << index << " is out of rangex."); } } void OptionCustom::createBuffers() { // Check that the option definition is correct as we are going // to use it to split the data_ buffer into set of sub buffers. definition_.validate(); std::vector buffers; OptionBuffer::iterator data = data_.begin(); OptionDataType data_type = definition_.getType(); if (data_type == OPT_RECORD_TYPE) { // An option comprises a record of data fields. We need to // get types of these data fields to allocate enough space // for each buffer. const OptionDefinition::RecordFieldsCollection& fields = definition_.getRecordFields(); // Go over all data fields within a record. for (OptionDefinition::RecordFieldsConstIter field = fields.begin(); field != fields.end(); ++field) { // For fixed-size data type such as boolean, integer, even // IP address we can use the utility function to get the required // buffer size. int data_size = OptionDataTypeUtil::getDataTypeLen(*field); // For variable size types (such as string) the function above // will return 0 so we need to do a runtime check. Since variable // length data fields may be laid only at the end of an option we // consume the rest of this option. Note that validate() function // in OptionDefinition object should have checked whether the // data fields layout is correct (that the variable string fields // are laid at the end). if (data_size == 0) { data_size = std::distance(data, data_.end()); if (data_size == 0) { // If we reached the end of buffer we assume that this option is // truncated because there is no remaining data to initialize // an option field. if (data_size == 0) { isc_throw(OutOfRange, "option buffer truncated"); } } } else { // Our data field requires that there is a certain chunk of // data left in the buffer. If not, option is truncated. if (std::distance(data, data_.end()) < data_size) { isc_throw(OutOfRange, "option buffer truncated"); } } // Store the created buffer. buffers.push_back(OptionBuffer(data, data + data_size)); // Proceed to the next data field. data += data_size; } } else if (data_type != OPT_EMPTY_TYPE) { // If data_type value is other than OPT_RECORD_TYPE, our option is // empty (have no data at all) or it comprises one or more // data fields of the same type. The type of those fields // is held in the data_type variable so let's use it to determine // a size of buffers. int data_size = OptionDataTypeUtil::getDataTypeLen(data_type); // The check below will fail if the input buffer is too short // for the data size being held by this option. // Note that data_size returned by getDataTypeLen may be zero // if variable length data is being held by the option but // this will not cause this check to throw exception. if (std::distance(data, data_.end()) < data_size) { isc_throw(OutOfRange, "option buffer truncated"); } // For an array of values we are taking different path because // we have to handle multiple buffers. if (definition_.getArrayType()) { // We don't perform other checks for data types that can't be // used together with array indicator such as strings, empty field // etc. This is because OptionDefinition::validate function should // have checked this already. Thus data_size must be greater than // zero. assert(data_size > 0); // Get equal chunks of data and store as collection of buffers. // Truncate any remaining part which length is not divisible by // data_size. Note that it is ok to truncate the data if and only // if the data buffer is long enough to keep at least one value. // This has been checked above already. do { buffers.push_back(OptionBuffer(data, data + data_size)); data += data_size; } while (std::distance(data, data_.end()) >= data_size); } else { // For non-arrays the data_size can be zero because // getDataTypeLen returns zero for variable size data types // such as strings. Simply take whole buffer. if (data_size == 0) { data_size = std::distance(data, data_.end()); } if (data_size > 0) { buffers.push_back(OptionBuffer(data, data + data_size)); } else { isc_throw(OutOfRange, "option buffer truncated"); } } } // If everything went ok we can replace old buffer set with new ones. std::swap(buffers_, buffers); } std::string OptionCustom::dataFieldToText(const OptionDataType data_type, const uint32_t index) const { std::ostringstream text; // Get the value of the data field. switch (data_type) { case OPT_BINARY_TYPE: text << util::encode::encodeHex(readBinary(index)); break; case OPT_BOOLEAN_TYPE: text << (readBoolean(index) ? "true" : "false"); break; case OPT_INT8_TYPE: text << readInteger(index); break; case OPT_INT16_TYPE: text << readInteger(index); break; case OPT_INT32_TYPE: text << readInteger(index); break; case OPT_UINT8_TYPE: text << readInteger(index); break; case OPT_UINT16_TYPE: text << readInteger(index); break; case OPT_UINT32_TYPE: text << readInteger(index); break; case OPT_IPV4_ADDRESS_TYPE: case OPT_IPV6_ADDRESS_TYPE: text << readAddress(index).toText(); break; case OPT_STRING_TYPE: text << readString(index); break; default: ; } // Append data field type in brackets. text << " ( " << OptionDataTypeUtil::getDataTypeName(data_type) << " ) "; return (text.str()); } void OptionCustom::pack4(isc::util::OutputBuffer& buf) { if (len() > 255) { isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " value is too high. At most 255 is supported."); } buf.writeUint8(type_); buf.writeUint8(len() - getHeaderLen()); // Write data from buffers. for (std::vector::const_iterator it = buffers_.begin(); it != buffers_.end(); ++it) { // In theory the createBuffers function should have taken // care that there are no empty buffers added to the // collection but it is almost always good to make sure. if (!it->empty()) { buf.writeData(&(*it)[0], it->size()); } } // Write suboptions. packOptions(buf); } void OptionCustom::pack6(isc::util::OutputBuffer& buf) { buf.writeUint16(type_); buf.writeUint16(len() - getHeaderLen()); // Write data from buffers. for (std::vector::const_iterator it = buffers_.begin(); it != buffers_.end(); ++it) { if (!it->empty()) { buf.writeData(&(*it)[0], it->size()); } } packOptions(buf); } asiolink::IOAddress OptionCustom::readAddress(const uint32_t index) const { checkIndex(index); // The address being read can be either IPv4 or IPv6. The decision // is made based on the buffer length. If it holds 4 bytes it is IPv4 // address, if it holds 16 bytes it is IPv6. if (buffers_[index].size() == asiolink::V4ADDRESS_LEN) { return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET)); } else if (buffers_[index].size() == asiolink::V6ADDRESS_LEN) { return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET6)); } else { isc_throw(BadDataTypeCast, "unable to read data from the buffer as" << " IP address. Invalid buffer length " << buffers_[index].size()); } } const OptionBuffer& OptionCustom::readBinary(const uint32_t index) const { checkIndex(index); return (buffers_[index]); } bool OptionCustom::readBoolean(const uint32_t index) const { checkIndex(index); return (OptionDataTypeUtil::readBool(buffers_[index])); } std::string OptionCustom::readString(const uint32_t index) const { checkIndex(index); return (OptionDataTypeUtil::readString(buffers_[index])); } void OptionCustom::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) { data_ = OptionBuffer(begin, end); // Chop the buffer stored in data_ into set of sub buffers. createBuffers(); } uint16_t OptionCustom::len() { // The length of the option is a sum of option header ... int length = getHeaderLen(); // ... lengths of all buffers that hold option data ... for (std::vector::const_iterator buf = buffers_.begin(); buf != buffers_.end(); ++buf) { length += buf->size(); } // ... and lengths of all suboptions for (OptionCustom::OptionCollection::iterator it = options_.begin(); it != options_.end(); ++it) { length += (*it).second->len(); } return (length); } void OptionCustom::setData(const OptionBufferConstIter first, const OptionBufferConstIter last) { // We will copy entire option buffer, so we have to resize data_. data_.resize(std::distance(first, last)); std::copy(first, last, data_.begin()); // Chop the data_ buffer into set of buffers that represent // option fields data. createBuffers(); } std::string OptionCustom::toText(int indent) { std::stringstream tmp; for (int i = 0; i < indent; ++i) tmp << " "; tmp << "type=" << type_ << ", len=" << len()-getHeaderLen() << ", data fields:" << std::endl; OptionDataType data_type = definition_.getType(); if (data_type == OPT_RECORD_TYPE) { const OptionDefinition::RecordFieldsCollection& fields = definition_.getRecordFields(); // For record types we iterate over fields defined in // option definition and match the appropriate buffer // with them. for (OptionDefinition::RecordFieldsConstIter field = fields.begin(); field != fields.end(); ++field) { for (int j = 0; j < indent + 2; ++j) { tmp << " "; } tmp << "#" << std::distance(fields.begin(), field) << " " << dataFieldToText(*field, std::distance(fields.begin(), field)) << std::endl; } } else { // For non-record types we iterate over all buffers // and print the data type set globally for an option // definition. We take the same code path for arrays // and non-arrays as they only differ in such a way that // non-arrays have just single data field. for (unsigned int i = 0; i < getDataFieldsNum(); ++i) { for (int j = 0; j < indent + 2; ++j) { tmp << " "; } tmp << "#" << i << " " << dataFieldToText(definition_.getType(), i) << std::endl; } } // print suboptions for (OptionCollection::const_iterator opt = options_.begin(); opt != options_.end(); ++opt) { tmp << (*opt).second->toText(indent+2); } return tmp.str(); } } // end of isc::dhcp namespace } // end of isc namespace