--- /dev/null
+// Copyright (C) 2013 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 <dhcp/dhcp4.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dns/labelsequence.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+#include <util/strutil.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+class Option4ClientFqdnImpl {
+public:
+ uint8_t flags_;
+ Option4ClientFqdn::Rcode rcode1_;
+ Option4ClientFqdn::Rcode rcode2_;
+ boost::shared_ptr<isc::dns::Name> domain_name_;
+ Option4ClientFqdn::DomainNameType domain_name_type_;
+
+ Option4ClientFqdnImpl(const uint8_t flag,
+ const Option4ClientFqdn::Rcode& rcode,
+ const std::string& domain_name,
+ const Option4ClientFqdn::DomainNameType name_type);
+
+ Option4ClientFqdnImpl(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source);
+
+ Option4ClientFqdnImpl& operator=(const Option4ClientFqdnImpl& source);
+
+ void setDomainName(const std::string& domain_name,
+ const Option4ClientFqdn::DomainNameType name_type);
+
+ static void checkFlags(const uint8_t flags);
+
+ void parseWireData(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+};
+
+Option4ClientFqdnImpl::
+Option4ClientFqdnImpl(const uint8_t flag,
+ const Option4ClientFqdn::Rcode& rcode,
+ const std::string& domain_name,
+ const Option4ClientFqdn::DomainNameType name_type)
+ : flags_(flag),
+ rcode1_(rcode),
+ rcode2_(rcode),
+ domain_name_(),
+ domain_name_type_(name_type) {
+
+ // Check if flags are correct.
+ checkFlags(flags_);
+ // Set domain name. It may throw an exception if domain name has wrong
+ // format.
+ setDomainName(domain_name, name_type);
+}
+
+Option4ClientFqdnImpl::Option4ClientFqdnImpl(OptionBufferConstIter first,
+ OptionBufferConstIter last)
+ : rcode1_(Option4ClientFqdn::RCODE_CLIENT()),
+ rcode2_(Option4ClientFqdn::RCODE_CLIENT()) {
+ parseWireData(first, last);
+ // Verify that flags value was correct.
+ checkFlags(flags_);
+}
+
+Option4ClientFqdnImpl::
+Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source)
+ : flags_(source.flags_),
+ rcode1_(source.rcode1_),
+ rcode2_(source.rcode2_),
+ domain_name_(),
+ domain_name_type_(source.domain_name_type_) {
+ if (source.domain_name_) {
+ domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+ }
+}
+
+Option4ClientFqdnImpl&
+Option4ClientFqdnImpl::operator=(const Option4ClientFqdnImpl& source) {
+ domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+
+ // Assignment is exception safe.
+ flags_ = source.flags_;
+ rcode1_ = source.rcode1_;
+ rcode2_ = source.rcode2_;
+ domain_name_type_ = source.domain_name_type_;
+
+ return (*this);
+}
+
+void
+Option4ClientFqdnImpl::
+setDomainName(const std::string& domain_name,
+ const Option4ClientFqdn::DomainNameType name_type) {
+ // domain-name must be trimmed. Otherwise, string comprising spaces only
+ // would be treated as a fully qualified name.
+ std::string name = isc::util::str::trim(domain_name);
+ if (name.empty()) {
+ if (name_type == Option4ClientFqdn::FULL) {
+ isc_throw(InvalidOption4ClientFqdnDomainName,
+ "fully qualified domain-name must not be empty"
+ << " when setting new domain-name for DHCPv4 Client"
+ << " FQDN Option");
+ }
+ // The special case when domain-name is empty is marked by setting the
+ // pointer to the domain-name object to NULL.
+ domain_name_.reset();
+
+ } else {
+ try {
+ domain_name_.reset(new isc::dns::Name(name));
+ domain_name_type_ = name_type;
+
+ } catch (const Exception& ex) {
+ isc_throw(InvalidOption4ClientFqdnDomainName,
+ "invalid domain-name value '"
+ << domain_name << "' when setting new domain-name for"
+ << " DHCPv4 Client FQDN Option");
+
+ }
+ }
+}
+
+void
+Option4ClientFqdnImpl::checkFlags(const uint8_t flags) {
+ // The Must Be Zero (MBZ) bits must not be set.
+ if ((flags & ~Option4ClientFqdn::FLAG_MASK) != 0) {
+ isc_throw(InvalidOption4ClientFqdnFlags,
+ "invalid DHCPv4 Client FQDN Option flags: 0x"
+ << std::hex << static_cast<int>(flags) << std::dec);
+ }
+
+ // According to RFC 4702, section 2.1. if the N bit is 1, the S bit
+ // MUST be 0. Checking it here.
+ if ((flags & (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S))
+ == (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S)) {
+ isc_throw(InvalidOption4ClientFqdnFlags,
+ "both N and S flag of the DHCPv4 Client FQDN Option are set."
+ << " According to RFC 4702, if the N bit is 1 the S bit"
+ << " MUST be 0");
+ }
+}
+
+void
+Option4ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+
+ // Buffer must comprise at least one byte with the flags.
+ // The domain-name may be empty.
+ if (std::distance(first, last) < Option4ClientFqdn::FIXED_FIELDS_LEN) {
+ isc_throw(OutOfRange, "DHCPv4 Client FQDN Option ("
+ << DHO_FQDN << ") is truncated");
+ }
+
+ // Parse flags
+ flags_ = *(first++);
+
+ // Parse RCODE1 and RCODE2.
+ rcode1_ = Option4ClientFqdn::Rcode(*(first++));
+ rcode2_ = Option4ClientFqdn::Rcode(*(first++));
+
+ // Parse domain-name if any.
+ if (std::distance(first, last) > 0) {
+ // The FQDN may comprise a partial domain-name. In this case it lacks
+ // terminating 0. If this is the case, we will need to add zero at
+ // the end because Name object constructor requires it.
+ if (*(last - 1) != 0) {
+ // Create temporary buffer and add terminating zero.
+ OptionBuffer buf(first, last);
+ buf.push_back(0);
+ // Reset domain name.
+ isc::util::InputBuffer name_buf(&buf[0], buf.size());
+ domain_name_.reset(new isc::dns::Name(name_buf));
+ // Terminating zero was missing, so set the domain-name type
+ // to partial.
+ domain_name_type_ = Option4ClientFqdn::PARTIAL;
+ } else {
+ // We are dealing with fully qualified domain name so there is
+ // no need to add terminating zero. Simply pass the buffer to
+ // Name object constructor.
+ isc::util::InputBuffer name_buf(&(*first),
+ std::distance(first, last));
+ domain_name_.reset(new isc::dns::Name(name_buf));
+ // Set the domain-type to fully qualified domain name.
+ domain_name_type_ = Option4ClientFqdn::FULL;
+ }
+ }
+}
+
+ Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, const Rcode& rcode)
+ : Option(Option::V4, DHO_FQDN),
+ impl_(new Option4ClientFqdnImpl(flag, rcode, "", PARTIAL)) {
+}
+
+Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag,
+ const Rcode& rcode,
+ const std::string& domain_name,
+ const DomainNameType domain_name_type)
+ : Option(Option::V4, DHO_FQDN),
+ impl_(new Option4ClientFqdnImpl(flag, rcode, domain_name,
+ domain_name_type)) {
+}
+
+Option4ClientFqdn::Option4ClientFqdn(OptionBufferConstIter first,
+ OptionBufferConstIter last)
+ : Option(Option::V4, DHO_FQDN, first, last),
+ impl_(new Option4ClientFqdnImpl(first, last)) {
+}
+
+Option4ClientFqdn::~Option4ClientFqdn() {
+ delete(impl_);
+}
+
+Option4ClientFqdn::Option4ClientFqdn(const Option4ClientFqdn& source)
+ : Option(source),
+ impl_(new Option4ClientFqdnImpl(*source.impl_)) {
+}
+
+Option4ClientFqdn&
+Option4ClientFqdn::operator=(const Option4ClientFqdn& source) {
+ Option4ClientFqdnImpl* old_impl = impl_;
+ impl_ = new Option4ClientFqdnImpl(*source.impl_);
+ delete(old_impl);
+ return (*this);
+}
+
+bool
+Option4ClientFqdn::getFlag(const Flag flag) const {
+ // Caller should query for one of the: E, N, S or O flags. However, there
+ // are valid enumerator values which should not be accepted by this function.
+ // For example a value of 0x3 is valid (because it belongs to the range between the
+ // lowest and highest enumerator). The value 0x3 represents two flags:
+ // S and O and would cause ambiguity. Therefore, we selectively check
+ // that the flag is equal to one of the explicit enumerator values. If
+ // not, throw an exception.
+ if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N && flag != FLAG_E) {
+ isc_throw(InvalidOption4ClientFqdnFlags, "invalid DHCPv4 Client FQDN"
+ << " Option flag specified, expected E, N, S or O");
+ }
+
+ return ((impl_->flags_ & flag) != 0);
+}
+
+void
+Option4ClientFqdn::setFlag(const Flag flag, const bool set_flag) {
+ // Check that flag is in range between 0x1 and 0x7. Note that this
+ // allows to set or clear multiple flags concurrently.
+ if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
+ isc_throw(InvalidOption4ClientFqdnFlags, "invalid DHCPv4 Client FQDN"
+ << " Option flag " << std::hex
+ << static_cast<int>(flag) << std::dec
+ << "is being set. Expected combination of E, N, S and O");
+ }
+
+ // Copy the current flags into local variable. That way we will be able
+ // to test new flags settings before applying them.
+ uint8_t new_flag = impl_->flags_;
+ if (set_flag) {
+ new_flag |= flag;
+ } else {
+ new_flag &= ~flag;
+ }
+
+ // Check new flags. If they are valid, apply them.
+ Option4ClientFqdnImpl::checkFlags(new_flag);
+ impl_->flags_ = new_flag;
+}
+
+void
+Option4ClientFqdn::setRcode(const Rcode& rcode) {
+ impl_->rcode1_ = rcode;
+ impl_->rcode2_ = rcode;
+}
+
+void
+Option4ClientFqdn::resetFlags() {
+ impl_->flags_ = 0;
+}
+
+std::string
+Option4ClientFqdn::getDomainName() const {
+ if (impl_->domain_name_) {
+ return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
+ PARTIAL));
+ }
+ // If an object holding domain-name is NULL it means that the domain-name
+ // is empty.
+ return ("");
+}
+
+void
+Option4ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const {
+ // Domain name, encoded as a set of labels.
+ isc::dns::LabelSequence labels(*impl_->domain_name_);
+ if (labels.getDataLength() > 0) {
+ size_t read_len = 0;
+ const uint8_t* data = labels.getData(&read_len);
+ if (impl_->domain_name_type_ == PARTIAL) {
+ --read_len;
+ }
+ buf.writeData(data, read_len);
+ }
+}
+
+void
+Option4ClientFqdn::setDomainName(const std::string& domain_name,
+ const DomainNameType domain_name_type) {
+ impl_->setDomainName(domain_name, domain_name_type);
+}
+
+void
+Option4ClientFqdn::resetDomainName() {
+ setDomainName("", PARTIAL);
+}
+
+Option4ClientFqdn::DomainNameType
+Option4ClientFqdn::getDomainNameType() const {
+ return (impl_->domain_name_type_);
+}
+
+void
+Option4ClientFqdn::pack(isc::util::OutputBuffer& buf) {
+ // Header = option code and length.
+ packHeader(buf);
+ // Flags field.
+ buf.writeUint8(impl_->flags_);
+ // RCODE1 and RCODE2
+ buf.writeUint8(impl_->rcode1_.getCode());
+ buf.writeUint8(impl_->rcode2_.getCode());
+ // Domain name.
+ packDomainName(buf);
+}
+
+void
+Option4ClientFqdn::unpack(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ setData(first, last);
+ impl_->parseWireData(first, last);
+}
+
+std::string
+Option4ClientFqdn::toText(int indent) {
+ std::ostringstream stream;
+ std::string in(indent, ' '); // base indentation
+ std::string in_add(2, ' '); // second-level indentation is 2 spaces long
+ stream << in << "type=" << type_ << "(CLIENT_FQDN)" << std::endl
+ << in << "flags:" << std::endl
+ << in << in_add << "N=" << (getFlag(FLAG_N) ? "1" : "0") << std::endl
+ << in << in_add << "E=" << (getFlag(FLAG_E) ? "1" : "0") << std::endl
+ << in << in_add << "O=" << (getFlag(FLAG_O) ? "1" : "0") << std::endl
+ << in << in_add << "S=" << (getFlag(FLAG_S) ? "1" : "0") << std::endl
+ << in << "domain-name='" << getDomainName() << "' ("
+ << (getDomainNameType() == PARTIAL ? "partial" : "full")
+ << ")" << std::endl;
+
+ return (stream.str());
+}
+
+uint16_t
+Option4ClientFqdn::len() {
+ // If domain name is partial, the NULL terminating character
+ // is not included and the option length have to be adjusted.
+ uint16_t domain_name_length = impl_->domain_name_type_ == FULL ?
+ impl_->domain_name_->getLength() : impl_->domain_name_->getLength() - 1;
+
+ return (getHeaderLen() + FIXED_FIELDS_LEN + domain_name_length);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
--- /dev/null
+// Copyright (C) 2013 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 OPTION4_CLIENT_FQDN_H
+#define OPTION4_CLIENT_FQDN_H
+
+#include <dhcp/option.h>
+#include <dns/name.h>
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when invalid flags have been specified for
+/// DHCPv4 Client FQDN %Option.
+class InvalidOption4ClientFqdnFlags : public Exception {
+public:
+ InvalidOption4ClientFqdnFlags(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception thrown when invalid domain name is specified.
+class InvalidOption4ClientFqdnDomainName : public Exception {
+public:
+ InvalidOption4ClientFqdnDomainName(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// Forward declaration to implementation of @c Option4ClientFqdn class.
+class Option4ClientFqdnImpl;
+
+/// @brief Represents DHCPv4 Client FQDN %Option (code 81).
+///
+/// This option has been defined in the RFC 4702 and it has a following
+/// structure:
+/// - Code (1 octet) - option code (always equal to 81).
+/// - Len (1 octet) - a length of the option.
+/// - Flags (1 octet) - a field carrying "NEOS" flags described below.
+/// - RCODE1 (1 octet) - deprecated field which should be set to 0 by the client
+/// and set to 255 by the server.
+/// - RCODE2 (1 octet) - deprecated, should be used in the same way as RCODE1.
+/// - Domain Name - variable length field comprising partial or fully qualified
+/// domain name.
+///
+/// The flags field has the following structure:
+/// @code
+/// 0 1 2 3 4 5 6 7
+/// +-+-+-+-+-+-+-+-+
+/// | MBZ |N|E|O|S|
+/// +-+-+-+-+-+-+-+-+
+/// @endcode
+/// where:
+/// - N flag specifies whether server should (0) or should not (1) perform DNS
+/// Update,
+/// - E flag indicates encoding of the Domain Name field. If this flag is set to 1
+/// it indicates canonical wire format without compression. 0 indicates the deprecated
+/// ASCII format.
+/// - O flag is set by the server to indicate that it has overridden client's
+/// preference set with the S bit.
+/// - S flag specifies whether server should (1) or should not (0) perform
+/// forward (FQDN-to-address) updates.
+///
+/// This class exposes a set of functions to modify flags and check their
+/// correctness.
+///
+/// Domain names being carried by DHCPv4 Client Fqdn %Option can be fully
+/// qualified or partial. Partial domain names are encoded similar to the
+/// fully qualified domain names, except that they lack terminating zero
+/// at the end of their wire representation. It is also accepted to create an
+/// instance of this option which has empty domain-name. Clients use empty
+/// domain-names to indicate that server should generate complete fully
+/// qualified domain-name.
+///
+/// RFC 4702 mandates that the DHCP client sets RCODE1 and RCODE2 to 0 and that
+/// server sets them to 255. This class allows to set the value for these
+/// fields and both fields are always set to the same value. There is no way
+/// to set them separately (e.g. set different value for RCODE1 and RCODE2).
+/// However, there are no use cases which would require it.
+///
+/// <b>Design choice:</b> This class uses pimpl idiom to separate the interface
+/// from implementation specifics. Implementations may use different approaches
+/// to handle domain names (mostly validation of the domain-names). The existing
+/// @c isc::dns::Name class is a natural (and the simplest) choice to handle
+/// domain-names. Use of this class however, implies that libdhcp must be linked
+/// with libdns. At some point these libraries may need to be separated, i.e. to
+/// support compilation and use of standalone DHCP server. This will require
+/// that the part of implementation which deals with domain-names is modified to
+/// not use classes from libdns. These changes will be transparent for this
+/// interface.
+class Option4ClientFqdn : public Option {
+public:
+
+ /// @brief Enumeration holding different flags used in the Client
+ /// FQDN %Option.
+ enum Flag {
+ FLAG_S = 0x01,
+ FLAG_O = 0x02,
+ FLAG_E = 0x04,
+ FLAG_N = 0x08
+ };
+
+ /// @brief Represents the value of one of the RCODE1 or RCODE2 fields.
+ ///
+ /// Typically, RCODE values are set to 255 by the server and to 0 by the
+ /// clients (as per RFC 4702).
+ class Rcode {
+ public:
+ Rcode(const uint8_t rcode)
+ : rcode_(rcode) { }
+
+ /// @brief Returns the value of the RCODE.
+ ///
+ /// Returned value can be directly used to create the on-wire format
+ /// of the DHCPv4 Client FQDN %Option.
+ uint8_t getCode() const {
+ return (rcode_);
+ }
+
+ private:
+ uint8_t rcode_;
+ };
+
+
+ /// @brief Type of the domain-name: partial or full.
+ enum DomainNameType {
+ PARTIAL,
+ FULL
+ };
+
+ /// @brief Mask which zeroes MBZ flag bits.
+ static const uint8_t FLAG_MASK = 0xF;
+
+ /// @brief The size of the fixed fields within DHCPv4 Client Fqdn %Option.
+ ///
+ /// The fixed fields are:
+ /// - Flags
+ /// - RCODE1
+ /// - RCODE2
+ static const uint16_t FIXED_FIELDS_LEN = 3;
+
+ /// @brief Constructor, creates option instance using flags and domain name.
+ ///
+ /// This constructor is used to create instance of the option which will be
+ /// included in outgoing messages.
+ ///
+ /// @param flag a combination of flags to be stored in flags field.
+ /// @param rcode @c Rcode object representing a value for RCODE1 and RCODE2
+ /// fields of the option. Both fields are assigned the same value encapsulated
+ /// by the parameter.
+ /// @param domain_name a name to be stored in the domain-name field.
+ /// @param partial_domain_name indicates if the domain name is partial
+ /// (if true) or full (false).
+ explicit Option4ClientFqdn(const uint8_t flag,
+ const Rcode& rcode,
+ const std::string& domain_name,
+ const DomainNameType domain_name_type = FULL);
+
+ /// @brief Constructor, creates option instance with empty domain name.
+ ///
+ /// This constructor creates an instance of the option with empty
+ /// domain-name. This domain-name is marked partial.
+ ///
+ /// @param flag a combination of flags to be stored in flags field.
+ /// @param rcode @c Rcode object representing a value for RCODE1 and RCODE2
+ /// fields. Both fields are assigned the same value encapsulated by this
+ /// parameter.
+ Option4ClientFqdn(const uint8_t flag, const Rcode& rcode);
+
+ /// @brief Constructor, creates an option instance from part of the buffer.
+ ///
+ /// This constructor is mainly used to parse options in the received
+ /// messages. Function parameters specify buffer bounds from which the
+ /// option should be created. The size of the buffer chunk, specified by
+ /// the constructor's parameters should be equal or larger than the size
+ /// of the option. Otherwise, constructor will throw an exception.
+ ///
+ /// @param first the lower bound of the buffer to create option from.
+ /// @param last the upper bound of the buffer to create option from.
+ explicit Option4ClientFqdn(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Copy constructor
+ Option4ClientFqdn(const Option4ClientFqdn& source);
+
+ /// @brief Destructor
+ virtual ~Option4ClientFqdn();
+
+ /// @brief Assignment operator
+ Option4ClientFqdn& operator=(const Option4ClientFqdn& source);
+
+ /// @brief Checks if the specified flag of the DHCPv4 Client FQDN %Option
+ /// is set.
+ ///
+ /// @param flag an enum value specifying the flag to be checked.
+ ///
+ /// @return true if the bit of the specified flag is set, false otherwise.
+ bool getFlag(const Flag flag) const;
+
+ /// @brief Modifies the value of the specified DHCPv4 Client Fqdn %Option
+ /// flag.
+ ///
+ /// @param flag an enum value specifying the flag to be modified.
+ /// @param set a boolean value which indicates whether flag should be
+ /// set (true), or cleared (false).
+ void setFlag(const Flag flag, const bool set);
+
+ /// @brief Sets the flag field value to 0.
+ void resetFlags();
+
+ /// @brief Set Rcode value.
+ ///
+ /// @param rcode An @c Rcode object representing value of RCODE1 and RCODE2.
+ /// Both fields are assigned the same value.
+ void setRcode(const Rcode& rcode);
+
+ /// @brief Returns the domain-name in the text format.
+ ///
+ /// If domain-name is partial, it lacks the dot at the end (e.g. myhost).
+ /// If domain-name is fully qualified, it has the dot at the end (e.g.
+ /// myhost.example.com.).
+ ///
+ /// @return domain-name in the text format.
+ std::string getDomainName() const;
+
+ /// @brief Writes domain-name in the wire format into a buffer.
+ ///
+ /// The data being written are appended at the end of the buffer.
+ ///
+ /// @param [out] buf buffer where domain-name will be written.
+ void packDomainName(isc::util::OutputBuffer& buf) const;
+
+ /// @brief Set new domain-name.
+ ///
+ /// @param domain_name domain name field value in the text format.
+ /// @param domain_name_type type of the domain name: partial or fully
+ /// qualified.
+ void setDomainName(const std::string& domain_name,
+ const DomainNameType domain_name_type);
+
+ /// @brief Set empty domain-name.
+ ///
+ /// This function is equivalent to @c Option6ClientFqdn::setDomainName
+ /// with empty partial domain-name. It is exception safe.
+ void resetDomainName();
+
+ /// @brief Returns enumerator value which indicates whether domain-name is
+ /// partial or full.
+ ///
+ /// @return An enumerator value indicating whether domain-name is partial
+ /// or full.
+ DomainNameType getDomainNameType() const;
+
+ /// @brief Writes option in the wire format into a buffer.
+ ///
+ /// @param [out] buf output buffer where option data will be stored.
+ virtual void pack(isc::util::OutputBuffer& buf);
+
+ /// @brief Parses option from the received buffer.
+ ///
+ /// Method creates an instance of the DHCPv4 Client FQDN %Option from the
+ /// wire format. Parameters specify the bounds of the buffer to read option
+ /// data from. The size of the buffer limited by the specified parameters
+ /// should be equal or larger than size of the option (including its
+ /// header). Otherwise exception will be thrown.
+ ///
+ /// @param first lower bound of the buffer to parse option from.
+ /// @param last upper bound of the buffer to parse option from.
+ virtual void unpack(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Returns string representation of the option.
+ ///
+ /// The string returned by the method comprises the bit value of each
+ /// option flag and the domain-name.
+ ///
+ /// @param indent number of spaces before printed text.
+ ///
+ /// @return string with text representation.
+ virtual std::string toText(int indent = 0);
+
+ /// @brief Returns length of the complete option (data length +
+ /// DHCPv4 option header).
+ ///
+ /// @return length of the option.
+ virtual uint16_t len();
+
+ ///
+ /// @name Well known Rcode declarations for DHCPv4 Client FQDN %Option
+ ///
+ //@{
+ /// @brief Rcode being set by the server.
+ inline static const Rcode& RCODE_SERVER() {
+ static Rcode rcode(255);
+ return (rcode);
+ }
+
+ /// @brief Rcode being set by the client.
+ inline static const Rcode& RCODE_CLIENT() {
+ static Rcode rcode(0);
+ return (rcode);
+ }
+ //@}
+
+private:
+
+ /// @brief A pointer to the implementation.
+ Option4ClientFqdnImpl* impl_;
+};
+
+/// A pointer to the @c Option4ClientFqdn object.
+typedef boost::shared_ptr<Option4ClientFqdn> Option4ClientFqdnPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION4_CLIENT_FQDN_H
--- /dev/null
+// Copyright (C) 2013 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 <config.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dns/name.h>
+#include <util/buffer.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc;
+using namespace isc::dhcp;
+
+// Redefine option flags here as uint8_t. They will be used to initialize
+// elements of the arrays that are used in tests below. Note that use of
+// enum values defined in Option4ClientFqdn class may cause compilation issues
+// during uint8_t arrays initialization. That is because the underlying
+// integral type used to represent enums is larger than one byte.
+const uint8_t FLAG_S = 0x01;
+const uint8_t FLAG_O = 0x02;
+const uint8_t FLAG_E = 0x04;
+const uint8_t FLAG_N = 0x08;
+
+// This test verifies that constructor accepts empty partial domain-name but
+// does not accept empty fully qualified domain name.
+TEST(Option4ClientFqdnTest, constructEmptyName) {
+ // Create an instance of the source option.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ // Constructor should not accept empty fully qualified domain name.
+ EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "",
+ Option4ClientFqdn::FULL),
+ InvalidOption4ClientFqdnDomainName);
+ // This check is similar to previous one, but using domain-name comprising
+ // a single space character. This should be treated as empty domain-name.
+ EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ " ",
+ Option4ClientFqdn::FULL),
+ InvalidOption4ClientFqdnDomainName);
+
+ // Try different constructor.
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O,
+ Option4ClientFqdn::RCODE_SERVER()))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that copy constructor makes a copy of the option and
+// the source option instance can be deleted (both instances don't share
+// any resources).
+TEST(Option4ClientFqdnTest, copyConstruct) {
+ // Create an instance of the source option.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+
+ // Use copy constructor to create a second instance of the option.
+ boost::scoped_ptr<Option4ClientFqdn> option_copy;
+ ASSERT_NO_THROW(
+ option_copy.reset(new Option4ClientFqdn(*option))
+ );
+ ASSERT_TRUE(option_copy);
+
+ // Copy construction should result in no shared resources between
+ // two objects. In particular, pointer to implementation should not
+ // be shared. Thus, we can release the source object now.
+ option.reset();
+
+ // Verify that all parameters have been copied to the target object.
+ EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example.com.", option_copy->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option_copy->getDomainNameType());
+
+ // Do another test with different parameters to verify that parameters
+ // change when copied object is changed.
+
+ // Create an option with different parameters.
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "example",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+
+ // Call copy-constructor to copy the option.
+ ASSERT_NO_THROW(
+ option_copy.reset(new Option4ClientFqdn(*option))
+ );
+ ASSERT_TRUE(option_copy);
+
+ option.reset();
+
+ EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("example", option_copy->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option_copy->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWire) {
+ const uint8_t in_data[] = {
+ FLAG_S, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that truncated option is rejected.
+TEST(Option4ClientFqdnTest, constructFromWireTruncated) {
+ // Empty buffer is invalid. It should be at least one octet long.
+ OptionBuffer in_buf;
+ for (uint8_t i = 0; i < 3; ++i) {
+ EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+ OutOfRange) << "Test of the truncated buffer failed for"
+ << " buffer length " << static_cast<int>(i);
+ in_buf.push_back(0);
+ }
+
+ // Buffer is now 3 bytes long, so it should not fail now.
+ EXPECT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()));
+}
+
+// This test verifies that the option in the on-wire format with partial
+// domain-name is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWirePartial) {
+ const uint8_t in_data[] = {
+ FLAG_N, // flags
+ 255, // RCODE1
+ 255, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116 // myhost
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with empty
+// domain-name is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWireEmpty) {
+ // Initialize the 3-byte long buffer. All bytes initialized to 0:
+ // Flags, RCODE1 and RCODE2.
+ OptionBuffer in_buf(3, 0);
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ // domain-name field should be empty because on-wire data comprised
+ // flags field only.
+ EXPECT_TRUE(option->getDomainName().empty());
+}
+
+// This test verifies that assignment operator can be used to assign one
+// instance of the option to another.
+TEST(Option4ClientFqdnTest, assignment) {
+ // Usually the smart pointer is used to declare options and call
+ // constructor within assert. Thanks to this approach, the option instance
+ // is in the function scope and only initialization is done within assert.
+ // In this particular test we can't use smart pointers because we are
+ // testing assignment operator like this:
+ //
+ // option2 = option;
+ //
+ // The two asserts below do not create the instances that we will used to
+ // test assignment. They just attempt to create instances of the options
+ // with the same parameters as those that will be created for the actual
+ // assignment test. If these asserts do not fail, we can create options
+ // for the assignment test, do not surround them with asserts and be sure
+ // they will not throw.
+ ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL));
+
+ ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_N,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL));
+
+ // Create options with the same parameters as tested above.
+
+ // Create first option.
+ Option4ClientFqdn option(Option4ClientFqdn::FLAG_S,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL);
+
+ // Verify that the values have been set correctly.
+ ASSERT_TRUE(option.getFlag(Option4ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option.getFlag(Option4ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option.getFlag(Option4ClientFqdn::FLAG_N));
+ ASSERT_EQ("myhost.example.com.", option.getDomainName());
+ ASSERT_EQ(Option4ClientFqdn::FULL, option.getDomainNameType());
+
+ // Create a second option.
+ Option4ClientFqdn option2(Option4ClientFqdn::FLAG_N,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL);
+
+ // Verify tha the values have been set correctly.
+ ASSERT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O));
+ ASSERT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_N));
+ ASSERT_EQ("myhost", option2.getDomainName());
+ ASSERT_EQ(Option4ClientFqdn::PARTIAL, option2.getDomainNameType());
+
+
+ // Make the assignment.
+ ASSERT_NO_THROW(option2 = option);
+
+ // Both options should now have the same values.
+ EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ(option.getDomainName(), option2.getDomainName());
+ EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+}
+
+// This test verifies that constructor will throw an exception if invalid
+// DHCPv6 Client FQDN Option flags are specified.
+TEST(Option4ClientFqdnTest, constructInvalidFlags) {
+ // First, check that constructor does not throw an exception when
+ // valid flags values are provided. That way we eliminate the issue
+ // that constructor always throws exception.
+ uint8_t flags = 0;
+ ASSERT_NO_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"));
+
+ // Invalid flags: The maximal value is 0xF when all flag bits are set
+ // (00001111b). The flag value of 0x18 sets the bit from the Must Be
+ // Zero (MBZ) bitset (00011000b).
+ flags = 0x18;
+ EXPECT_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"),
+ InvalidOption4ClientFqdnFlags);
+
+ // According to RFC 4702, section 2.1. if the N bit is set the S bit MUST
+ // be zero. If both are set, constructor is expected to throw.
+ flags = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S;
+ EXPECT_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"),
+ InvalidOption4ClientFqdnFlags);
+}
+
+// This test verifies that constructor which parses option from on-wire format
+// will throw exception if parsed flags field is invalid.
+TEST(Option4ClientFqdnTest, constructFromWireInvalidFlags) {
+ // Create a buffer which holds flags field only. Set valid flag field at
+ // at first to make sure that constructor doesn't always throw an exception.
+ OptionBuffer in_buf(3, 0);
+ in_buf[0] = FLAG_S;
+ ASSERT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()));
+
+ // Replace the flags with invalid value and verify that constructor throws
+ // appropriate exception.
+ in_buf[0] = FLAG_N | FLAG_S;
+ EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+ InvalidOption4ClientFqdnFlags);
+}
+
+// This test verifies that if invalid domain name is used the constructor
+// will throw appropriate exception.
+TEST(Option4ClientFqdnTest, constructInvalidName) {
+ // First, check that constructor does not throw when valid domain name
+ // is specified. That way we eliminate the possibility that constructor
+ // always throws exception.
+ ASSERT_NO_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"));
+
+ // Specify invalid domain name and expect that exception is thrown.
+ EXPECT_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "my...host.example.com"),
+ InvalidOption4ClientFqdnDomainName);
+}
+
+// This test verifies that getFlag throws an exception if flag value other
+// than explicitly defined in the Option4ClientFqdn::Flag is spcified.
+TEST(Option4ClientFqdnTest, getFlag) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // The 0x3 is a valid enumerator value (even though it is not explicitly
+ // included in the Option4ClientFqdn::Flag definition). The getFlag()
+ // function should not accept it. Only explicit values are accepted.
+ EXPECT_THROW(option->getFlag(static_cast<Option4ClientFqdn::Flag>(0x3)),
+ InvalidOption4ClientFqdnFlags);
+}
+
+// This test verifies that flags can be modified and that incorrect flags
+// are rejected.
+TEST(Option4ClientFqdnTest, setFlag) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // All flags should be set to 0 initially.
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+
+ // Set N = 1
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, true));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+ // Set O = 1
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_O, true));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+
+ // Set S = 1, this should throw exception because S and N must not
+ // be set in the same time.
+ ASSERT_THROW(option->setFlag(Option4ClientFqdn::FLAG_S, true),
+ InvalidOption4ClientFqdnFlags);
+
+ // Set N = 0
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, false));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+ // Set S = 1, this should not result in exception because N has been
+ // cleared.
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_S, true));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+
+ // Set N = 1, this should result in exception because S = 1
+ ASSERT_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, true),
+ InvalidOption4ClientFqdnFlags);
+
+ // Set O = 0
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_O, false));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+
+ // Try out of bounds settings.
+ uint8_t flags = 0;
+ ASSERT_THROW(option->setFlag(static_cast<Option4ClientFqdn::Flag>(flags),
+ true),
+ InvalidOption4ClientFqdnFlags);
+
+ flags = 0x14;
+ ASSERT_THROW(option->setFlag(static_cast<Option4ClientFqdn::Flag>(flags),
+ true),
+ InvalidOption4ClientFqdnFlags);
+}
+
+// This test verifies that flags field of the option is set to 0 when resetFlags
+// function is called.
+TEST(Option4ClientFqdnTest, resetFlags) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_O,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+
+ // Check that flags we set in the constructor are set.
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+ option->resetFlags();
+
+ // After reset, all flags should be 0.
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+}
+
+// This test verifies that current domain-name can be replaced with a new
+// domain-name.
+TEST(Option4ClientFqdnTest, setDomainName) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+ ASSERT_EQ("myhost.example.com.", option->getDomainName());
+ ASSERT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+ // Partial domain-name.
+ ASSERT_NO_THROW(option->setDomainName("myhost",
+ Option4ClientFqdn::PARTIAL));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ // Fully qualified domain-name.
+ ASSERT_NO_THROW(option->setDomainName("example.com",
+ Option4ClientFqdn::FULL));
+ EXPECT_EQ("example.com.", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+ // Empty domain name (partial). This should be successful.
+ ASSERT_NO_THROW(option->setDomainName("", Option4ClientFqdn::PARTIAL));
+ EXPECT_TRUE(option->getDomainName().empty());
+
+ // Fully qualified domain-names must not be empty.
+ EXPECT_THROW(option->setDomainName("", Option4ClientFqdn::FULL),
+ InvalidOption4ClientFqdnDomainName);
+ EXPECT_THROW(option->setDomainName(" ", Option4ClientFqdn::FULL),
+ InvalidOption4ClientFqdnDomainName);
+}
+
+// This test verifies that current domain-name can be reset to empty one.
+TEST(Option4ClientFqdnTest, resetDomainName) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+ ASSERT_EQ("myhost.example.com.", option->getDomainName());
+ ASSERT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+ // Set the domain-name to empty one.
+ ASSERT_NO_THROW(option->resetDomainName());
+ EXPECT_TRUE(option->getDomainName().empty());
+}
+
+// This test verifies on-wire format of the option is correctly created.
+TEST(Option4ClientFqdnTest, pack) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option4ClientFqdn::FLAG_S;
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 81, 23, // header
+ FLAG_S, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies on-wire format of the option with partial domain name
+// is correctly created.
+TEST(Option4ClientFqdnTest, packPartial) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option4ClientFqdn::FLAG_S;
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 81, 10, // header
+ FLAG_S, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116 // myhost
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies that on-wire option data holding fully qualified domain
+// name is parsed correctly.
+TEST(Option4ClientFqdnTest, unpack) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ // Make sure that the parameters have been set correctly. Later in this
+ // test we will check that they will be replaced with new values when
+ // unpack is called.
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ const uint8_t in_data[] = {
+ FLAG_S, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Initialize new values from the on-wire format.
+ ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+ // Check that new values are correct.
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that on-wire option data holding partial domain name
+// is parsed correctly.
+TEST(Option4ClientFqdnTest, unpackPartial) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+ // Make sure that the parameters have been set correctly. Later in this
+ // test we will check that they will be replaced with new values when
+ // unpack is called.
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+ const uint8_t in_data[] = {
+ FLAG_S, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116 // myhost
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Initialize new values from the on-wire format.
+ ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+ // Check that new values are correct.
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the empty buffer is rejected when decoding an option
+// from on-wire format.
+TEST(Option4ClientFqdnTest, unpackTruncated) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O,
+ Option4ClientFqdn::RCODE_CLIENT()))
+ );
+ ASSERT_TRUE(option);
+
+ // Empty buffer is invalid. It should be at least 1 octet long.
+ OptionBuffer in_buf;
+ EXPECT_THROW(option->unpack(in_buf.begin(), in_buf.end()), OutOfRange);
+}
+
+// This test verifies that string representation of the option returned by
+// toText method is correctly formatted.
+TEST(Option4ClientFqdnTest, toText) {
+ // Create option instance. Check that constructor doesn't throw.
+ uint8_t flags = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_O;
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // The base indentation of the option will be set to 2. It should appear
+ // as follows.
+ std::string ref_string =
+ " type=81(CLIENT_FQDN)\n"
+ " flags:\n"
+ " N=1\n"
+ " E=0\n"
+ " O=1\n"
+ " S=0\n"
+ " domain-name='myhost.example.com.' (full)\n";
+ const int indent = 2;
+ EXPECT_EQ(ref_string, option->toText(indent));
+
+ // Create another option with different parameters:
+ // - flags set to 0
+ // - domain-name is now partial, not fully qualified
+ // Also, remove base indentation.
+ flags = 0;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ref_string =
+ "type=81(CLIENT_FQDN)\n"
+ "flags:\n"
+ " N=0\n"
+ " E=0\n"
+ " O=0\n"
+ " S=0\n"
+ "domain-name='myhost' (partial)\n";
+ EXPECT_EQ(ref_string, option->toText());
+}
+
+// This test verifies that the correct length of the option in on-wire
+// format is returned.
+TEST(Option4ClientFqdnTest, len) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+ // This option comprises a header (2 octets), flag field (1 octet),
+ // RCODE1 and RCODE2 (2 octets) and wire representation of the
+ // domain name (length equal to the length of the string representation
+ // of the domain name + 1).
+ EXPECT_EQ(25, option->len());
+
+ // Let's check that the size will change when domain name of a different
+ // size is used.
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "example.com"))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_EQ(18, option->len());
+
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_EQ(12, option->len());
+ }
+
+} // anonymous namespace