Browse Source

[master] Merge branch 'trac2976'

Marcin Siodelski 12 years ago
parent
commit
eac5e75147

+ 3 - 0
src/bin/d2/Makefile.am

@@ -52,6 +52,8 @@ b10_dhcp_ddns_SOURCES += d_process.h
 b10_dhcp_ddns_SOURCES += d2_process.cc d2_process.h
 b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
 b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
+b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
+b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 
 nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes
@@ -61,6 +63,7 @@ b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
 
 b10_dhcp_ddnsdir = $(pkgdatadir)
 b10_dhcp_ddns_DATA = dhcp-ddns.spec

+ 221 - 0
src/bin/d2/d2_update_message.cc

@@ -0,0 +1,221 @@
+// 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 <d2/d2_update_message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/question.h>
+
+namespace isc {
+namespace d2 {
+
+using namespace isc::dns;
+
+D2UpdateMessage::D2UpdateMessage(const Direction direction)
+    : message_(direction == INBOUND ?
+               dns::Message::PARSE : dns::Message::RENDER) {
+    // If this object is to create an outgoing message, we have to
+    // set the proper Opcode field and QR flag here.
+    if (direction == OUTBOUND) {
+        message_.setOpcode(Opcode(Opcode::UPDATE_CODE));
+        message_.setHeaderFlag(dns::Message::HEADERFLAG_QR, false);
+
+    }
+}
+
+D2UpdateMessage::QRFlag
+D2UpdateMessage::getQRFlag() const {
+    return (message_.getHeaderFlag(dns::Message::HEADERFLAG_QR) ?
+            RESPONSE : REQUEST);
+}
+
+uint16_t
+D2UpdateMessage::getId() const {
+    return (message_.getQid());
+}
+
+void
+D2UpdateMessage::setId(const uint16_t id) {
+    message_.setQid(id);
+}
+
+
+const dns::Rcode&
+D2UpdateMessage::getRcode() const {
+    return (message_.getRcode());
+}
+
+void
+D2UpdateMessage::setRcode(const dns::Rcode& rcode) {
+    message_.setRcode(rcode);
+}
+
+unsigned int
+D2UpdateMessage::getRRCount(const UpdateMsgSection section) const {
+    return (message_.getRRCount(ddnsToDnsSection(section)));
+}
+
+const dns::RRsetIterator
+D2UpdateMessage::beginSection(const UpdateMsgSection section) const {
+    return (message_.beginSection(ddnsToDnsSection(section)));
+}
+
+const dns::RRsetIterator
+D2UpdateMessage::endSection(const UpdateMsgSection section) const {
+    return (message_.endSection(ddnsToDnsSection(section)));
+}
+
+void
+D2UpdateMessage::setZone(const Name& zone, const RRClass& rrclass) {
+    // The Zone data is kept in the underlying Question class. If there
+    // is a record stored there already, we need to remove it, because
+    // we may have at most one Zone record in the DNS Update message.
+    if (message_.getRRCount(dns::Message::SECTION_QUESTION) > 0) {
+        message_.clearSection(dns::Message::SECTION_QUESTION);
+    }
+    // Add the new record...
+    Question question(zone, rrclass, RRType::SOA());
+    message_.addQuestion(question);
+    // ... and update the local class member holding the D2Zone object.
+    zone_.reset(new D2Zone(question.getName(), question.getClass()));
+}
+
+D2ZonePtr
+D2UpdateMessage::getZone() const {
+    return (zone_);
+}
+
+void
+D2UpdateMessage::addRRset(const UpdateMsgSection section,
+                          const dns::RRsetPtr& rrset) {
+    if (section == SECTION_ZONE) {
+        isc_throw(isc::BadValue, "unable to add RRset to the Zone section"
+                  " of the DNS Update message, use setZone instead");
+    }
+    message_.addRRset(ddnsToDnsSection(section), rrset);
+}
+
+void
+D2UpdateMessage::toWire(AbstractMessageRenderer& renderer) {
+    // We are preparing the wire format of the message, meaning
+    // that this message will be sent as a request to the DNS.
+    // Therefore, we expect that this message is a REQUEST.
+    if (getQRFlag() != REQUEST) {
+        isc_throw(InvalidQRFlag, "QR flag must be cleared for the outgoing"
+                  " DNS Update message");
+    }
+    // According to RFC2136, the ZONE section may contain exactly one
+    // record.
+    if (getRRCount(SECTION_ZONE) != 1) {
+        isc_throw(InvalidZoneSection, "Zone section of the DNS Update message"
+                  " must comprise exactly one record (RFC2136, section 2.3)");
+    }
+    message_.toWire(renderer);
+}
+
+void
+D2UpdateMessage::fromWire(isc::util::InputBuffer& buffer) {
+    // First, use the underlying dns::Message implementation to get the
+    // contents of the DNS response message. Note that it may or may
+    // not be the message that we are interested in, but needs to be
+    // parsed so as we can check its ID, Opcode etc.
+    message_.fromWire(buffer);
+    // This class exposes the getZone() function. This function will return
+    // pointer to the D2Zone object if non-empty Zone section exists in the
+    // received message. It will return NULL pointer if it doesn't exist.
+    // The pointer is held in the D2UpdateMessage class member. We need to
+    // update this pointer every time we parse the message.
+    if (getRRCount(D2UpdateMessage::SECTION_ZONE) > 0) {
+        // There is a Zone section in the received message. Replace
+        // Zone pointer with the new value.
+        QuestionPtr question = *message_.beginQuestion();
+        // If the Zone counter is greater than 0 (which we have checked)
+        // there must be a valid Question pointer stored in the message_
+        // object. If there isn't, it is a programming error.
+        assert(question);
+        zone_.reset(new D2Zone(question->getName(), question->getClass()));
+
+    } else {
+        // Zone section doesn't hold any pointers, so set the pointer to NULL.
+        zone_.reset();
+
+    }
+    // Check that the content of the received message is sane.
+    // One of the basic checks to do is to verify that we have
+    // received the DNS update message. If not, it can be dropped
+    // or an error message can be printed. Other than that, we
+    // will check that there is at most one Zone record and QR flag
+    // is set.
+    validateResponse();
+}
+
+dns::Message::Section
+D2UpdateMessage::ddnsToDnsSection(const UpdateMsgSection section) {
+    /// The following switch maps the enumerator values from the
+    /// DNS Update message to the corresponding enumerator values
+    /// representing fields of the DNS message.
+    switch(section) {
+    case SECTION_ZONE :
+        return (dns::Message::SECTION_QUESTION);
+
+    case SECTION_PREREQUISITE:
+        return (dns::Message::SECTION_ANSWER);
+
+    case SECTION_UPDATE:
+        return (dns::Message::SECTION_AUTHORITY);
+
+    case SECTION_ADDITIONAL:
+        return (dns::Message::SECTION_ADDITIONAL);
+
+    default:
+        ;
+    }
+    isc_throw(dns::InvalidMessageSection,
+              "unknown message section " << section);
+}
+
+void
+D2UpdateMessage::validateResponse() const {
+    // Verify that we are dealing with the DNS Update message. According to
+    // RFC 2136, section 3.8 server will copy the Opcode from the query.
+    // If we are dealing with a different type of message, we may simply
+    // stop further processing, because it is likely that the message was
+    // directed to someone else.
+    if (message_.getOpcode() != Opcode::UPDATE()) {
+        isc_throw(NotUpdateMessage, "received message is not a DDNS update,"
+                  << " received message code is "
+                  << message_.getOpcode().getCode());
+    }
+    // Received message should have QR flag set, which indicates that it is
+    // a RESPONSE.
+    if (getQRFlag() == REQUEST) {
+        isc_throw(InvalidQRFlag, "received message should have QR flag set,"
+                  " to indicate that it is a RESPONSE message; the QR"
+                  << " flag in received message is unset");
+    }
+    // DNS server may copy a Zone record from the query message. Since query
+    // must comprise exactly one Zone record (RFC 2136, section 2.3), the
+    // response message may contain 1 record at most. It may also contain no
+    // records if a server chooses not to copy Zone section.
+    if (getRRCount(SECTION_ZONE) > 1) {
+        isc_throw(InvalidZoneSection, "received message contains "
+                  << getRRCount(SECTION_ZONE) << " Zone records,"
+                  << " it should contain at most 1 record");
+    }
+}
+
+} // namespace d2
+} // namespace isc
+

+ 337 - 0
src/bin/d2/d2_update_message.h

@@ -0,0 +1,337 @@
+// 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 D2_UPDATE_MESSAGE_H
+#define D2_UPDATE_MESSAGE_H
+
+#include <d2/d2_zone.h>
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+#include <map>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception indicating that Zone section contains invalid content.
+///
+/// This exception is thrown when ZONE section of the DNS Update message
+/// is invalid. According to RFC2136, section 2.3, the zone section is
+/// allowed to contain exactly one record. When Request message contains
+/// more records or is empty, this exception is thrown.
+class InvalidZoneSection : public Exception {
+public:
+    InvalidZoneSection(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that QR flag has invalid value.
+///
+/// This exception is thrown when QR flag has invalid value for
+/// the operation performed on the particular message. For instance,
+/// the QR flag must be set to indicate that the given message is
+/// a RESPONSE when @c D2UpdateMessage::fromWire is performed.
+/// The QR flag must be cleared when @c D2UpdateMessage::toWire
+/// is executed.
+class InvalidQRFlag : public Exception {
+public:
+    InvalidQRFlag(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that the parsed message is not DNS Update.
+///
+/// This exception is thrown when decoding the DNS message which is not
+/// a DNS Update.
+class NotUpdateMessage : public Exception {
+public:
+    NotUpdateMessage(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+
+/// @brief The @c D2UpdateMessage encapsulates a DNS Update message.
+///
+/// This class represents the DNS Update message. Functions exposed by this
+/// class allow to specify the data sections carried by the message and create
+/// an on-wire format of this message. This class is also used to decode
+/// messages received from the DNS server in the on-wire format.
+///
+/// <b>Design choice:</b> A dedicated class has been created to encapsulate
+/// DNS Update message because existing @c isc::dns::Message is designed to
+/// support regular DNS messages (described in RFC 1035) only. Although DNS
+/// Update has the same format, particular sections serve different purposes.
+/// In order to avoid rewrite of significant portions of @c isc::dns::Message
+/// class, this class is implemented in-terms-of @c isc::dns::Message class
+/// to reuse its functionality where possible.
+class D2UpdateMessage {
+public:
+
+    /// @brief Indicates if the @c D2UpdateMessage object encapsulates Inbound
+    /// or Outbound message.
+    enum Direction {
+        INBOUND,
+        OUTBOUND
+    };
+
+    /// @brief Indicates whether DNS Update message is a REQUEST or RESPONSE.
+    enum QRFlag {
+        REQUEST,
+        RESPONSE
+    };
+
+    /// @brief Identifies sections in the DNS Update Message.
+    ///
+    /// Each message comprises message Header and may contain the following
+    /// sections:
+    /// - ZONE
+    /// - PREREQUISITE
+    /// - UPDATE
+    /// - ADDITIONAL
+    ///
+    /// The enum elements are used by functions such as @c getRRCount (to get
+    /// the number of records in a corresponding section) and @c beginSection
+    /// and @c endSection (to access data in the corresponding section).
+    enum UpdateMsgSection {
+        SECTION_ZONE,
+        SECTION_PREREQUISITE,
+        SECTION_UPDATE,
+        SECTION_ADDITIONAL
+    };
+
+public:
+    /// @brief Constructor used to create an instance of the DNS Update Message
+    /// (either outgoing or incoming).
+    ///
+    /// This constructor is used to create an instance of either incoming or
+    /// outgoing DNS Update message. The boolean argument indicates wheteher it
+    /// is incoming (true) or outgoing (false) message. For incoming messages
+    /// the @c D2UpdateMessage::fromWire function is used to parse on-wire data.
+    /// For outgoing messages, modifier functions should be used to set the
+    /// message contents and @c D2UpdateMessage::toWire function to create
+    /// on-wire data.
+    ///
+    /// @param direction indicates if this is an inbound or outbound message.
+    D2UpdateMessage(const Direction direction = OUTBOUND);
+
+    ///
+    /// @name Copy constructor and assignment operator
+    ///
+    /// Copy constructor and assignment operator are private because we assume
+    /// there will be no need to copy messages on the client side.
+    //@{
+private:
+    D2UpdateMessage(const D2UpdateMessage& source);
+    D2UpdateMessage& operator=(const D2UpdateMessage& source);
+    //@}
+
+public:
+
+    /// @brief Returns enum value indicating if the message is a
+    /// REQUEST or RESPONSE
+    ///
+    /// The returned value is REQUEST if the message is created as an outgoing
+    /// message. In such case the QR flag bit in the message header is cleared.
+    /// The returned value is RESPONSE if the message is created as an incoming
+    /// message and the QR flag bit was set in the received message header.
+    ///
+    /// @return An enum value indicating whether the message is a
+    /// REQUEST or RESPONSE.
+    QRFlag getQRFlag() const;
+
+    /// @brief Returns message ID.
+    ///
+    /// @return message ID.
+    uint16_t getId() const;
+
+    /// @brief Sets message ID.
+    ///
+    /// @param id 16-bit value of the message id.
+    void setId(const uint16_t id);
+
+    /// @brief Returns an object representing message RCode.
+    ///
+    /// @return An object representing message RCode.
+    const dns::Rcode& getRcode() const;
+
+    /// @brief Sets message RCode.
+    ///
+    /// @param rcode An object representing message RCode.
+    void setRcode(const dns::Rcode& rcode);
+
+    /// @brief Returns number of RRsets in the specified message section.
+    ///
+    /// @param section An @c UpdateMsgSection enum specifying a message section
+    /// for which the number of RRsets is to be returned.
+    ///
+    /// @return A number of RRsets in the specified message section.
+    unsigned int getRRCount(const UpdateMsgSection section) const;
+
+    /// @name Functions returning iterators to RRsets in message sections.
+    ///
+    //@{
+    /// @brief Return iterators pointing to the beginning of the list of RRsets,
+    /// which belong to the specified section.
+    ///
+    /// @param section An @c UpdateMsgSection enum specifying a message section
+    /// for which the iterator should be returned.
+    ///
+    /// @return An iterator pointing to the beginning of the list of the
+    /// RRsets, which belong to the specified section.
+    const dns::RRsetIterator beginSection(const UpdateMsgSection section) const;
+
+    /// @brief Return iterators pointing to the end of the list of RRsets,
+    /// which belong to the specified section.
+    ///
+    /// @param section An @c UpdateMsgSection enum specifying a message section
+    /// for which the iterator should be returned.
+    ///
+    /// @return An iterator pointing to the end of the list of the
+    /// RRsets, which belong to the specified section.
+    const dns::RRsetIterator endSection(const UpdateMsgSection section) const;
+    //@}
+
+    /// @brief Sets the Zone record.
+    ///
+    /// This function creates the @c D2Zone object, representing a Zone record
+    /// for the outgoing message. If the Zone record is already set, it is
+    /// replaced by the new record being set by this function. The RRType for
+    /// the record is always SOA.
+    ///
+    /// @param zone A name of the zone being updated.
+    /// @param rrclass A class of the zone record.
+    void setZone(const dns::Name& zone, const dns::RRClass& rrclass);
+
+    /// @brief Returns a pointer to the object representing Zone record.
+    ///
+    /// @return A pointer to the object representing Zone record.
+    D2ZonePtr getZone() const;
+
+    /// @brief Adds an RRset to the specified section.
+    ///
+    /// This function may throw exception if the specified section is
+    /// out of bounds or Zone section update is attempted. For Zone
+    /// section @c D2UpdateMessage::setZone function should be used instead.
+    /// Also, this function expects that @c rrset argument is non-NULL.
+    ///
+    /// @param section A message section where the RRset should be added.
+    /// @param rrset A reference to a RRset which should be added.
+    void addRRset(const UpdateMsgSection section, const dns::RRsetPtr& rrset);
+
+    /// @name Functions to handle message encoding and decoding.
+    ///
+    //@{
+    /// @brief Encode outgoing message into wire format.
+    ///
+    /// This function encodes the DNS Update into the wire format. The format of
+    /// such a message is described in the RFC2136, section 2. Some of the
+    /// sections which belong to encoded message may be empty. If a particular
+    /// message section is empty (does not comprise any RRs), the corresponding
+    /// counter in the message header is set to 0. These counters are: PRCOUNT,
+    /// UPCOUNT, ADCOUNT for the Prerequisites, Update RRs and Additional Data
+    /// RRs respectively. The ZOCOUNT must be equal to 1 because RFC2136
+    /// requires that the message comprises exactly one Zone record.
+    ///
+    /// This function does not guarantee exception safety. However, exceptions
+    /// should be rare because @c D2UpdateMessage class API prevents invalid
+    /// use of the class. The typical case, when this function may throw an
+    /// exception is when this it is called on the object representing
+    /// incoming (instead of outgoing) message. In such case, the QR field
+    /// will be set to RESPONSE, which is invalid setting when calling this
+    /// function.
+    ///
+    /// @param renderer A renderer object used to generate the message wire
+    /// format.
+    void toWire(dns::AbstractMessageRenderer& renderer);
+
+    /// @brief Decode incoming message from the wire format.
+    ///
+    /// This function decodes the DNS Update message stored in the buffer
+    /// specified by the function argument. In the first turn, this function
+    /// parses message header and extracts the section counters: ZOCOUNT,
+    /// PRCOUNT, UPCOUNT and ADCOUNT. Using these counters, function identifies
+    /// message sections, which follow message header. These sections can be
+    /// later accessed using: @c D2UpdateMessage::getZone,
+    /// @c D2UpdateMessage::beginSection and @c D2UpdateMessage::endSection
+    /// functions.
+    ///
+    /// This function is NOT exception safe. It signals message decoding errors
+    /// through exceptions. Message decoding error may occur if the received
+    /// message does not conform to the general DNS Message format, specified in
+    /// RFC 1035. Errors which are specific to DNS Update messages include:
+    /// - Invalid Opcode - not an UPDATE.
+    /// - Invalid QR flag - the QR bit should be set to indicate that the
+    /// message is the server response.
+    /// - The number of records in the Zone section is greater than 1.
+    ///
+    /// @param buffer input buffer, holding DNS Update message to be parsed.
+    void fromWire(isc::util::InputBuffer& buffer);
+    //@}
+
+private:
+    /// Maps the values of the @c UpdateMessageSection field to the
+    /// corresponding values in the @c isc::dns::Message class. This
+    /// mapping is required here because this class uses @c isc::dns::Message
+    /// class to do the actual processing of the DNS Update message.
+    ///
+    /// @param section An enum indicating the section for which the
+    /// corresponding  enum value from @c isc::dns::Message will be returned.
+    ///
+    /// @return The enum value indicating the section in the DNS message
+    /// represented by the @c isc::dns::Message class.
+    static
+    dns::Message::Section ddnsToDnsSection(const UpdateMsgSection section);
+
+    /// @brief Checks received response message for correctness.
+    ///
+    /// This function verifies that the received response from a server is
+    /// correct. Currently this function checks the following:
+    /// - Opcode is 'DNS Update',
+    /// - QR flag is RESPONSE (flag bit is set),
+    /// - Zone section comprises at most one record.
+    ///
+    /// The function will throw exception if any of the conditions above are
+    /// not met.
+    ///
+    /// @throw isc::d2::NotUpdateMessage if invalid Opcode.
+    /// @throw isc::d2::InvalidQRFlag if QR flag is not set to RESPONSE
+    /// @throw isc::d2::InvalidZone section, if Zone section comprises more
+    /// than one record.
+    void validateResponse() const;
+
+    /// @brief An object representing DNS Message which is used by the
+    /// implementation of @c D2UpdateMessage to perform low level.
+    ///
+    /// Declaration of this object pollutes the header with the details
+    /// of @c D2UpdateMessage implementation. It might be cleaner to use
+    /// Pimpl idiom to hide this object in an D2UpdateMessageImpl. However,
+    /// it would bring additional complications to the implementation
+    /// while the benefit would low - this header is not a part of any
+    /// common library. Therefore, if implementation is changed, modification of
+    /// private members of this class in the header has low impact.
+    dns::Message message_;
+
+    /// @brief Holds a pointer to the object, representing Zone in the DNS
+    /// Update.
+    D2ZonePtr zone_;
+
+};
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_UPDATE_MESSAGE_H

+ 36 - 0
src/bin/d2/d2_zone.cc

@@ -0,0 +1,36 @@
+// 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 <d2/d2_zone.h>
+
+namespace isc {
+namespace d2 {
+
+D2Zone::D2Zone(const dns::Name& name, const dns::RRClass& rrclass)
+    : name_(name), rrclass_(rrclass) {
+}
+
+std::string D2Zone::toText() const {
+    return (name_.toText() + " " + rrclass_.toText() + " SOA\n");
+}
+
+std::ostream&
+operator<<(std::ostream& os, const D2Zone& zone) {
+    os << zone.toText();
+    return (os);
+}
+
+} // namespace d2
+} // namespace isc
+

+ 117 - 0
src/bin/d2/d2_zone.h

@@ -0,0 +1,117 @@
+// 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 D2_ZONE_H
+#define D2_ZONE_H
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace d2 {
+
+/// @brief The @c D2Zone encapsulates the Zone section in DNS Update message.
+///
+/// This class is used by the @c D2UpdateMessage to encapsulate the Zone section
+/// of the DNS Update message. Class members hold corresponding values of
+/// section's fields: NAME, CLASS. This class does not hold the RTYPE field
+/// value because RTYPE is always equal to SOA for DNS Update message (see
+/// RFC 2136, section 2.3).
+///
+/// Note, that this @c D2Zone class neither exposes functions to decode messages
+/// from wire format nor to encode to wire format. This is not needed, because
+/// @c isc::d2::D2UpdateMessage class uses @c D2Zone only to return the parsed
+/// Zone information to the caller. Internally, D2UpdateMessage parses and
+/// stores Zone section using @c isc::dns::Question class, and the @c toWire
+/// and @c fromWire functions of the @c isc::dns::Question class are used.
+class D2Zone {
+public:
+    /// @brief Constructor from Name and RRClass.
+    ///
+    /// @param name The name of the Zone.
+    /// @param rrclass The RR class of the Zone.
+    D2Zone(const dns::Name& name, const dns::RRClass& rrclass);
+
+    ///
+    /// @name Getters
+    ///
+    //@{
+    /// @brief Returns the Zone name.
+    ///
+    /// @return A reference to the Zone name.
+    const dns::Name& getName() const { return (name_); }
+
+    /// @brief Returns the Zone class.
+    ///
+    /// @return A reference to the Zone class.
+    const dns::RRClass& getClass() const { return (rrclass_); }
+    //@}
+
+    /// @brief Returns text representation of the Zone.
+    ///
+    /// This function concatenates the name of the Zone, Class and Type.
+    /// The type is always SOA.
+    ///
+    /// @return A text representation of the Zone.
+    std::string toText() const;
+
+    ///
+    /// @name Comparison Operators
+    ///
+    //@{
+    /// @brief Equality operator to compare @c D2Zone objects in query and
+    /// response messages.
+    ///
+    /// @param rhs Zone to compare against.
+    ///
+    /// @return true if name and class are equal, false otherwise.
+    bool operator==(const D2Zone& rhs) const {
+        return ((rrclass_ == rhs.rrclass_) && (name_ == rhs.name_));
+    }
+
+    /// @brief Inequality operator to compare @c D2Zone objects in query and
+    /// response messages.
+    ///
+    /// @param rhs Zone to compare against.
+    ///
+    /// @return true if any of name or class are unequal, false otherwise.
+    bool operator!=(const D2Zone& rhs) const {
+        return (!operator==(rhs));
+    }
+    //@}
+
+private:
+    dns::Name name_;       ///< Holds the Zone name.
+    dns::RRClass rrclass_; ///< Holds the Zone class.
+};
+
+typedef boost::shared_ptr<D2Zone> D2ZonePtr;
+
+/// @brief Insert the @c D2Zone as a string into stream.
+///
+/// @param os A @c std::ostream object on which the insertion operation is
+/// performed.
+/// @param zone A reference to the @c D2Zone object output by the
+/// operation.
+///
+/// @return A reference to the same @c std::ostream object referenced by
+/// parameter @c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const D2Zone& zone);
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_ZONE_H

+ 5 - 0
src/bin/d2/tests/Makefile.am

@@ -56,11 +56,15 @@ d2_unittests_SOURCES += ../d_process.h
 d2_unittests_SOURCES += ../d_controller.cc ../d2_controller.h
 d2_unittests_SOURCES += ../d2_process.cc ../d2_process.h
 d2_unittests_SOURCES += ../d2_controller.cc ../d2_controller.h
+d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
+d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
 d2_unittests_SOURCES += d2_unittests.cc
 d2_unittests_SOURCES += d2_process_unittests.cc
 d2_unittests_SOURCES += d_controller_unittests.cc
 d2_unittests_SOURCES += d2_controller_unittests.cc
+d2_unittests_SOURCES += d2_update_message_unittests.cc
+d2_unittests_SOURCES += d2_zone_unittests.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
@@ -71,6 +75,7 @@ d2_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
 endif
 
 noinst_PROGRAMS = $(TESTS)

+ 591 - 0
src/bin/d2/tests/d2_update_message_unittests.cc

@@ -0,0 +1,591 @@
+// 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 <d2/d2_update_message.h>
+#include <d2/d2_zone.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata.h>
+#include <dns/rrttl.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::util;
+
+namespace {
+
+// @brief Test fixture class for testing D2UpdateMessage object.
+class D2UpdateMessageTest : public ::testing::Test {
+public:
+    // @brief Constructor.
+    //
+    // Does nothing.
+    D2UpdateMessageTest() { }
+
+    // @brief Destructor.
+    //
+    // Does nothing.
+    ~D2UpdateMessageTest() { };
+
+    // @brief Return string representation of the name encoded in wire format.
+    //
+    // This function reads the number of bytes specified in the second
+    // argument from the buffer. It doesn't check if buffer has sufficient
+    // length for reading given number of bytes. Caller should verify it
+    // prior to calling this function.
+    //
+    // @param buf input buffer, its internal pointer will be moved to
+    //        the position after a name being read from it.
+    // @param name_length length of the name stored in the buffer
+    // @param no_zero_byte if true it indicates that the given buffer does not
+    //        comprise the zero byte, which signals end of the name. This is
+    //        the case, when dealing with compressed messages which don't have
+    //        this byte.
+    //
+    // @return string representation of the name.
+    std::string readNameFromWire(InputBuffer& buf, size_t name_length,
+                                 const bool no_zero_byte = false) {
+        std::vector<uint8_t> name_data;
+        // Create another InputBuffer which holds only the name in the wire
+        // format.
+        buf.readVector(name_data, name_length);
+        if (no_zero_byte) {
+            ++name_length;
+            name_data.push_back(0);
+        }
+        InputBuffer name_buf(&name_data[0], name_length);
+        // Parse the name and return its textual representation.
+        Name name(name_buf);
+        return (name.toText());
+    }
+};
+
+// This test verifies that DNS Update message ID can be set using
+// setId function.
+TEST_F(D2UpdateMessageTest, setId) {
+    // Message ID is initialized to 0.
+    D2UpdateMessage msg;
+    EXPECT_EQ(0, msg.getId());
+    // Override the default value and verify that it has been set.
+    msg.setId(0x1234);
+    EXPECT_EQ(0x1234, msg.getId());
+}
+
+// This test verifies that the DNS Update message RCODE can be set
+// using setRcode function.
+TEST_F(D2UpdateMessageTest, setRcode) {
+    D2UpdateMessage msg;
+    // Rcode must be explicitly set before it is accessed.
+    msg.setRcode(Rcode::NOERROR());
+    EXPECT_EQ(Rcode::NOERROR().getCode(), msg.getRcode().getCode());
+    // Let's override current value to make sure that getter does
+    // not return fixed value.
+    msg.setRcode(Rcode::NOTIMP());
+    EXPECT_EQ(Rcode::NOTIMP().getCode(), msg.getRcode().getCode());
+}
+
+// This test verifies that the Zone section in the DNS Update message
+// can be set.
+TEST_F(D2UpdateMessageTest, setZone) {
+    D2UpdateMessage msg;
+    // The zone pointer is initialized to NULL.
+    D2ZonePtr zone = msg.getZone();
+    EXPECT_FALSE(zone);
+    // Let's create a new Zone and check that it is returned
+    // via getter.
+    msg.setZone(Name("example.com"), RRClass::ANY());
+    zone = msg.getZone();
+    EXPECT_TRUE(zone);
+    EXPECT_EQ("example.com.", zone->getName().toText());
+    EXPECT_EQ(RRClass::ANY().getCode(), zone->getClass().getCode());
+
+    // Now, let's check that the existing Zone object can be
+    // overriden with a new one.
+    msg.setZone(Name("foo.example.com"), RRClass::NONE());
+    zone = msg.getZone();
+    EXPECT_TRUE(zone);
+    EXPECT_EQ("foo.example.com.", zone->getName().toText());
+    EXPECT_EQ(RRClass::NONE().getCode(), zone->getClass().getCode());
+}
+
+// This test verifies that the DNS message is properly decoded from the
+// wire format.
+TEST_F(D2UpdateMessageTest, fromWire) {
+    // The following table holds the DNS response in on-wire format.
+    // This message comprises the following sections:
+    // - HEADER
+    // - PREREQUISITE section with one RR
+    // - UPDATE section with 1 RR.
+    // Such a response may be generated by the DNS server as a result
+    // of copying the contents of the REQUEST message sent by DDNS client.
+    const uint8_t bin_msg[] = {
+        // HEADER section starts here (see RFC 2136, section 2).
+        0x05, 0xAF, // ID=0x05AF
+        0xA8, 0x6,  // QR=1, Opcode=6, RCODE=YXDOMAIN
+        0x0, 0x1,   // ZOCOUNT=1
+        0x0, 0x2,   // PRCOUNT=2
+        0x0, 0x1,   // UPCOUNT=1
+        0x0, 0x0,   // ADCOUNT=0
+
+        // Zone section starts here. The The first field comprises
+        // the Zone name encoded as a set of labels, each preceded
+        // by a length of the following label. The whole Zone name is
+        // terminated with a NULL char.
+        // For Zone section format see (RFC 2136, section 2.3).
+        0x7, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example (7 is length)
+        0x3, 0x63, 0x6F, 0x6D, //.com. (0x3 is a length)
+        0x0,      // NULL character terminates the Zone name.
+        0x0, 0x6, // ZTYPE='SOA'
+        0x0, 0x1, // ZCLASS='IN'
+
+        // Prerequisite section starts here. This section comprises two
+        // prerequisites:
+        // - 'Name is not in use'
+        // - 'Name is in use'
+        // See RFC 2136, section 2.4 for the format of Prerequisite section.
+        // Each prerequisite RR starts with its name. It is expressed in the
+        // compressed format as described in RFC 1035, section 4.1.4. The first
+        // label is expressed as in case of non-compressed name. It is preceded
+        // by the length value. The following two bytes are the pointer to the
+        // offset in the message where 'example.com' was used. That is, in the
+        // Zone name at offset 12. Pointer starts with two bits set - they
+        // mark start of the pointer.
+
+        // First prerequisite. NONE class indicates that the update requires
+        // that the name 'foo.example.com' is not use/
+        0x03, 0x66, 0x6F, 0x6F, // foo.
+        0xC0, 0x0C,             // pointer to example.com.
+        0x0, 0x1C,              // TYPE=AAAA
+        0x0, 0xFE,              // CLASS=NONE
+        0x0, 0x0, 0x0, 0x0,     // TTL=0
+        0x0, 0x0,               // RDLENGTH=0
+
+        // Second prerequisite. ANY class indicates tha the update requires
+        // that the name 'bar.example.com' exists.
+        0x03, 0x62, 0x61, 0x72, // bar.
+        0xC0, 0x0C,             // pointer to example.com.
+        0x0, 0x1C,              // TYPE=AAAA
+        0x0, 0xFF,              // CLASS=ANY
+        0x0, 0x0, 0x0, 0x0,     // TTL=0
+        0x0, 0x0,               // RDLENGTH=0
+
+        // Update section starts here. The format of this section conforms to
+        // RFC 2136, section 2.5. The name of the RR is again expressed in
+        // compressed format. The two pointer bytes point to the offset in the
+        // message where 'foo.example.com' was used already - 29.
+        0xC0, 0x1D,             // pointer to foo.example.com.
+        0x0, 0x1C,              // TYPE=AAAA
+        0x0, 0x1,               // CLASS=IN
+        0xAA, 0xBB, 0xCC, 0xDD, // TTL=0xAABBCCDD
+        0x0, 0x10,              // RDLENGTH=16
+        // The following 16 bytes of RDATA hold IPv6 address: 2001:db8:1::1.
+        0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+    };
+    InputBuffer buf(bin_msg, sizeof(bin_msg));
+
+    // Create an object to be used to decode the message from the wire format.
+    D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+    // Decode the message.
+    ASSERT_NO_THROW(msg.fromWire(buf));
+
+    // Check that the message header is valid.
+    EXPECT_EQ(0x05AF, msg.getId());
+    EXPECT_EQ(D2UpdateMessage::RESPONSE, msg.getQRFlag());
+    EXPECT_EQ(Rcode::YXDOMAIN_CODE, msg.getRcode().getCode());
+
+    // The ZOCOUNT must contain exactly one zone. If it does, we should get
+    // the name, class and type of the zone and verify they are valid.
+    ASSERT_EQ(1, msg.getRRCount(D2UpdateMessage::SECTION_ZONE));
+    D2ZonePtr zone = msg.getZone();
+    ASSERT_TRUE(zone);
+    EXPECT_EQ("example.com.", zone->getName().toText());
+    EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());
+
+    // Check the Prerequisite section. It should contain two records.
+    ASSERT_EQ(2, msg.getRRCount(D2UpdateMessage::SECTION_PREREQUISITE));
+
+    // Proceed to the first prerequisite.
+    RRsetIterator rrset_it =
+        msg.beginSection(D2UpdateMessage::SECTION_PREREQUISITE);
+    RRsetPtr prereq1 = *rrset_it;
+    ASSERT_TRUE(prereq1);
+    // Check record fields.
+    EXPECT_EQ("foo.example.com.", prereq1->getName().toText()); // NAME
+    EXPECT_EQ(RRType::AAAA().getCode(), prereq1->getType().getCode()); // TYPE
+    EXPECT_EQ(RRClass::NONE().getCode(),
+              prereq1->getClass().getCode()); // CLASS
+    EXPECT_EQ(0, prereq1->getTTL().getValue()); // TTL
+    EXPECT_EQ(0, prereq1->getRdataCount()); // RDLENGTH
+
+    // Move to next prerequisite section.
+    ++rrset_it;
+    RRsetPtr prereq2 = *rrset_it;
+    ASSERT_TRUE(prereq2);
+    // Check record fields.
+    EXPECT_EQ("bar.example.com.", prereq2->getName().toText()); // NAME
+    EXPECT_EQ(RRType::AAAA().getCode(), prereq2->getType().getCode()); // TYPE
+    EXPECT_EQ(RRClass::ANY().getCode(), prereq2->getClass().getCode()); // CLASS
+    EXPECT_EQ(0, prereq2->getTTL().getValue()); // TTL
+    EXPECT_EQ(0, prereq2->getRdataCount()); // RDLENGTH
+
+    // Check the Update section. There is only one record, so beginSection()
+    // should return the pointer to this sole record.
+    ASSERT_EQ(1, msg.getRRCount(D2UpdateMessage::SECTION_UPDATE));
+    rrset_it = msg.beginSection(D2UpdateMessage::SECTION_UPDATE);
+    RRsetPtr update = *rrset_it;
+    ASSERT_TRUE(update);
+    // Check the record fields.
+    EXPECT_EQ("foo.example.com.", update->getName().toText()); // NAME
+    EXPECT_EQ(RRType::AAAA().getCode(), update->getType().getCode()); // TYPE
+    EXPECT_EQ(RRClass::IN().getCode(), update->getClass().getCode()); // CLASS
+    EXPECT_EQ(0xAABBCCDD, update->getTTL().getValue()); // TTL
+    // There should be exactly one record holding the IPv6 address.
+    // This record can be accessed using RdataIterator. This record
+    // can be compared with the reference record, holding expected IPv6
+    // address using compare function.
+    ASSERT_EQ(1, update->getRdataCount());
+    RdataIteratorPtr rdata_it = update->getRdataIterator();
+    ASSERT_TRUE(rdata_it);
+    in::AAAA rdata_ref("2001:db8:1::1");
+    EXPECT_EQ(0, rdata_ref.compare(rdata_it->getCurrent()));
+
+    // @todo: at this point we don't test Additional Data records. We may
+    // consider implementing tests for it in the future.
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises invalid Opcode (is not a DNS Update).
+TEST_F(D2UpdateMessageTest, fromWireInvalidOpcode) {
+    // This is a binary representation of the DNS message.
+    // It comprises invalid Opcode=3, expected value is 6
+    // (Update).
+    const uint8_t bin_msg[] = {
+        0x05, 0xAF, // ID=0x05AF
+        0x98, 0x6,  // QR=1, Opcode=3, RCODE=YXDOMAIN
+        0x0, 0x0,   // ZOCOUNT=0
+        0x0, 0x0,   // PRCOUNT=0
+        0x0, 0x0,   // UPCOUNT=0
+        0x0, 0x0    // ADCOUNT=0
+    };
+    InputBuffer buf(bin_msg, sizeof(bin_msg));
+    // The 'true' argument passed to the constructor turns the
+    // message into the parse mode in which the fromWire function
+    // can be used to decode the binary mesasage data.
+    D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+    // When using invalid Opcode, the fromWire function should
+    // throw NotUpdateMessage exception.
+    EXPECT_THROW(msg.fromWire(buf), isc::d2::NotUpdateMessage);
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises invalid QR flag. The QR bit is
+// expected to be set to indicate that the message is a RESPONSE.
+TEST_F(D2UpdateMessageTest, fromWireInvalidQRFlag) {
+    // This is a binary representation of the DNS message.
+    // It comprises invalid QR flag = 0.
+    const uint8_t bin_msg[] = {
+        0x05, 0xAF, // ID=0x05AF
+        0x28, 0x6,  // QR=0, Opcode=6, RCODE=YXDOMAIN
+        0x0, 0x0,   // ZOCOUNT=0
+        0x0, 0x0,   // PRCOUNT=0
+        0x0, 0x0,   // UPCOUNT=0
+        0x0, 0x0    // ADCOUNT=0
+    };
+    InputBuffer buf(bin_msg, sizeof(bin_msg));
+    // The 'true' argument passed to the constructor turns the
+    // message into the parse mode in which the fromWire function
+    // can be used to decode the binary mesasage data.
+    D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+    // When using invalid QR flag, the fromWire function should
+    // throw InvalidQRFlag exception.
+    EXPECT_THROW(msg.fromWire(buf), isc::d2::InvalidQRFlag);
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises more than one (two in this case)
+// Zone records.
+TEST_F(D2UpdateMessageTest, fromWireTooManyZones) {
+    // This is a binary representation of the DNS message. This message
+    // comprises two Zone records.
+    const uint8_t bin_msg[] = {
+        0x05, 0xAF, // ID=0x05AF
+        0xA8, 0x6,  // QR=1, Opcode=6, RCODE=YXDOMAIN
+        0x0, 0x2,   // ZOCOUNT=2
+        0x0, 0x0,   // PRCOUNT=0
+        0x0, 0x0,   // UPCOUNT=0
+        0x0, 0x0,   // ADCOUNT=0
+
+        // Start first Zone record.
+        0x7, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example (7 is length)
+        0x3, 0x63, 0x6F, 0x6D, //.com. (0x3 is a length)
+        0x0,      // NULL character terminates the Zone name.
+        0x0, 0x6, // ZTYPE='SOA'
+        0x0, 0x1, // ZCLASS='IN'
+
+        // Start second Zone record. Presence of this record should result
+        // in error when parsing this message.
+        0x3, 0x63, 0x6F, 0x6D, // com. (0x3 is a length)
+        0x0,      // NULL character terminates the Zone name.
+        0x0, 0x6, // ZTYPE='SOA'
+        0x0, 0x1  // ZCLASS='IN'
+    };
+    InputBuffer buf(bin_msg, sizeof(bin_msg));
+
+    // The 'true' argument passed to the constructor turns the
+    // message into the parse mode in which the fromWire function
+    // can be used to decode the binary mesasage data.
+    D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+    // When parsing a message with more than one Zone record,
+    // exception should be thrown.
+    EXPECT_THROW(msg.fromWire(buf), isc::d2::InvalidZoneSection);
+}
+
+// This test verifies that the wire format of the message is produced
+// in the render mode.
+TEST_F(D2UpdateMessageTest, toWire) {
+    D2UpdateMessage msg;
+    // Set message ID.
+    msg.setId(0x1234);
+    // Rcode to NOERROR.
+    msg.setRcode(Rcode(Rcode::NOERROR_CODE));
+
+    // Set Zone section. This section must comprise exactly
+    // one Zone. toWire function would fail if Zone is not set.
+    msg.setZone(Name("example.com"), RRClass::IN());
+
+    // Set prerequisities.
+
+    // 'Name Is Not In Use' prerequisite (RFC 2136, section 2.4.5)
+    RRsetPtr prereq1(new RRset(Name("foo.example.com"), RRClass::NONE(),
+                               RRType::ANY(), RRTTL(0)));
+    msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq1);
+
+    // 'Name is In Use' prerequisite (RFC 2136, section 2.4.4)
+    RRsetPtr prereq2(new RRset(Name("bar.example.com"), RRClass::ANY(),
+                               RRType::ANY(), RRTTL(0)));
+    msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq2);
+
+    // Set Update Section.
+
+    // Create RR holding a name being added. This RR is constructed
+    // in conformance to RFC 2136, section 2.5.1.
+    RRsetPtr updaterr1(new RRset(Name("foo.example.com"), RRClass::IN(),
+                                 RRType::A(), RRTTL(10)));
+    // RR record is of the type A, thus RDATA holds 4 octet Internet
+    // address. This address is 10.10.1.1.
+    char rdata1[] = {
+        0xA, 0xA , 0x1, 0x1
+    };
+    InputBuffer buf_rdata1(rdata1, 4);
+    updaterr1->addRdata(createRdata(RRType::A(), RRClass::IN(), buf_rdata1,
+                                    buf_rdata1.getLength()));
+    // Add the RR to the message.
+    msg.addRRset(D2UpdateMessage::SECTION_UPDATE, updaterr1);
+
+    // Render message into the wire format.
+    MessageRenderer renderer;
+    ASSERT_NO_THROW(msg.toWire(renderer));
+
+    // Make sure that created packet is not truncated.
+    ASSERT_EQ(77, renderer.getLength());
+
+    // Create input buffer from the rendered data. InputBuffer
+    // is handy to validate the byte contents of the rendered
+    // message.
+    InputBuffer buf(renderer.getData(), renderer.getLength());
+
+    // Start validating the message header.
+
+    // Verify message ID.
+    EXPECT_EQ(0x1234, buf.readUint16());
+    // The 2-bytes following message ID comprise the following fields:
+    // - QR - 1 bit indicating that it is REQUEST. Should be 0.
+    // - Opcode - 4 bits which should hold value of 5 indicating this is
+    //            an Update message. Binary form is "0101".
+    // - Z - These bits are unused for Update Message and should be 0.
+    // - RCODE - Response code, set to NOERROR for REQUEST. It is 0.
+    //8706391835
+    // The binary value is:
+    //   0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+    // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+    // | QR|     Opcode    |           Z               |     RCODE     |
+    // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+    // | 0 | 0   1   0   1 | 0   0   0   0   0   0   0 | 0   0   0   0 |
+    // +---+---+---+-------+---+---+---+---+---+---+---+---+---+---+---+
+    // and the hexadecimal representation is 0x2800.
+    EXPECT_EQ(0x2800, buf.readUint16());
+
+    // ZOCOUNT - holds the number of zones for the update. For Request
+    // message it must be exactly one record (RFC2136, section 2.3).
+    EXPECT_EQ(1, buf.readUint16());
+
+    // PRCOUNT - holds the number of prerequisites. Earlier we have added
+    // two prerequisites. Thus, expect that this conter is 2.
+    EXPECT_EQ(2, buf.readUint16());
+
+    // UPCOUNT - holds the number of RRs in the Update Section. We have
+    // added 1 RR, which adds the name foo.example.com to the Zone.
+    EXPECT_EQ(1, buf.readUint16());
+
+    // ADCOUNT - holds the number of RRs in the Additional Data Section.
+    EXPECT_EQ(0, buf.readUint16());
+
+    // Start validating the Zone section. This section comprises the
+    // following data:
+    // - ZNAME
+    // - ZTYPE
+    // - ZCLASS
+
+    // ZNAME holds 'example.com.' encoded as set of labels. Each label
+    // is preceded by its length. The name is ended with the byte holding
+    // zero value. This yields the total size of the name in wire format
+    // of 13 bytes.
+
+    // The simplest way to convert the name from wire format to a string
+    // is to use dns::Name class. It should be ok to rely on the Name class
+    // to decode the name, because it is unit tested elswhere.
+    std::string zone_name = readNameFromWire(buf, 13);
+    EXPECT_EQ("example.com.", zone_name);
+
+    // ZTYPE of the Zone section must be SOA according to RFC 2136,
+    // section 2.3.
+    EXPECT_EQ(RRType::SOA().getCode(), buf.readUint16());
+
+    // ZCLASS of the Zone section is IN.
+    EXPECT_EQ(RRClass::IN().getCode(), buf.readUint16());
+
+    // Start checks on Prerequisite section. Each prerequisite comprises
+    // the following fields:
+    // - NAME - name of the RR in wire format
+    // - TYPE - two octets with one of the RR TYPE codes
+    // - CLASS - two octets with one of the RR CLASS codes
+    // - TTL - a 32-bit signed integer specifying Time-To-Live
+    // - RDLENGTH - length of the RDATA field
+    // - RDATA - a variable length string of octets containing
+    //           resource data.
+    // In case of this message, we expect to have two prerequisite RRs.
+    // Their structure is checked below.
+
+    // First prerequisite should comprise the 'Name is not in use prerequisite'
+    // for 'foo.example.com'.
+
+    // Check the name first. Message renderer is using compression for domain
+    // names as described in RFC 1035, section 4.1.4. The name in this RR is
+    // foo.example.com. The name of the zone is example.com and it has occured
+    // in this message already at offset 12 (the size of the header is 12).
+    // Therefore, name of this RR is encoded as 'foo', followed by a pointer
+    // to offset in this message where the remainder of this name was used.
+    // This pointer has the following format:
+    // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    // | 1  1|                 OFFSET                  |
+    // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    // | 1  1| 0  0  0  0  0  0  0  0  0  0  1  1  0  0|
+    // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    // which has a following hexadecimal representation: 0xC00C
+
+    // Let's read the non-compressed part first - 'foo.'
+    std::string name_prereq1 = readNameFromWire(buf, 4, true);
+    EXPECT_EQ("foo.", name_prereq1);
+    // The remaining two bytes hold the pointer to 'example.com'.
+    EXPECT_EQ(0xC00C, buf.readUint16());
+    // TYPE is ANY
+    EXPECT_EQ(RRType::ANY().getCode(), buf.readUint16());
+    // CLASS is NONE
+    EXPECT_EQ(RRClass::NONE().getCode(), buf.readUint16());
+    // TTL is a 32-but value, expecting 0
+    EXPECT_EQ(0, buf.readUint32());
+    // There is no RDATA, so RDLENGTH is 0
+    EXPECT_EQ(0, buf.readUint16());
+
+    // Start checking second prerequisite.
+
+    std::string name_prereq2 = readNameFromWire(buf, 4, true);
+    EXPECT_EQ("bar.", name_prereq2);
+    // The remaining two bytes hold the pointer to 'example.com'.
+    EXPECT_EQ(0xC00C, buf.readUint16());
+    // TYPE is ANY
+    EXPECT_EQ(RRType::ANY().getCode(), buf.readUint16());
+    // CLASS is ANY
+    EXPECT_EQ(RRClass::ANY().getCode(), buf.readUint16());
+    // TTL is a 32-but value, expecting 0
+    EXPECT_EQ(0, buf.readUint32());
+    // There is no RDATA, so RDLENGTH is 0
+    EXPECT_EQ(0, buf.readUint16());
+
+    // Start checking Update section. This section contains RRset with
+    // one A RR.
+
+    // The name of the RR is 'foo.example.com'. It is encoded in the
+    // compressed format - as a pointer to the name of prerequisite 1.
+    // This name is in offset 0x1D in this message.
+    EXPECT_EQ(0xC01D, buf.readUint16());
+    // TYPE is A
+    EXPECT_EQ(RRType::A().getCode(), buf.readUint16());
+    // CLASS is IN (same as zone class)
+    EXPECT_EQ(RRClass::IN().getCode(), buf.readUint16());
+    // TTL is a 32-but value, set here to 10.
+    EXPECT_EQ(10, buf.readUint32());
+    // For A records, the RDATA comprises the 4-byte Internet address.
+    // So, RDLENGTH is 4.
+    EXPECT_EQ(4, buf.readUint16());
+    // We have stored the following address in RDATA field: 10.10.1.1
+    // (which is 0A 0A 01 01) in hexadecimal format.
+    EXPECT_EQ(0x0A0A0101, buf.readUint32());
+
+    // @todo: consider extending this test to verify Additional Data
+    // section.
+}
+
+// This test verifies that an attempt to call toWire function on the
+// received message will result in an exception.
+TEST_F(D2UpdateMessageTest, toWireInvalidQRFlag) {
+    // This is a binary representation of the DNS message.
+    // This message is valid and should be parsed with no
+    // error.
+    const uint8_t bin_msg[] = {
+        0x05, 0xAF, // ID=0x05AF
+        0xA8, 0x6,  // QR=1, Opcode=6, RCODE=YXDOMAIN
+        0x0, 0x0,   // ZOCOUNT=0
+        0x0, 0x0,   // PRCOUNT=0
+        0x0, 0x0,   // UPCOUNT=0
+        0x0, 0x0    // ADCOUNT=0
+    };
+
+    InputBuffer buf(bin_msg, sizeof(bin_msg));
+    // The 'true' argument passed to the constructor turns the
+    // message into the parse mode in which the fromWire function
+    // can be used to decode the binary mesasage data.
+    D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+    ASSERT_NO_THROW(msg.fromWire(buf));
+
+    // The message is parsed. The QR Flag should now indicate that
+    // it is a Response message.
+    ASSERT_EQ(D2UpdateMessage::RESPONSE, msg.getQRFlag());
+
+    // An attempt to call toWire on the Response message should
+    // result in the InvalidQRFlag exception.
+    MessageRenderer renderer;
+    EXPECT_THROW(msg.toWire(renderer), isc::d2::InvalidQRFlag);
+}
+
+} // End of anonymous namespace

+ 75 - 0
src/bin/d2/tests/d2_zone_unittests.cc

@@ -0,0 +1,75 @@
+// 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 <d2/d2_zone.h>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::dns;
+
+namespace {
+
+// This test verifies that Zone object is created and its constructor sets
+// appropriate values for its members.
+TEST(D2ZoneTest, constructor) {
+    // Create first object.
+    D2Zone zone1(Name("example.com"), RRClass::ANY());
+    EXPECT_EQ("example.com.", zone1.getName().toText());
+    EXPECT_EQ(RRClass::ANY().getCode(), zone1.getClass().getCode());
+    // Create another object to make sure that constructor doesn't assign
+    // fixed values, but they change when constructor's parameters change.
+    D2Zone zone2(Name("foo.example.com"), RRClass::IN());
+    EXPECT_EQ("foo.example.com.", zone2.getName().toText());
+    EXPECT_EQ(RRClass::IN().getCode(), zone2.getClass().getCode());
+}
+
+// This test verifies that toText() function returns text representation of
+// of the zone in expected format.
+TEST(D2ZoneTest, toText) {
+    // Create first object.
+    D2Zone zone1(Name("example.com"), RRClass::ANY());
+    EXPECT_EQ("example.com. ANY SOA\n", zone1.toText());
+    // Create another object with different parameters to make sure that the
+    // function's output changes accordingly.
+    D2Zone zone2(Name("foo.example.com"), RRClass::IN());
+    EXPECT_EQ("foo.example.com. IN SOA\n", zone2.toText());
+}
+
+// This test verifies that the equality and inequality operators behave as
+// expected.
+TEST(D2ZoneTest, compare) {
+    const Name a("a"), b("b");
+    const RRClass in(RRClass::IN()), any(RRClass::ANY());
+
+    // Equality check
+    EXPECT_TRUE(D2Zone(a, any) == D2Zone(a, any));
+    EXPECT_FALSE(D2Zone(a, any) != D2Zone(a, any));
+
+    // Inequality check, objects differ by class.
+    EXPECT_FALSE(D2Zone(a, any) == D2Zone(a, in));
+    EXPECT_TRUE(D2Zone(a, any) != D2Zone(a, in));
+
+    // Inequality check, objects differ by name.
+    EXPECT_FALSE(D2Zone(a, any) == D2Zone(b, any));
+    EXPECT_TRUE(D2Zone(a, any) != D2Zone(b, any));
+
+    // Inequality check, objects differ by name and class.
+    EXPECT_FALSE(D2Zone(a, any) == D2Zone(b, in));
+    EXPECT_TRUE(D2Zone(a, any) != D2Zone(b, in));
+}
+
+} // End of anonymous namespace