Browse Source

[trac893] Merge branch 'trac813' into trac893

JINMEI Tatuya 14 years ago
parent
commit
1b967a5870
45 changed files with 1757 additions and 336 deletions
  1. 1 0
      src/lib/dns/Makefile.am
  2. 21 8
      src/lib/dns/edns.cc
  3. 2 8
      src/lib/dns/edns.h
  4. 248 156
      src/lib/dns/message.cc
  5. 52 3
      src/lib/dns/message.h
  6. 1 1
      src/lib/dns/python/tests/message_python_test.py
  7. 1 1
      src/lib/dns/question.cc
  8. 3 3
      src/lib/dns/question.h
  9. 2 10
      src/lib/dns/rdata/any_255/tsig_250.cc
  10. 2 2
      src/lib/dns/rrclass-placeholder.h
  11. 1 1
      src/lib/dns/rrclass.cc
  12. 3 3
      src/lib/dns/rrset.cc
  13. 3 3
      src/lib/dns/rrset.h
  14. 1 1
      src/lib/dns/rrttl.cc
  15. 2 2
      src/lib/dns/rrttl.h
  16. 1 0
      src/lib/dns/tests/Makefile.am
  17. 190 1
      src/lib/dns/tests/message_unittest.cc
  18. 3 0
      src/lib/dns/tests/run_unittests.cc
  19. 17 4
      src/lib/dns/tests/testdata/Makefile.am
  20. 82 9
      src/lib/dns/tests/testdata/gen-wiredata.py.in
  21. 21 0
      src/lib/dns/tests/testdata/message_fromWire12.spec
  22. 20 0
      src/lib/dns/tests/testdata/message_fromWire13.spec
  23. 21 0
      src/lib/dns/tests/testdata/message_fromWire14.spec
  24. 22 0
      src/lib/dns/tests/testdata/message_fromWire15.spec
  25. 21 0
      src/lib/dns/tests/testdata/message_fromWire16.spec
  26. 24 0
      src/lib/dns/tests/testdata/message_toText1.spec
  27. 14 0
      src/lib/dns/tests/testdata/message_toText1.txt
  28. 14 0
      src/lib/dns/tests/testdata/message_toText2.spec
  29. 31 0
      src/lib/dns/tests/testdata/message_toText3.spec
  30. 21 0
      src/lib/dns/tests/testdata/message_toWire2.spec
  31. 22 0
      src/lib/dns/tests/testdata/message_toWire3.spec
  32. 16 0
      src/lib/dns/tests/testdata/tsigrecord_toWire1.spec
  33. 19 0
      src/lib/dns/tests/testdata/tsigrecord_toWire2.spec
  34. 15 15
      src/lib/dns/tests/tsig_unittest.cc
  35. 161 0
      src/lib/dns/tests/tsigrecord_unittest.cc
  36. 4 29
      src/lib/dns/tsig.cc
  37. 1 71
      src/lib/dns/tsig.h
  38. 145 0
      src/lib/dns/tsigrecord.cc
  39. 285 0
      src/lib/dns/tsigrecord.h
  40. 2 5
      src/lib/util/time_utilities.cc
  41. 28 0
      src/lib/util/time_utilities.h
  42. 2 0
      src/lib/util/unittests/Makefile.am
  43. 61 0
      src/lib/util/unittests/testdata.cc
  44. 46 0
      src/lib/util/unittests/testdata.h
  45. 105 0
      src/lib/util/unittests/textdata.h

+ 1 - 0
src/lib/dns/Makefile.am

@@ -87,6 +87,7 @@ libdns___la_SOURCES += question.h question.cc
 libdns___la_SOURCES += tsig.h tsig.cc
 libdns___la_SOURCES += tsigerror.h tsigerror.cc
 libdns___la_SOURCES += tsigkey.h tsigkey.cc
+libdns___la_SOURCES += tsigrecord.h tsigrecord.cc
 libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
 libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
 

+ 21 - 8
src/lib/dns/edns.cc

@@ -110,19 +110,25 @@ EDNS::toText() const {
     return (ret);
 }
 
+namespace {
+/// Helper function to define unified implementation for the public versions
+/// of toWire().
 template <typename Output>
 int
-EDNS::toWire(Output& output, const uint8_t extended_rcode) const {
+toWireCommon(Output& output, const uint8_t version,
+             const uint16_t udp_size, const bool dnssec_aware,
+             const uint8_t extended_rcode)
+{
     // Render EDNS OPT RR
     uint32_t extrcode_flags = extended_rcode << EXTRCODE_SHIFT;
-    extrcode_flags |= (version_ << VERSION_SHIFT) & VERSION_MASK;
-    if (dnssec_aware_) {
+    extrcode_flags |= (version << VERSION_SHIFT) & VERSION_MASK;
+    if (dnssec_aware) {
         extrcode_flags |= EXTFLAG_DO;
     }
 
     // Construct an RRset corresponding to the EDNS.
     // We don't support any options for now, so the OPT RR can be empty.
-    RRsetPtr edns_rrset(new RRset(Name::ROOT_NAME(), RRClass(udp_size_),
+    RRsetPtr edns_rrset(new RRset(Name::ROOT_NAME(), RRClass(udp_size),
                                   RRType::OPT(), RRTTL(extrcode_flags)));
     edns_rrset->addRdata(ConstRdataPtr(new generic::OPT()));
 
@@ -130,9 +136,12 @@ EDNS::toWire(Output& output, const uint8_t extended_rcode) const {
 
     return (1);
 }
+}
 
 unsigned int
-EDNS::toWire(MessageRenderer& renderer, const uint8_t extended_rcode) const {
+EDNS::toWire(AbstractMessageRenderer& renderer,
+             const uint8_t extended_rcode) const
+{
     // If adding the OPT RR would exceed the size limit, don't do it.
     // 11 = len(".") + type(2byte) + class(2byte) + TTL(4byte) + RDLEN(2byte)
     // (RDATA is empty in this simple implementation)
@@ -140,12 +149,16 @@ EDNS::toWire(MessageRenderer& renderer, const uint8_t extended_rcode) const {
         return (0);
     }
 
-    return (toWire<MessageRenderer>(renderer, extended_rcode));
+    return (toWireCommon(renderer, version_, udp_size_, dnssec_aware_,
+                         extended_rcode));
 }
 
 unsigned int
-EDNS::toWire(isc::util::OutputBuffer& buffer, const uint8_t extended_rcode) const {
-    return (toWire<isc::util::OutputBuffer>(buffer, extended_rcode));
+EDNS::toWire(isc::util::OutputBuffer& buffer,
+             const uint8_t extended_rcode) const
+{
+    return (toWireCommon(buffer, version_, udp_size_, dnssec_aware_,
+                         extended_rcode));
 }
 
 EDNS*

+ 2 - 8
src/lib/dns/edns.h

@@ -32,7 +32,7 @@ namespace dns {
 
 class EDNS;
 class Name;
-class MessageRenderer;
+class AbstractMessageRenderer;
 class RRClass;
 class RRTTL;
 class RRType;
@@ -314,7 +314,7 @@ public:
     /// \param extended_rcode Upper 8 bits of extended RCODE to be rendered as
     /// part of the EDNS OPT RR.
     /// \return 1 if the OPT RR fits in the message size limit; otherwise 0.
-    unsigned int toWire(MessageRenderer& renderer,
+    unsigned int toWire(AbstractMessageRenderer& renderer,
                         const uint8_t extended_rcode) const;
 
     /// \brief Render the \c EDNS in the wire format.
@@ -354,12 +354,6 @@ public:
     // something like this.
     //void addOption();
 
-private:
-    /// Helper method to define unified implementation for the public versions
-    /// of toWire().
-    template <typename Output>
-    int toWire(Output& output, const uint8_t extended_rcode) const;
-
 public:
     /// \brief The highest EDNS version this implementation supports.
     static const uint8_t SUPPORTED_VERSION = 0;

+ 248 - 156
src/lib/dns/message.cc

@@ -15,6 +15,7 @@
 #include <stdint.h>
 
 #include <algorithm>
+#include <cassert>
 #include <string>
 #include <sstream>
 #include <vector>
@@ -40,6 +41,7 @@
 #include <dns/rrtype.h>
 #include <dns/rrttl.h>
 #include <dns/rrset.h>
+#include <dns/tsig.h>
 
 using namespace std;
 using namespace boost;
@@ -81,7 +83,7 @@ const unsigned int HEADERFLAG_MASK = 0x87b0;
 const uint16_t MESSAGE_REPLYPRESERVE = (Message::HEADERFLAG_RD |
                                         Message::HEADERFLAG_CD);
 
-const char *sectiontext[] = {
+const char* const sectiontext[] = {
     "QUESTION",
     "ANSWER",
     "AUTHORITY",
@@ -114,8 +116,8 @@ public:
     vector<QuestionPtr> questions_;
     vector<RRsetPtr> rrsets_[NUM_SECTIONS];
     ConstEDNSPtr edns_;
+    ConstTSIGRecordPtr tsig_rr_;
 
-    // tsig/sig0: TODO
     // RRsetsSorter* sorter_; : TODO
 
     void init();
@@ -123,6 +125,17 @@ public:
     void setRcode(const Rcode& rcode);
     int parseQuestion(InputBuffer& buffer);
     int parseSection(const Message::Section section, InputBuffer& buffer);
+    void addRR(Message::Section section, const Name& name,
+               const RRClass& rrclass, const RRType& rrtype,
+               const RRTTL& ttl, ConstRdataPtr rdata);
+    void addEDNS(Message::Section section, const Name& name,
+                 const RRClass& rrclass, const RRType& rrtype,
+                 const RRTTL& ttl, const Rdata& rdata);
+    void addTSIG(Message::Section section, unsigned int count,
+                 const InputBuffer& buffer, size_t start_position,
+                 const Name& name, const RRClass& rrclass,
+                 const RRTTL& ttl, const Rdata& rdata);
+    void toWire(AbstractMessageRenderer& renderer, TSIGContext* tsig_ctx);
 };
 
 MessageImpl::MessageImpl(Message::Mode mode) :
@@ -140,6 +153,7 @@ MessageImpl::init() {
     rcode_ = NULL;
     opcode_ = NULL;
     edns_ = EDNSPtr();
+    tsig_rr_ = ConstTSIGRecordPtr();
 
     for (int i = 0; i < NUM_SECTIONS; ++i) {
         counts_[i] = 0;
@@ -164,6 +178,139 @@ MessageImpl::setRcode(const Rcode& rcode) {
     rcode_ = &rcode_placeholder_;
 }
 
+namespace {
+template <typename T>
+struct RenderSection {
+    RenderSection(AbstractMessageRenderer& renderer, const bool partial_ok) :
+        counter_(0), renderer_(renderer), partial_ok_(partial_ok),
+        truncated_(false)
+    {}
+    void operator()(const T& entry) {
+        // If it's already truncated, ignore the rest of the section.
+        if (truncated_) {
+            return;
+        }
+        const size_t pos0 = renderer_.getLength();
+        counter_ += entry->toWire(renderer_);
+        if (renderer_.isTruncated()) {
+            truncated_ = true;
+            if (!partial_ok_) {
+                // roll back to the end of the previous RRset.
+                renderer_.trim(renderer_.getLength() - pos0);
+            }
+        }
+    }
+    unsigned int getTotalCount() { return (counter_); }
+    unsigned int counter_;
+    AbstractMessageRenderer& renderer_;
+    const bool partial_ok_;
+    bool truncated_;
+};
+}
+
+void
+MessageImpl::toWire(AbstractMessageRenderer& renderer, TSIGContext* tsig_ctx) {
+    if (mode_ != Message::RENDER) {
+        isc_throw(InvalidMessageOperation,
+                  "Message rendering attempted in non render mode");
+    }
+    if (rcode_ == NULL) {
+        isc_throw(InvalidMessageOperation,
+                  "Message rendering attempted without Rcode set");
+    }
+    if (opcode_ == NULL) {
+        isc_throw(InvalidMessageOperation,
+                  "Message rendering attempted without Opcode set");
+    }
+
+    // reserve room for the header
+    renderer.skip(HEADERLEN);
+
+    uint16_t qdcount =
+        for_each(questions_.begin(), questions_.end(),
+                 RenderSection<QuestionPtr>(renderer, false)).getTotalCount();
+
+    // TBD: sort RRsets in each section based on configuration policy.
+    uint16_t ancount = 0;
+    if (!renderer.isTruncated()) {
+        ancount =
+            for_each(rrsets_[Message::SECTION_ANSWER].begin(),
+                     rrsets_[Message::SECTION_ANSWER].end(),
+                     RenderSection<RRsetPtr>(renderer, true)).getTotalCount();
+    }
+    uint16_t nscount = 0;
+    if (!renderer.isTruncated()) {
+        nscount =
+            for_each(rrsets_[Message::SECTION_AUTHORITY].begin(),
+                     rrsets_[Message::SECTION_AUTHORITY].end(),
+                     RenderSection<RRsetPtr>(renderer, true)).getTotalCount();
+    }
+    uint16_t arcount = 0;
+    if (renderer.isTruncated()) {
+        flags_ |= Message::HEADERFLAG_TC;
+    } else {
+        arcount =
+            for_each(rrsets_[Message::SECTION_ADDITIONAL].begin(),
+                     rrsets_[Message::SECTION_ADDITIONAL].end(),
+                     RenderSection<RRsetPtr>(renderer, false)).getTotalCount();
+    }
+
+    // Add EDNS OPT RR if necessary.  Basically, we add it only when EDNS
+    // has been explicitly set.  However, if the RCODE would require it and
+    // no EDNS has been set we generate a temporary local EDNS and use it.
+    if (!renderer.isTruncated()) {
+        ConstEDNSPtr local_edns = edns_;
+        if (!local_edns && rcode_->getExtendedCode() != 0) {
+            local_edns = ConstEDNSPtr(new EDNS());
+        }
+        if (local_edns) {
+            arcount += local_edns->toWire(renderer, rcode_->getExtendedCode());
+        }
+    }
+
+    // Adjust the counter buffer.
+    // XXX: these may not be equal to the number of corresponding entries
+    // in rrsets_[] or questions_ if truncation occurred or an EDNS OPT RR
+    // was inserted.  This is not good, and we should revisit the entire
+    // design.
+    counts_[Message::SECTION_QUESTION] = qdcount;
+    counts_[Message::SECTION_ANSWER] = ancount;
+    counts_[Message::SECTION_AUTHORITY] = nscount;
+    counts_[Message::SECTION_ADDITIONAL] = arcount;
+
+    // fill in the header
+    size_t header_pos = 0;
+    renderer.writeUint16At(qid_, header_pos);
+    header_pos += sizeof(uint16_t);
+
+    uint16_t codes_and_flags =
+        (opcode_->getCode() << OPCODE_SHIFT) & OPCODE_MASK;
+    codes_and_flags |= (rcode_->getCode() & RCODE_MASK);
+    codes_and_flags |= (flags_ & HEADERFLAG_MASK);
+    renderer.writeUint16At(codes_and_flags, header_pos);
+    header_pos += sizeof(uint16_t);
+    // XXX: should avoid repeated pattern (TODO)
+    renderer.writeUint16At(qdcount, header_pos);
+    header_pos += sizeof(uint16_t);
+    renderer.writeUint16At(ancount, header_pos);
+    header_pos += sizeof(uint16_t);
+    renderer.writeUint16At(nscount, header_pos);
+    header_pos += sizeof(uint16_t);
+    renderer.writeUint16At(arcount, header_pos);
+
+    // Add TSIG, if necessary, at the end of the message.
+    // TBD: truncate case consideration
+    if (tsig_ctx != NULL) {
+        tsig_ctx->sign(qid_, renderer.getData(),
+                       renderer.getLength())->toWire(renderer);
+
+        // update the ARCOUNT for the TSIG RR
+        ++arcount;
+        assert(arcount != 0);   // this should never happen for a sane message
+        renderer.writeUint16At(arcount, header_pos);
+    }
+}
+
 Message::Message(Mode mode) :
     impl_(new MessageImpl(mode))
 {}
@@ -262,6 +409,16 @@ Message::setEDNS(ConstEDNSPtr edns) {
     impl_->edns_ = edns;
 }
 
+const TSIGRecord*
+Message::getTSIGRecord() const {
+    if (impl_->mode_ != Message::PARSE) {
+        isc_throw(InvalidMessageOperation,
+                  "getTSIGRecord performed in non-parse mode");
+    }
+
+    return (impl_->tsig_rr_.get());
+}
+
 unsigned int
 Message::getRRCount(const Section section) const {
     if (section >= MessageImpl::NUM_SECTIONS) {
@@ -363,129 +520,14 @@ Message::addQuestion(const Question& question) {
     addQuestion(QuestionPtr(new Question(question)));
 }
 
-namespace {
-template <typename T>
-struct RenderSection {
-    RenderSection(MessageRenderer& renderer, const bool partial_ok) :
-        counter_(0), renderer_(renderer), partial_ok_(partial_ok),
-        truncated_(false)
-    {}
-    void operator()(const T& entry) {
-        // If it's already truncated, ignore the rest of the section.
-        if (truncated_) {
-            return;
-        }
-        const size_t pos0 = renderer_.getLength();
-        counter_ += entry->toWire(renderer_);
-        if (renderer_.isTruncated()) {
-            truncated_ = true;
-            if (!partial_ok_) {
-                // roll back to the end of the previous RRset.
-                renderer_.trim(renderer_.getLength() - pos0);
-            }
-        }
-    }
-    unsigned int getTotalCount() { return (counter_); }
-    unsigned int counter_;
-    MessageRenderer& renderer_;
-    const bool partial_ok_;
-    bool truncated_;
-};
+void
+Message::toWire(AbstractMessageRenderer& renderer) {
+    impl_->toWire(renderer, NULL);
 }
 
 void
-Message::toWire(MessageRenderer& renderer) {
-    if (impl_->mode_ != Message::RENDER) {
-        isc_throw(InvalidMessageOperation,
-                  "Message rendering attempted in non render mode");
-    }
-    if (impl_->rcode_ == NULL) {
-        isc_throw(InvalidMessageOperation,
-                  "Message rendering attempted without Rcode set");
-    }
-    if (impl_->opcode_ == NULL) {
-        isc_throw(InvalidMessageOperation,
-                  "Message rendering attempted without Opcode set");
-    }
-
-    // reserve room for the header
-    renderer.skip(HEADERLEN);
-
-    uint16_t qdcount =
-        for_each(impl_->questions_.begin(), impl_->questions_.end(),
-                 RenderSection<QuestionPtr>(renderer, false)).getTotalCount();
-
-    // TBD: sort RRsets in each section based on configuration policy.
-    uint16_t ancount = 0;
-    if (!renderer.isTruncated()) {
-        ancount =
-            for_each(impl_->rrsets_[SECTION_ANSWER].begin(),
-                     impl_->rrsets_[SECTION_ANSWER].end(),
-                     RenderSection<RRsetPtr>(renderer, true)).getTotalCount();
-    }
-    uint16_t nscount = 0;
-    if (!renderer.isTruncated()) {
-        nscount =
-            for_each(impl_->rrsets_[SECTION_AUTHORITY].begin(),
-                     impl_->rrsets_[SECTION_AUTHORITY].end(),
-                     RenderSection<RRsetPtr>(renderer, true)).getTotalCount();
-    }
-    uint16_t arcount = 0;
-    if (renderer.isTruncated()) {
-        setHeaderFlag(HEADERFLAG_TC, true);
-    } else {
-        arcount =
-            for_each(impl_->rrsets_[SECTION_ADDITIONAL].begin(),
-                     impl_->rrsets_[SECTION_ADDITIONAL].end(),
-                     RenderSection<RRsetPtr>(renderer, false)).getTotalCount();
-    }
-
-    // Add EDNS OPT RR if necessary.  Basically, we add it only when EDNS
-    // has been explicitly set.  However, if the RCODE would require it and
-    // no EDNS has been set we generate a temporary local EDNS and use it.
-    if (!renderer.isTruncated()) {
-        ConstEDNSPtr local_edns = impl_->edns_;
-        if (!local_edns && impl_->rcode_->getExtendedCode() != 0) {
-            local_edns = ConstEDNSPtr(new EDNS());
-        }
-        if (local_edns) {
-            arcount += local_edns->toWire(renderer,
-                                          impl_->rcode_->getExtendedCode());
-        }
-    }
- 
-    // Adjust the counter buffer.
-    // XXX: these may not be equal to the number of corresponding entries
-    // in rrsets_[] or questions_ if truncation occurred or an EDNS OPT RR
-    // was inserted.  This is not good, and we should revisit the entire
-    // design.
-    impl_->counts_[SECTION_QUESTION] = qdcount;
-    impl_->counts_[SECTION_ANSWER] = ancount;
-    impl_->counts_[SECTION_AUTHORITY] = nscount;
-    impl_->counts_[SECTION_ADDITIONAL] = arcount;
-
-    // TBD: TSIG, SIG(0) etc.
-
-    // fill in the header
-    size_t header_pos = 0;
-    renderer.writeUint16At(impl_->qid_, header_pos);
-    header_pos += sizeof(uint16_t);
-
-    uint16_t codes_and_flags =
-        (impl_->opcode_->getCode() << OPCODE_SHIFT) & OPCODE_MASK;
-    codes_and_flags |= (impl_->rcode_->getCode() & RCODE_MASK);
-    codes_and_flags |= (impl_->flags_ & HEADERFLAG_MASK);
-    renderer.writeUint16At(codes_and_flags, header_pos);
-    header_pos += sizeof(uint16_t);
-    // XXX: should avoid repeated pattern (TODO)
-    renderer.writeUint16At(qdcount, header_pos);
-    header_pos += sizeof(uint16_t);
-    renderer.writeUint16At(ancount, header_pos);
-    header_pos += sizeof(uint16_t);
-    renderer.writeUint16At(nscount, header_pos);
-    header_pos += sizeof(uint16_t);
-    renderer.writeUint16At(arcount, header_pos);
-    header_pos += sizeof(uint16_t);
+Message::toWire(AbstractMessageRenderer& renderer, TSIGContext& tsig_ctx) {
+    impl_->toWire(renderer, &tsig_ctx);
 }
 
 void
@@ -613,6 +655,9 @@ MessageImpl::parseSection(const Message::Section section,
     unsigned int added = 0;
 
     for (unsigned int count = 0; count < counts_[section]; ++count) {
+        // We need to remember the start position for TSIG processing
+        const size_t start_position = buffer.getPosition();
+
         const Name name(buffer);
 
         // buffer must store at least RR TYPE, RR CLASS, TTL, and RDLEN.
@@ -630,32 +675,12 @@ MessageImpl::parseSection(const Message::Section section,
         ConstRdataPtr rdata = createRdata(rrtype, rrclass, buffer, rdlen);
 
         if (rrtype == RRType::OPT()) {
-            if (section != Message::SECTION_ADDITIONAL) {
-                isc_throw(DNSMessageFORMERR,
-                          "EDNS OPT RR found in an invalid section");
-            }
-            if (edns_) {
-                isc_throw(DNSMessageFORMERR, "multiple EDNS OPT RR found");
-            }
-
-            uint8_t extended_rcode;
-            edns_ = ConstEDNSPtr(createEDNSFromRR(name, rrclass, rrtype, ttl,
-                                                  *rdata, extended_rcode));
-            setRcode(Rcode(rcode_->getCode(), extended_rcode));
-            continue;
+            addEDNS(section, name, rrclass, rrtype, ttl, *rdata);
+        } else if (rrtype == RRType::TSIG()) {
+            addTSIG(section, count, buffer, start_position, name, rrclass, ttl,
+                    *rdata);
         } else {
-            vector<RRsetPtr>::iterator it =
-                find_if(rrsets_[section].begin(), rrsets_[section].end(),
-                        MatchRR(name, rrtype, rrclass));
-            if (it != rrsets_[section].end()) {
-                (*it)->setTTL(min((*it)->getTTL(), ttl));
-                (*it)->addRdata(rdata);
-            } else {
-                RRsetPtr rrset =
-                    RRsetPtr(new RRset(name, rrclass, rrtype, ttl)); 
-                rrset->addRdata(rdata);
-                rrsets_[section].push_back(rrset);
-            }
+            addRR(section, name, rrclass, rrtype, ttl, rdata);
             ++added;
         }
     }
@@ -663,6 +688,65 @@ MessageImpl::parseSection(const Message::Section section,
     return (added);
 }
 
+void
+MessageImpl::addRR(Message::Section section, const Name& name,
+                   const RRClass& rrclass, const RRType& rrtype,
+                   const RRTTL& ttl, ConstRdataPtr rdata)
+{
+    vector<RRsetPtr>::iterator it =
+        find_if(rrsets_[section].begin(), rrsets_[section].end(),
+                MatchRR(name, rrtype, rrclass));
+    if (it != rrsets_[section].end()) {
+        (*it)->setTTL(min((*it)->getTTL(), ttl));
+        (*it)->addRdata(rdata);
+    } else {
+        RRsetPtr rrset(new RRset(name, rrclass, rrtype, ttl));
+        rrset->addRdata(rdata);
+        rrsets_[section].push_back(rrset);
+    }
+}
+
+void
+MessageImpl::addEDNS(Message::Section section,  const Name& name,
+                     const RRClass& rrclass, const RRType& rrtype,
+                     const RRTTL& ttl, const Rdata& rdata)
+{
+    if (section != Message::SECTION_ADDITIONAL) {
+        isc_throw(DNSMessageFORMERR,
+                  "EDNS OPT RR found in an invalid section");
+    }
+    if (edns_) {
+        isc_throw(DNSMessageFORMERR, "multiple EDNS OPT RR found");
+    }
+
+    uint8_t extended_rcode;
+    edns_ = ConstEDNSPtr(createEDNSFromRR(name, rrclass, rrtype, ttl, rdata,
+                                          extended_rcode));
+    setRcode(Rcode(rcode_->getCode(), extended_rcode));
+}
+
+void
+MessageImpl::addTSIG(Message::Section section, unsigned int count,
+                     const InputBuffer& buffer, size_t start_position,
+                     const Name& name, const RRClass& rrclass,
+                     const RRTTL& ttl, const Rdata& rdata)
+{
+    if (section != Message::SECTION_ADDITIONAL) {
+        isc_throw(DNSMessageFORMERR,
+                  "TSIG RR found in an invalid section");
+    }
+    if (count != counts_[section] - 1) {
+        isc_throw(DNSMessageFORMERR, "TSIG RR is not the last record");
+    }
+    if (tsig_rr_) {
+        isc_throw(DNSMessageFORMERR, "multiple TSIG RRs found");
+    }
+    tsig_rr_ = ConstTSIGRecordPtr(new TSIGRecord(name, rrclass,
+                                                 ttl, rdata,
+                                                 buffer.getPosition() -
+                                                 start_position));
+}
+
 namespace {
 template <typename T>
 struct SectionFormatter {
@@ -696,31 +780,31 @@ Message::toText() const {
     // for simplicity we don't consider extended rcode (unlike BIND9)
     s += ", status: " + impl_->rcode_->toText();
     s += ", id: " + boost::lexical_cast<string>(impl_->qid_);
-    s += "\n;; flags: ";
+    s += "\n;; flags:";
     if (getHeaderFlag(HEADERFLAG_QR)) {
-        s += "qr ";
+        s += " qr";
     }
     if (getHeaderFlag(HEADERFLAG_AA)) {
-        s += "aa ";
+        s += " aa";
     }
     if (getHeaderFlag(HEADERFLAG_TC)) {
-        s += "tc ";
+        s += " tc";
     }
     if (getHeaderFlag(HEADERFLAG_RD)) {
-        s += "rd ";
+        s += " rd";
     }
     if (getHeaderFlag(HEADERFLAG_RA)) {
-        s += "ra ";
+        s += " ra";
     }
     if (getHeaderFlag(HEADERFLAG_AD)) {
-        s += "ad ";
+        s += " ad";
     }
     if (getHeaderFlag(HEADERFLAG_CD)) {
-        s += "cd ";
+        s += " cd";
     }
 
     // for simplicity, don't consider the update case for now
-    s += "; QUESTION: " +
+    s += "; QUERY: " + // note: not "QUESTION" to be compatible with BIND 9 dig
         lexical_cast<string>(impl_->counts_[SECTION_QUESTION]);
     s += ", ANSWER: " +
         lexical_cast<string>(impl_->counts_[SECTION_ANSWER]);
@@ -731,6 +815,9 @@ Message::toText() const {
     if (impl_->edns_ != NULL) {
         ++arcount;
     }
+    if (impl_->tsig_rr_ != NULL) {
+        ++arcount;
+    }
     s += ", ADDITIONAL: " + lexical_cast<string>(arcount) + "\n";
 
     if (impl_->edns_ != NULL) {
@@ -767,6 +854,11 @@ Message::toText() const {
                  SectionFormatter<RRsetPtr>(SECTION_ADDITIONAL, s));
     }
 
+    if (impl_->tsig_rr_ != NULL) {
+        s += "\n;; TSIG PSEUDOSECTION:\n";
+        s += impl_->tsig_rr_->toText();
+    }
+
     return (s);
 }
 

+ 52 - 3
src/lib/dns/message.h

@@ -33,6 +33,8 @@ class InputBuffer;
 }
 
 namespace dns {
+class TSIGContext;
+class TSIGRecord;
 
 ///
 /// \brief A standard DNS module exception that is thrown if a wire format
@@ -80,7 +82,7 @@ public:
 
 typedef uint16_t qid_t;
 
-class MessageRenderer;
+class AbstractMessageRenderer;
 class Message;
 class MessageImpl;
 class Opcode;
@@ -368,6 +370,25 @@ public:
     /// \c Message.
     void setEDNS(ConstEDNSPtr edns);
 
+    /// \brief Return, if any, the TSIG record contained in the received
+    /// message.
+    ///
+    /// Currently, this method is only intended to return a TSIG record
+    /// for an incoming message built via the \c fromWire() method in the
+    /// PARSE mode.  A call to this method in the RENDER mode is invalid and
+    /// result in an exception.  Also, calling this method is meaningless
+    /// unless \c fromWire() is performed.
+    ///
+    /// The returned pointer is valid only during the lifetime of the
+    /// \c Message object and until \c clear() is called.  The \c Message
+    /// object retains the ownership of \c TSIGRecord; the caller must not
+    /// try to delete it.
+    ///
+    /// \exception InvalidMessageOperation Message is not in the PARSE mode.
+    ///
+    /// \return A pointer to the stored \c TSIGRecord or \c NULL.
+    const TSIGRecord* getTSIGRecord() const;
+
     /// \brief Returns the number of RRs contained in the given section.
     ///
     /// In the \c PARSE mode, the returned value may not be identical to
@@ -523,13 +544,31 @@ public:
     /// class \c InvalidMessageOperation will be thrown.
     std::string toText() const;
 
-    /// \brief Render the message in wire formant into a \c MessageRenderer
+    /// \brief Render the message in wire formant into a message renderer
     /// object.
     ///
     /// This \c Message must be in the \c RENDER mode and both \c Opcode and
     /// \c Rcode must have been set beforehand; otherwise, an exception of
     /// class \c InvalidMessageOperation will be thrown.
-    void toWire(MessageRenderer& renderer);
+    ///
+    /// \param renderer DNS message rendering context that encapsulates the
+    /// output buffer and name compression information.
+    void toWire(AbstractMessageRenderer& renderer);
+
+    /// \brief Render the message in wire formant into a message renderer
+    /// object with TSIG.
+    ///
+    /// This method is similar to the other version of \c toWire(), but
+    /// it will also add a TSIG RR with (in many cases) the TSIG MAC for
+    /// the message along with the given TSIG context (\c tsig_ctx).
+    /// The TSIG RR will be placed at the end of \c renderer.
+    /// \c tsig_ctx will be updated based on the fact it was used for signing
+    /// and with the latest MAC.
+    ///
+    /// \param renderer See the other version
+    /// \param tsig_ctx A TSIG context that is to be used for signing the
+    /// message
+    void toWire(AbstractMessageRenderer& renderer, TSIGContext& tsig_ctx);
 
     /// \brief Parse the header section of the \c Message.
     void parseHeader(isc::util::InputBuffer& buffer);
@@ -563,6 +602,16 @@ private:
 /// that originated the asynchronous call falls out of scope.
 typedef boost::shared_ptr<Message> MessagePtr;
 
+/// Insert the \c Message as a string into stream.
+///
+/// This method convert \c message into a string and inserts it into the
+/// output stream \c os.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param record A \c Message 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 Message& message);
 }
 }

+ 1 - 1
src/lib/dns/python/tests/message_python_test.py

@@ -282,7 +282,7 @@ class MessageTest(unittest.TestCase):
         
         msg_str =\
 """;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4149
-;; flags: qr aa rd ; QUESTION: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
+;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
 
 ;; QUESTION SECTION:
 ;test.example.com. IN A

+ 1 - 1
src/lib/dns/question.cc

@@ -56,7 +56,7 @@ Question::toWire(OutputBuffer& buffer) const {
 }
 
 unsigned int
-Question::toWire(MessageRenderer& renderer) const {
+Question::toWire(AbstractMessageRenderer& renderer) const {
     renderer.writeName(name_);
     rrtype_.toWire(renderer);
     rrclass_.toWire(renderer);

+ 3 - 3
src/lib/dns/question.h

@@ -32,7 +32,7 @@ class OutputBuffer;
 
 namespace dns {
 
-class MessageRenderer;
+class AbstractMessageRenderer;
 class Question;
 
 /// \brief A pointer-like type pointing to an \c Question object.
@@ -218,13 +218,13 @@ public:
     /// \param renderer DNS message rendering context that encapsulates the
     /// output buffer and name compression information.
     /// \return 1
-    unsigned int toWire(MessageRenderer& renderer) const;
+    unsigned int toWire(AbstractMessageRenderer& renderer) const;
 
     /// \brief Render the Question in the wire format without name compression.
     ///
     /// This method behaves like the render version except it doesn't compress
     /// the owner name.
-    /// See \c toWire(MessageRenderer& renderer)const.
+    /// See \c toWire(AbstractMessageRenderer& renderer)const.
     ///
     /// \param buffer An output buffer to store the wire data.
     /// \return 1

+ 2 - 10
src/lib/dns/rdata/any_255/tsig_250.cc

@@ -24,7 +24,7 @@
 #include <dns/messagerenderer.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
-
+#include <dns/tsigerror.h>
 
 using namespace std;
 using namespace boost;
@@ -313,15 +313,7 @@ TSIG::toText() const {
         result += encodeBase64(impl_->mac_) + " ";
     }
     result += lexical_cast<string>(impl_->original_id_) + " ";
-    if (impl_->error_ == 16) {  // XXX: we'll soon introduce generic converter.
-        result += "BADSIG ";
-    } else if (impl_->error_ == 17) {
-        result += "BADKEY ";
-    } else if (impl_->error_ == 18) {
-        result += "BADTIME ";
-    } else {
-        result += lexical_cast<string>(impl_->error_) + " ";
-    }
+    result += TSIGError(impl_->error_).toText() + " ";
     result += lexical_cast<string>(impl_->other_data_.size());
     if (impl_->other_data_.size() > 0) {
         result += " " + encodeBase64(impl_->other_data_);

+ 2 - 2
src/lib/dns/rrclass-placeholder.h

@@ -31,7 +31,7 @@ class OutputBuffer;
 namespace dns {
 
 // forward declarations
-class MessageRenderer;
+class AbstractMessageRenderer;
 
 ///
 /// \brief A standard DNS module exception that is thrown if an RRClass object
@@ -169,7 +169,7 @@ public:
     /// standard exception will be thrown.
     ///
     /// \param buffer An output buffer to store the wire data.
-    void toWire(MessageRenderer& renderer) const;
+    void toWire(AbstractMessageRenderer& renderer) const;
     /// \brief Render the \c RRClass in the wire format.
     ///
     /// This method renders the class code in network byte order into the

+ 1 - 1
src/lib/dns/rrclass.cc

@@ -52,7 +52,7 @@ RRClass::toWire(OutputBuffer& buffer) const {
 }
 
 void
-RRClass::toWire(MessageRenderer& renderer) const {
+RRClass::toWire(AbstractMessageRenderer& renderer) const {
     renderer.writeUint16(classcode_);
 }
 

+ 3 - 3
src/lib/dns/rrset.cc

@@ -104,8 +104,8 @@ AbstractRRset::toWire(OutputBuffer& buffer) const {
 }
 
 unsigned int
-AbstractRRset::toWire(MessageRenderer& renderer) const {
-    const unsigned int rrs_written = rrsetToWire<MessageRenderer>(
+AbstractRRset::toWire(AbstractMessageRenderer& renderer) const {
+    const unsigned int rrs_written = rrsetToWire<AbstractMessageRenderer>(
         *this, renderer, renderer.getLengthLimit());
     if (getRdataCount() > rrs_written) {
         renderer.setTruncated();
@@ -202,7 +202,7 @@ BasicRRset::toWire(OutputBuffer& buffer) const {
 }
 
 unsigned int
-BasicRRset::toWire(MessageRenderer& renderer) const {
+BasicRRset::toWire(AbstractMessageRenderer& renderer) const {
     return (AbstractRRset::toWire(renderer));
 }
 

+ 3 - 3
src/lib/dns/rrset.h

@@ -47,7 +47,7 @@ class Name;
 class RRType;
 class RRClass;
 class RRTTL;
-class MessageRenderer;
+class AbstractMessageRenderer;
 class AbstractRRset;
 class BasicRRset;
 class RdataIterator;
@@ -311,7 +311,7 @@ public:
     /// \return The number of RRs rendered.  If the truncation is necessary
     /// this value may be different from the number of RDATA objects contained
     /// in the RRset.
-    virtual unsigned int toWire(MessageRenderer& renderer) const = 0;
+    virtual unsigned int toWire(AbstractMessageRenderer& renderer) const = 0;
 
     /// \brief Render the RRset in the wire format without any compression.
     ///
@@ -617,7 +617,7 @@ public:
     ///
     /// This method simply uses the default implementation.
     /// See \c AbstractRRset::toWire(MessageRenderer&)const.
-    virtual unsigned int toWire(MessageRenderer& renderer) const;
+    virtual unsigned int toWire(AbstractMessageRenderer& renderer) const;
 
     /// \brief Render the RRset in the wire format without any compression.
     ///

+ 1 - 1
src/lib/dns/rrttl.cc

@@ -63,7 +63,7 @@ RRTTL::toWire(OutputBuffer& buffer) const {
 }
 
 void
-RRTTL::toWire(MessageRenderer& renderer) const {
+RRTTL::toWire(AbstractMessageRenderer& renderer) const {
     renderer.writeUint32(ttlval_);
 }
 

+ 2 - 2
src/lib/dns/rrttl.h

@@ -28,7 +28,7 @@ class OutputBuffer;
 namespace dns {
 
 // forward declarations
-class MessageRenderer;
+class AbstractMessageRenderer;
 
 ///
 /// \brief A standard DNS module exception that is thrown if an RRTTL object
@@ -123,7 +123,7 @@ public:
     ///
     /// \param renderer DNS message rendering context that encapsulates the
     /// output buffer in which the RRTTL is to be stored.
-    void toWire(MessageRenderer& renderer) const;
+    void toWire(AbstractMessageRenderer& renderer) const;
     /// \brief Render the \c RRTTL in the wire format.
     ///
     /// This method renders the TTL value in network byte order into the

+ 1 - 0
src/lib/dns/tests/Makefile.am

@@ -50,6 +50,7 @@ run_unittests_SOURCES += message_unittest.cc
 run_unittests_SOURCES += tsig_unittest.cc
 run_unittests_SOURCES += tsigerror_unittest.cc
 run_unittests_SOURCES += tsigkey_unittest.cc
+run_unittests_SOURCES += tsigrecord_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)

+ 190 - 1
src/lib/dns/tests/message_unittest.cc

@@ -12,9 +12,18 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <fstream>
+
+#include <boost/scoped_ptr.hpp>
+
 #include <exceptions/exceptions.h>
 
 #include <util/buffer.h>
+#include <util/time_utilities.h>
+
+#include <util/unittests/testdata.h>
+#include <util/unittests/textdata.h>
+
 #include <dns/edns.h>
 #include <dns/exceptions.h>
 #include <dns/message.h>
@@ -26,6 +35,8 @@
 #include <dns/rrclass.h>
 #include <dns/rrttl.h>
 #include <dns/rrtype.h>
+#include <dns/tsig.h>
+#include <dns/tsigkey.h>
 
 #include <gtest/gtest.h>
 
@@ -53,6 +64,18 @@ using namespace isc::dns::rdata;
 const uint16_t Message::DEFAULT_MAX_UDPSIZE;
 const Name test_name("test.example.com");
 
+namespace isc {
+namespace util {
+namespace detail {
+extern int64_t (*gettimeFunction)();
+}
+}
+}
+
+// XXX: this is defined as class static constants, but some compilers
+// seemingly cannot find the symbol when used in the EXPECT_xxx macros.
+const uint16_t TSIGContext::DEFAULT_FUDGE;
+
 namespace {
 class MessageTest : public ::testing::Test {
 protected:
@@ -60,7 +83,9 @@ protected:
                     message_parse(Message::PARSE),
                     message_render(Message::RENDER),
                     bogus_section(static_cast<Message::Section>(
-                                      Message::SECTION_ADDITIONAL + 1))
+                                      Message::SECTION_ADDITIONAL + 1)),
+                    tsig_ctx(TSIGKey("www.example.com:"
+                                     "SFuWd/q99SzF8Yzd1QbB9g=="))
     {
         rrset_a = RRsetPtr(new RRset(test_name, RRClass::IN(),
                                      RRType::A(), RRTTL(3600)));
@@ -88,6 +113,9 @@ protected:
     RRsetPtr rrset_a;           // A RRset with two RDATAs
     RRsetPtr rrset_aaaa;        // AAAA RRset with one RDATA with RRSIG
     RRsetPtr rrset_rrsig;       // RRSIG for the AAAA RRset
+    TSIGContext tsig_ctx;
+    vector<unsigned char> expected_data;
+
     static void factoryFromFile(Message& message, const char* datafile);
 };
 
@@ -166,6 +194,70 @@ TEST_F(MessageTest, setEDNS) {
     EXPECT_EQ(edns, message_render.getEDNS());
 }
 
+TEST_F(MessageTest, fromWireWithTSIG) {
+    // Initially there should be no TSIG
+    EXPECT_EQ(static_cast<void*>(NULL), message_parse.getTSIGRecord());
+
+    // getTSIGRecord() is only valid in the parse mode.
+    EXPECT_THROW(message_render.getTSIGRecord(), InvalidMessageOperation);
+
+    factoryFromFile(message_parse, "message_toWire2.wire");
+    const char expected_mac[] = {
+        0x22, 0x70, 0x26, 0xad, 0x29, 0x7b, 0xee, 0xe7,
+        0x21, 0xce, 0x6c, 0x6f, 0xff, 0x1e, 0x9e, 0xf3
+    };
+    const TSIGRecord* tsig_rr = message_parse.getTSIGRecord();
+    ASSERT_NE(static_cast<void*>(NULL), tsig_rr);
+    EXPECT_EQ(Name("www.example.com"), tsig_rr->getName());
+    EXPECT_EQ(85, tsig_rr->getLength()); // see TSIGRecordTest.getLength
+    EXPECT_EQ(TSIGKey::HMACMD5_NAME(), tsig_rr->getRdata().getAlgorithm());
+    EXPECT_EQ(0x4da8877a, tsig_rr->getRdata().getTimeSigned());
+    EXPECT_EQ(TSIGContext::DEFAULT_FUDGE, tsig_rr->getRdata().getFudge());
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        tsig_rr->getRdata().getMAC(),
+                        tsig_rr->getRdata().getMACSize(),
+                        expected_mac, sizeof(expected_mac));
+    EXPECT_EQ(0, tsig_rr->getRdata().getError());
+    EXPECT_EQ(0, tsig_rr->getRdata().getOtherLen());
+    EXPECT_EQ(static_cast<void*>(NULL), tsig_rr->getRdata().getOtherData());
+
+    // If we clear the message for reuse, the recorded TSIG will be cleared.
+    message_parse.clear(Message::PARSE);
+    EXPECT_EQ(static_cast<void*>(NULL), message_parse.getTSIGRecord());
+}
+
+TEST_F(MessageTest, fromWireWithTSIGCompressed) {
+    // Mostly same as fromWireWithTSIG, but the TSIG owner name is compressed.
+    factoryFromFile(message_parse, "message_fromWire12.wire");
+    const TSIGRecord* tsig_rr = message_parse.getTSIGRecord();
+    ASSERT_NE(static_cast<void*>(NULL), tsig_rr);
+    EXPECT_EQ(Name("www.example.com"), tsig_rr->getName());
+    // len(www.example.com) = 17, but when fully compressed, the length is
+    // 2 bytes.  So the length of the record should be 15 bytes shorter.
+    EXPECT_EQ(70, tsig_rr->getLength());
+}
+
+TEST_F(MessageTest, fromWireWithBadTSIG) {
+    // Multiple TSIG RRs
+    EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire13.wire"),
+                 DNSMessageFORMERR);
+    message_parse.clear(Message::PARSE);
+
+    // TSIG in the answer section (must be in additional)
+    EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire14.wire"),
+                 DNSMessageFORMERR);
+    message_parse.clear(Message::PARSE);
+
+    // TSIG is not the last record.
+    EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire15.wire"),
+                 DNSMessageFORMERR);
+    message_parse.clear(Message::PARSE);
+
+    // Unexpected RR Class (this will fail in constructing TSIGRecord)
+    EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire16.wire"),
+                 DNSMessageFORMERR);
+}
+
 TEST_F(MessageTest, getRRCount) {
     // by default all counters should be 0
     EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION));
@@ -519,6 +611,65 @@ TEST_F(MessageTest, toWireInParseMode) {
     EXPECT_THROW(message_parse.toWire(renderer), InvalidMessageOperation);
 }
 
+// See dnssectime_unittest.cc
+template <int64_t NOW>
+int64_t
+testGetTime() {
+    return (NOW);
+}
+
+void
+commonTSIGToWireCheck(Message& message, MessageRenderer& renderer,
+                      TSIGContext& tsig_ctx, const char* const expected_file)
+{
+    message.setOpcode(Opcode::QUERY());
+    message.setRcode(Rcode::NOERROR());
+    message.setHeaderFlag(Message::HEADERFLAG_RD, true);
+    message.addQuestion(Question(Name("www.example.com"), RRClass::IN(),
+                                 RRType::A()));
+
+    message.toWire(renderer, tsig_ctx);
+    vector<unsigned char> expected_data;
+    UnitTestUtil::readWireData(expected_file, expected_data);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+                        renderer.getLength(),
+                        &expected_data[0], expected_data.size());
+}
+
+TEST_F(MessageTest, toWireWithTSIG) {
+    // Rendering a message with TSIG.  Various special cases specific to
+    // TSIG are tested in the tsig tests.  We only check the message contains
+    // a TSIG at the end and the ARCOUNT of the header is updated.
+
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+    message_render.setQid(0x2d65);
+
+    {
+        SCOPED_TRACE("Message sign with TSIG");
+        commonTSIGToWireCheck(message_render, renderer, tsig_ctx,
+                              "message_toWire2.wire");
+    }
+}
+
+TEST_F(MessageTest, toWireWithEDNSAndTSIG) {
+    // Similar to the previous test, but with an EDNS before TSIG.
+    // The wire data check will confirm the ordering.
+    isc::util::detail::gettimeFunction = testGetTime<0x4db60d1f>;
+
+    message_render.setQid(0x6cd);
+
+    EDNSPtr edns(new EDNS());
+    edns->setUDPSize(4096);
+    message_render.setEDNS(edns);
+
+    {
+        SCOPED_TRACE("Message sign with TSIG and EDNS");
+        commonTSIGToWireCheck(message_render, renderer, tsig_ctx,
+                              "message_toWire3.wire");
+    }
+}
+
 TEST_F(MessageTest, toWireWithoutOpcode) {
     message_render.setRcode(Rcode::NOERROR());
     EXPECT_THROW(message_render.toWire(renderer), InvalidMessageOperation);
@@ -529,6 +680,44 @@ TEST_F(MessageTest, toWireWithoutRcode) {
     EXPECT_THROW(message_render.toWire(renderer), InvalidMessageOperation);
 }
 
+TEST_F(MessageTest, toText) {
+    // Check toText() output for a typical DNS response with records in
+    // all sections
+    ifstream ifs;
+    unittests::openTestData("message_toText1.txt", ifs);
+    factoryFromFile(message_parse, "message_toText1.wire");
+    {
+        SCOPED_TRACE("Message toText test (basic case)");
+        unittests::matchTextData(ifs, message_parse.toText());
+    }
+
+    // Another example with EDNS.  The expected data was slightly modified
+    // from the dig output (other than replacing tabs with a space): adding
+    // a newline after the "OPT PSEUDOSECTION".  This is an intentional change
+    // in our version for better readability.
+    ifs.close();
+    message_parse.clear(Message::PARSE);
+    unittests::openTestData("message_toText2.txt", ifs);
+    factoryFromFile(message_parse, "message_toText2.wire");
+    {
+        SCOPED_TRACE("Message toText test with EDNS");
+        unittests::matchTextData(ifs, message_parse.toText());
+    }
+
+    // Another example with TSIG.  The expected data was slightly modified
+    // from the dig output (other than replacing tabs with a space): removing
+    // a redundant white space at the end of TSIG RDATA.  We'd rather consider
+    // it a dig's defect than a feature.
+    ifs.close();
+    message_parse.clear(Message::PARSE);
+    unittests::openTestData("message_toText3.txt", ifs);
+    factoryFromFile(message_parse, "message_toText3.wire");
+    {
+        SCOPED_TRACE("Message toText test with TSIG");
+        unittests::matchTextData(ifs, message_parse.toText());
+    }
+}
+
 TEST_F(MessageTest, toTextWithoutOpcode) {
     message_render.setRcode(Rcode::NOERROR());
     EXPECT_THROW(message_render.toText(), InvalidMessageOperation);

+ 3 - 0
src/lib/dns/tests/run_unittests.cc

@@ -14,13 +14,16 @@
 
 #include <gtest/gtest.h>
 
+#include <util/unittests/testdata.h>
 #include <dns/tests/unittest_util.h>
 
 int
 main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
     isc::UnitTestUtil::addDataPath(TEST_DATA_SRCDIR);
+    isc::util::unittests::addTestDataPath(TEST_DATA_SRCDIR);
     isc::UnitTestUtil::addDataPath(TEST_DATA_BUILDDIR);
+    isc::util::unittests::addTestDataPath(TEST_DATA_BUILDDIR);
 
     return (RUN_ALL_TESTS());
 }

+ 17 - 4
src/lib/dns/tests/testdata/Makefile.am

@@ -3,6 +3,12 @@ CLEANFILES = *.wire
 BUILT_SOURCES = edns_toWire1.wire edns_toWire2.wire edns_toWire3.wire
 BUILT_SOURCES += edns_toWire4.wire
 BUILT_SOURCES += message_fromWire10.wire message_fromWire11.wire
+BUILT_SOURCES += message_fromWire12.wire message_fromWire13.wire
+BUILT_SOURCES += message_fromWire14.wire message_fromWire15.wire
+BUILT_SOURCES += message_fromWire16.wire
+BUILT_SOURCES += message_toWire2.wire message_toWire3.wire
+BUILT_SOURCES += message_toText1.wire message_toText2.wire
+BUILT_SOURCES += message_toText3.wire
 BUILT_SOURCES += name_toWire5.wire name_toWire6.wire
 BUILT_SOURCES += rdatafields1.wire rdatafields2.wire rdatafields3.wire
 BUILT_SOURCES += rdatafields4.wire rdatafields5.wire rdatafields6.wire
@@ -33,6 +39,7 @@ BUILT_SOURCES += rdata_tsig_fromWire9.wire
 BUILT_SOURCES += rdata_tsig_toWire1.wire rdata_tsig_toWire2.wire
 BUILT_SOURCES += rdata_tsig_toWire3.wire rdata_tsig_toWire4.wire
 BUILT_SOURCES += rdata_tsig_toWire5.wire
+BUILT_SOURCES += tsigrecord_toWire1.wire tsigrecord_toWire2.wire
 
 # NOTE: keep this in sync with real file listing
 # so is included in tarball
@@ -45,8 +52,13 @@ EXTRA_DIST += message_fromWire3 message_fromWire4
 EXTRA_DIST += message_fromWire5 message_fromWire6
 EXTRA_DIST += message_fromWire7 message_fromWire8
 EXTRA_DIST += message_fromWire9 message_fromWire10.spec
-EXTRA_DIST += message_fromWire11.spec
-EXTRA_DIST += message_toWire1
+EXTRA_DIST += message_fromWire11.spec message_fromWire12.spec
+EXTRA_DIST += message_fromWire13.spec message_fromWire14.spec
+EXTRA_DIST += message_fromWire15.spec message_fromWire16.spec
+EXTRA_DIST += message_toWire1 message_toWire2.spec message_toWire3.spec
+EXTRA_DIST += message_toText1.txt message_toText1.spec
+EXTRA_DIST += message_toText2.txt message_toText2.spec
+EXTRA_DIST += message_toText3.txt message_toText3.spec
 EXTRA_DIST += name_fromWire1 name_fromWire2 name_fromWire3_1 name_fromWire3_2
 EXTRA_DIST += name_fromWire4 name_fromWire6 name_fromWire7 name_fromWire8
 EXTRA_DIST += name_fromWire9 name_fromWire10 name_fromWire11 name_fromWire12
@@ -66,7 +78,8 @@ EXTRA_DIST += rdata_nsec_fromWire6.spec rdata_nsec_fromWire7.spec
 EXTRA_DIST += rdata_nsec_fromWire8.spec rdata_nsec_fromWire9.spec
 EXTRA_DIST += rdata_nsec_fromWire10.spec
 EXTRA_DIST += rdata_nsec3param_fromWire1
-EXTRA_DIST += rdata_nsec3_fromWire1 rdata_nsec3_fromWire3
+EXTRA_DIST += rdata_nsec3_fromWire1
+EXTRA_DIST += rdata_nsec3_fromWire2.spec rdata_nsec3_fromWire3
 EXTRA_DIST += rdata_nsec3_fromWire4.spec rdata_nsec3_fromWire5.spec
 EXTRA_DIST += rdata_nsec3_fromWire6.spec rdata_nsec3_fromWire7.spec
 EXTRA_DIST += rdata_nsec3_fromWire8.spec rdata_nsec3_fromWire9.spec
@@ -94,7 +107,7 @@ EXTRA_DIST += rdata_tsig_fromWire9.spec
 EXTRA_DIST += rdata_tsig_toWire1.spec rdata_tsig_toWire2.spec
 EXTRA_DIST += rdata_tsig_toWire3.spec rdata_tsig_toWire4.spec
 EXTRA_DIST += rdata_tsig_toWire5.spec
-EXTRA_DIST += rdata_nsec3_fromWire2.spec
+EXTRA_DIST += tsigrecord_toWire1.spec tsigrecord_toWire2.spec
 
 .spec.wire:
 	./gen-wiredata.py -o $@ $<

+ 82 - 9
src/lib/dns/tests/testdata/gen-wiredata.py.in

@@ -15,7 +15,7 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-import configparser, re, time, sys
+import configparser, re, time, socket, sys
 from datetime import datetime
 from optparse import OptionParser
 
@@ -215,6 +215,74 @@ class EDNS:
         f.write('# RDLEN=%d\n' % self.rdlen)
         f.write('%04x\n' % self.rdlen)
 
+class RR:
+    '''This is a base class for various types of RR test data.
+    For each RR type (A, AAAA, NS, etc), we define a derived class of RR
+    to dump type specific RDATA parameters.  This class defines parameters
+    common to all types of RDATA, namely the owner name, RR class and TTL.
+    The dump() method of derived classes are expected to call dump_header(),
+    whose default implementation is provided in this class.  This method
+    decides whether to dump the test data as an RR (with name, type, class)
+    or only as RDATA (with its length), and dumps the corresponding data
+    via the specified file object.
+
+    By convention we assume derived classes are named after the common
+    standard mnemonic of the corresponding RR types.  For example, the
+    derived class for the RR type SOA should be named "SOA".
+
+    Configurable parameters are as follows:
+    - as_rr (bool): Whether or not the data is to be dumped as an RR.  False
+      by default.
+    - rr_class (string): The RR class of the data.  Only meaningful when the
+      data is dumped as an RR.  Default is 'IN'.
+    - rr_ttl (integer): The TTL value of the RR.  Only meaningful when the
+      data is dumped as an RR.  Default is 86400 (1 day).
+    '''
+
+    def __init__(self):
+        self.as_rr = False
+        # only when as_rr is True, same for class/TTL:
+        self.rr_name = 'example.com'
+        self.rr_class = 'IN'
+        self.rr_ttl = 86400
+    def dump_header(self, f, rdlen):
+        type_txt = self.__class__.__name__
+        type_code = parse_value(type_txt, dict_rrtype)
+        if self.as_rr:
+            rrclass = parse_value(self.rr_class, dict_rrclass)
+            f.write('\n# %s RR (QNAME=%s Class=%s TTL=%d RDLEN=%d)\n' %
+                    (type_txt, self.rr_name,
+                     code_totext(rrclass, rdict_rrclass), self.rr_ttl, rdlen))
+            f.write('%s %04x %04x %08x %04x\n' %
+                    (encode_name(self.rr_name), type_code, rrclass,
+                     self.rr_ttl, rdlen))
+        else:
+            f.write('\n# %s RDATA (RDLEN=%d)\n' % (type_txt, rdlen))
+            f.write('%04x\n' % rdlen)
+
+class A(RR):
+    rdlen = 4                   # fixed by default
+    address = '192.0.2.1'
+
+    def dump(self, f):
+        self.dump_header(f, self.rdlen)
+        f.write('# Address=%s\n' % (self.address))
+        bin_address = socket.inet_aton(self.address)
+        f.write('%02x%02x%02x%02x\n' % (bin_address[0], bin_address[1],
+                                        bin_address[2], bin_address[3]))
+
+class NS(RR):
+    rdlen = None                   # auto calculate
+    nsname = 'ns.example.com'
+
+    def dump(self, f):
+        nsname_wire = encode_name(self.nsname)
+        if self.rdlen is None:
+            self.rdlen = len(nsname_wire) / 2
+        self.dump_header(f, self.rdlen)
+        f.write('# NS name=%s\n' % (self.nsname))
+        f.write('%s\n' % nsname_wire)
+
 class SOA:
     # this currently doesn't support name compression within the RDATA.
     rdlen = -1                  # auto-calculate
@@ -432,7 +500,7 @@ class RRSIG:
         f.write('# Tag=%d Signer=%s and Signature\n' % (self.tag, self.signer))
         f.write('%04x %s %s\n' % (self.tag, name_wire, sig_wire))
 
-class TSIG:
+class TSIG(RR):
     rdlen = None                # auto-calculate
     algorithm = 'hmac-sha256'
     time_signed = 1286978795    # arbitrarily chosen default
@@ -444,12 +512,17 @@ class TSIG:
     other_len = None         # 6 if error is BADTIME; otherwise 0
     other_data = None        # use time_signed + fudge + 1 for BADTIME
     dict_macsize = { 'hmac-md5' : 16, 'hmac-sha1' : 20, 'hmac-sha256' : 32 }
+
+    # TSIG has some special defaults
+    def __init__(self):
+        self.rr_class = 'ANY'
+        self.rr_ttl = 0
+
     def dump(self, f):
         if str(self.algorithm) == 'hmac-md5':
             name_wire = encode_name('hmac-md5.sig-alg.reg.int')
         else:
             name_wire = encode_name(self.algorithm)
-        rdlen = self.rdlen
         mac_size = self.mac_size
         if mac_size is None:
             if self.algorithm in self.dict_macsize.keys():
@@ -468,11 +541,10 @@ class TSIG:
                 if self.error == 18 else ''
         else:
             other_data = encode_string(self.other_data, other_len)
-        if rdlen is None:
-            rdlen = int(len(name_wire) / 2 + 16 + len(mac) / 2 + \
-                            len(other_data) / 2)
-        f.write('\n# TSIG RDATA (RDLEN=%d)\n' % rdlen)
-        f.write('%04x\n' % rdlen);
+        if self.rdlen is None:
+            self.rdlen = int(len(name_wire) / 2 + 16 + len(mac) / 2 + \
+                                 len(other_data) / 2)
+        self.dump_header(f, self.rdlen)
         f.write('# Algorithm=%s Time-Signed=%d Fudge=%d\n' %
                 (self.algorithm, self.time_signed, self.fudge))
         f.write('%s %012x %04x\n' % (name_wire, self.time_signed, self.fudge))
@@ -488,7 +560,8 @@ def get_config_param(section):
     config_param = {'name' : (Name, {}),
                     'header' : (DNSHeader, header_xtables),
                     'question' : (DNSQuestion, question_xtables),
-                    'edns' : (EDNS, {}), 'soa' : (SOA, {}), 'txt' : (TXT, {}),
+                    'edns' : (EDNS, {}), 'a' : (A, {}), 'ns' : (NS, {}),
+                    'soa' : (SOA, {}), 'txt' : (TXT, {}),
                     'rp' : (RP, {}), 'rrsig' : (RRSIG, {}),
                     'nsec' : (NSEC, {}), 'nsec3' : (NSEC3, {}),
                     'tsig' : (TSIG, {}) }

+ 21 - 0
src/lib/dns/tests/testdata/message_fromWire12.spec

@@ -0,0 +1,21 @@
+#
+# A simple DNS response message with TSIG signed, but the owner name of TSIG
+# is compressed
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: ptr=12
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65

+ 20 - 0
src/lib/dns/tests/testdata/message_fromWire13.spec

@@ -0,0 +1,20 @@
+#
+# Invalid TSIG: containing 2 TSIG RRs.
+#
+
+[custom]
+sections: header:question:tsig:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 2
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65

+ 21 - 0
src/lib/dns/tests/testdata/message_fromWire14.spec

@@ -0,0 +1,21 @@
+#
+# Invalid TSIG: not in the additional section.
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+# TSIG goes to the answer section
+ancount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65

+ 22 - 0
src/lib/dns/tests/testdata/message_fromWire15.spec

@@ -0,0 +1,22 @@
+#
+# Invalid TSIG: not at the end of the message
+#
+
+[custom]
+sections: header:question:tsig:edns
+[header]
+id: 0x2d65
+rd: 1
+arcount: 2
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
+[edns]
+# (all default)

+ 21 - 0
src/lib/dns/tests/testdata/message_fromWire16.spec

@@ -0,0 +1,21 @@
+#
+# Invalid TSIG: not in the additional section.
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_class: IN
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65

+ 24 - 0
src/lib/dns/tests/testdata/message_toText1.spec

@@ -0,0 +1,24 @@
+#
+# A standard DNS message (taken from an invokation of dig)
+#
+
+[custom]
+sections: header:question:a/1:ns:a/2
+[header]
+id: 29174
+qr: 1
+aa: 1
+ancount: 1
+nscount: 1
+arcount: 1
+[question]
+name: www.example.com
+[a/1]
+as_rr: True
+rr_name: www.example.com
+address: 192.0.2.80
+[ns]
+as_rr: True
+[a/2]
+as_rr: True
+rr_name: ns.example.com

+ 14 - 0
src/lib/dns/tests/testdata/message_toText1.txt

@@ -0,0 +1,14 @@
+;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29174
+;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
+
+;; QUESTION SECTION:
+;www.example.com. IN A
+
+;; ANSWER SECTION:
+www.example.com. 86400 IN A 192.0.2.80
+
+;; AUTHORITY SECTION:
+example.com. 86400 IN NS ns.example.com.
+
+;; ADDITIONAL SECTION:
+ns.example.com. 86400 IN A 192.0.2.1

+ 14 - 0
src/lib/dns/tests/testdata/message_toText2.spec

@@ -0,0 +1,14 @@
+#
+# A standard DNS message with EDNS (taken from an invokation of dig)
+#
+
+[custom]
+sections: header:question:edns
+[header]
+id: 45981
+qr: 1
+rcode: refused
+arcount: 1
+[question]
+[edns]
+do: 1

+ 31 - 0
src/lib/dns/tests/testdata/message_toText3.spec

@@ -0,0 +1,31 @@
+#
+# A standard DNS message with TSIG (taken from an invokation of dig)
+#
+
+[custom]
+sections: header:question:a/1:ns:a/2:tsig
+[header]
+id: 10140
+qr: 1
+aa: 1
+ancount: 1
+nscount: 1
+arcount: 2
+[question]
+name: www.example.com
+[a/1]
+as_rr: True
+rr_name: www.example.com
+address: 192.0.2.80
+[ns]
+as_rr: True
+[a/2]
+as_rr: True
+rr_name: ns.example.com
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 1304384318
+original_id: 10140
+mac: 0x5257c80396f2fa95b20c77ae9a652fb2

+ 21 - 0
src/lib/dns/tests/testdata/message_toWire2.spec

@@ -0,0 +1,21 @@
+#
+# A simple DNS response message with TSIG signed
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65

+ 22 - 0
src/lib/dns/tests/testdata/message_toWire3.spec

@@ -0,0 +1,22 @@
+#
+# A simple DNS response message with EDNS and TSIG
+#
+
+[custom]
+sections: header:question:edns:tsig
+[header]
+id: 0x06cd
+rd: 1
+arcount: 2
+[question]
+name: www.example.com
+[edns]
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4db60d1f
+mac_size: 16
+mac: 0x93444053881c83d7eb120e86f25b369e
+original_id: 0x06cd

+ 16 - 0
src/lib/dns/tests/testdata/tsigrecord_toWire1.spec

@@ -0,0 +1,16 @@
+#
+# A simple TSIG RR (some of the parameters are taken from a live example
+# and don't have a specific meaning)
+#
+
+[custom]
+sections: tsig
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0xdadadadadadadadadadadadadadadada
+original_id: 0x2d65

+ 19 - 0
src/lib/dns/tests/testdata/tsigrecord_toWire2.spec

@@ -0,0 +1,19 @@
+#
+# TSIG RR after some names that could (unexpectedly) cause name compression
+#
+
+[custom]
+sections: name/1:name/2:tsig
+[name/1]
+name: hmac-md5.sig-alg.reg.int
+[name/2]
+name: foo.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0xdadadadadadadadadadadadadadadada
+original_id: 0x2d65

+ 15 - 15
src/lib/dns/tests/tsig_unittest.cc

@@ -26,6 +26,7 @@
 #include <util/buffer.h>
 #include <util/encode/base64.h>
 #include <util/unittests/newhook.h>
+#include <util/time_utilities.h>
 
 #include <dns/message.h>
 #include <dns/messagerenderer.h>
@@ -36,6 +37,7 @@
 #include <dns/rrtype.h>
 #include <dns/tsig.h>
 #include <dns/tsigkey.h>
+#include <dns/tsigrecord.h>
 
 #include <dns/tests/unittest_util.h>
 
@@ -49,14 +51,12 @@ using isc::UnitTestUtil;
 
 // See dnssectime.cc
 namespace isc {
-namespace dns {
-namespace tsig {
+namespace util {
 namespace detail {
 extern int64_t (*gettimeFunction)();
 }
 }
 }
-}
 
 namespace {
 // See dnssectime_unittest.cc
@@ -75,7 +75,7 @@ protected:
     {
         // Make sure we use the system time by default so that we won't be
         // confused due to other tests that tweak the time.
-        tsig::detail::gettimeFunction = NULL;
+        isc::util::detail::gettimeFunction = NULL;
 
         decodeBase64("SFuWd/q99SzF8Yzd1QbB9g==", secret);
         tsig_ctx.reset(new TSIGContext(TSIGKey(test_name,
@@ -87,7 +87,7 @@ protected:
                                                       secret.size())));
     }
     ~TSIGTest() {
-        tsig::detail::gettimeFunction = NULL;
+        isc::util::detail::gettimeFunction = NULL;
     }
 
     // Many of the tests below create some DNS message and sign it under
@@ -209,7 +209,7 @@ const uint8_t common_expected_mac[] = {
     0x21, 0xce, 0x6c, 0x6f, 0xff, 0x1e, 0x9e, 0xf3
 };
 TEST_F(TSIGTest, sign) {
-    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
 
     {
         SCOPED_TRACE("Sign test for query");
@@ -223,7 +223,7 @@ TEST_F(TSIGTest, sign) {
 // non canonical) characters.  The digest must be the same.  It should actually
 // be ensured at the level of TSIGKey, but we confirm that at this level, too.
 TEST_F(TSIGTest, signUsingUpperCasedKeyName) {
-    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
 
     TSIGContext cap_ctx(TSIGKey(Name("WWW.EXAMPLE.COM"),
                                 TSIGKey::HMACMD5_NAME(),
@@ -239,7 +239,7 @@ TEST_F(TSIGTest, signUsingUpperCasedKeyName) {
 
 // Same as the previous test, but for the algorithm name.
 TEST_F(TSIGTest, signUsingUpperCasedAlgorithmName) {
-    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
 
     TSIGContext cap_ctx(TSIGKey(test_name,
                                 Name("HMAC-md5.SIG-alg.REG.int"),
@@ -325,7 +325,7 @@ TEST_F(TSIGTest, signExceptionSafety) {
 //   HMAC Size: 20
 //   HMAC: 415340c7daf824ed684ee586f7b5a67a2febc0d3
 TEST_F(TSIGTest, signUsingHMACSHA1) {
-    tsig::detail::gettimeFunction = testGetTime<0x4dae7d5f>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4dae7d5f>;
 
     secret.clear();
     decodeBase64("MA+QDhXbyqUak+qnMFyTyEirzng=", secret);
@@ -350,7 +350,7 @@ TEST_F(TSIGTest, signUsingHMACSHA1) {
 // Answer: www.example.com. 86400 IN A 192.0.2.1
 // MAC: 8fcda66a7cd1a3b9948eb1869d384a9f
 TEST_F(TSIGTest, signResponse) {
-    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
 
     ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
                                                    tsig_ctx.get());
@@ -385,7 +385,7 @@ TEST_F(TSIGTest, signResponse) {
 //    Answer: example.com. 86400 IN NS ns.example.com.
 //    MAC: 102458f7f62ddd7d638d746034130968
 TEST_F(TSIGTest, signContinuation) {
-    tsig::detail::gettimeFunction = testGetTime<0x4da8e951>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8e951>;
 
     const uint16_t axfr_qid = 0x3410;
     const Name zone_name("example.com");
@@ -435,7 +435,7 @@ TEST_F(TSIGTest, signContinuation) {
 //   Error: 0x12 (BADTIME), Other Len: 6
 //   Other data: 00004da8be86
 TEST_F(TSIGTest, badtimeResponse) {
-    tsig::detail::gettimeFunction = testGetTime<0x4da8b9d6>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6>;
 
     const uint16_t test_qid = 0x7fc4;
     ConstTSIGRecordPtr tsig = createMessageAndSign(test_qid, test_name,
@@ -444,7 +444,7 @@ TEST_F(TSIGTest, badtimeResponse) {
 
     // "advance the clock" and try validating, which should fail due to BADTIME
     // (verifyTentative actually doesn't check the time, though)
-    tsig::detail::gettimeFunction = testGetTime<0x4da8be86>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8be86>;
     tsig_verify_ctx->verifyTentative(tsig, TSIGError::BAD_TIME());
     EXPECT_EQ(TSIGError::BAD_TIME(), tsig_verify_ctx->getError());
 
@@ -468,7 +468,7 @@ TEST_F(TSIGTest, badtimeResponse) {
 }
 
 TEST_F(TSIGTest, badsigResponse) {
-    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
 
     // Sign a simple message, and force the verification to fail with
     // BADSIG.
@@ -489,7 +489,7 @@ TEST_F(TSIGTest, badsigResponse) {
 
 TEST_F(TSIGTest, badkeyResponse) {
     // A similar test as badsigResponse but for BADKEY
-    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
     tsig_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name,
                                                           tsig_ctx.get()),
                                      TSIGError::BAD_KEY());

+ 161 - 0
src/lib/dns/tests/tsigrecord_unittest.cc

@@ -0,0 +1,161 @@
+// Copyright (C) 2011  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 <vector>
+#include <sstream>
+
+#include <gtest/gtest.h>
+
+#include <util/buffer.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/tsig.h>
+#include <dns/tsigkey.h>
+#include <dns/tsigrecord.h>
+
+#include <dns/tests/unittest_util.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+
+namespace {
+class TSIGRecordTest : public ::testing::Test {
+protected:
+    TSIGRecordTest() :
+        test_name("www.example.com"), test_mac(16, 0xda),
+        test_rdata(any::TSIG(TSIGKey::HMACMD5_NAME(), 0x4da8877a,
+                             TSIGContext::DEFAULT_FUDGE,
+                             test_mac.size(), &test_mac[0],
+                             0x2d65, 0, 0, NULL)),
+        test_record(test_name, test_rdata),
+        buffer(0), renderer(buffer)
+    {}
+    const Name test_name;
+    vector<unsigned char> test_mac;
+    const any::TSIG test_rdata;
+    const TSIGRecord test_record;
+    OutputBuffer buffer;
+    MessageRenderer renderer;
+    vector<unsigned char> data;
+};
+
+TEST_F(TSIGRecordTest, getName) {
+    EXPECT_EQ(test_name, test_record.getName());
+}
+
+TEST_F(TSIGRecordTest, getLength) {
+    // 85 = 17 + 26 + 16 + 24
+    // len(www.example.com) = 17
+    // len(hmac-md5.sig-alg.reg.int) = 26
+    // len(MAC) = 16
+    // the rest are fixed length fields (26 in total)
+    EXPECT_EQ(85, test_record.getLength());
+}
+
+TEST_F(TSIGRecordTest, fromParams) {
+    // Construct the same TSIG RR as test_record from parameters.
+    // See the getLength test for the magic number of 85 (although it
+    // actually doesn't matter)
+    const TSIGRecord record(test_name, TSIGRecord::getClass(),
+                            TSIGRecord::getTTL(), test_rdata, 85);
+    // Perform straight sanity checks
+    EXPECT_EQ(test_name, record.getName());
+    EXPECT_EQ(85, record.getLength());
+    EXPECT_EQ(0, test_rdata.compare(record.getRdata()));
+
+    // The constructor doesn't check the length...
+    EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+                               TSIGRecord::getTTL(), test_rdata, 82));
+    // ...even for impossibly small values...
+    EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+                               TSIGRecord::getTTL(), test_rdata, 1));
+    // ...or too large values.
+    EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+                               TSIGRecord::getTTL(), test_rdata, 65536));
+
+    // RDATA must indeed be TSIG
+    EXPECT_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+                            TSIGRecord::getTTL(), in::A("192.0.2.1"), 85),
+                 DNSMessageFORMERR);
+
+    // Unexpected class
+    EXPECT_THROW(TSIGRecord(test_name, RRClass::IN(), TSIGRecord::getTTL(),
+                            test_rdata, 85),
+                 DNSMessageFORMERR);
+
+    // Unexpected TTL (simply ignored)
+    EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+                               RRTTL(3600), test_rdata, 85));
+}
+
+TEST_F(TSIGRecordTest, recordToWire) {
+    UnitTestUtil::readWireData("tsigrecord_toWire1.wire", data);
+    EXPECT_EQ(1, test_record.toWire(renderer));
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        renderer.getData(), renderer.getLength(),
+                        &data[0], data.size());
+
+    // Same test for a dumb buffer
+    buffer.clear();
+    EXPECT_EQ(1, test_record.toWire(buffer));
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        buffer.getData(), buffer.getLength(),
+                        &data[0], data.size());
+}
+
+TEST_F(TSIGRecordTest, recordToOLongToWire) {
+    // Rendering the test record requires a room of 85 bytes (see the
+    // getLength test).  By setting the limit to 84, it will fail, and
+    // the renderer will be marked as "truncated".
+    renderer.setLengthLimit(84);
+    EXPECT_FALSE(renderer.isTruncated()); // not marked before render attempt
+    EXPECT_EQ(0, test_record.toWire(renderer));
+    EXPECT_TRUE(renderer.isTruncated());
+}
+
+TEST_F(TSIGRecordTest, recordToWireAfterNames) {
+    // A similar test but the TSIG RR follows some domain names that could
+    // cause name compression inside TSIG.  Our implementation shouldn't
+    // compress either owner (key) name or the algorithm name.  This test
+    // confirms that.
+
+    UnitTestUtil::readWireData("tsigrecord_toWire2.wire", data);
+    renderer.writeName(TSIGKey::HMACMD5_NAME());
+    renderer.writeName(Name("foo.example.com"));
+    EXPECT_EQ(1, test_record.toWire(renderer));
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        renderer.getData(), renderer.getLength(),
+                        &data[0], data.size());
+}
+
+TEST_F(TSIGRecordTest, toText) {
+    EXPECT_EQ("www.example.com. 0 ANY TSIG hmac-md5.sig-alg.reg.int. "
+              "1302890362 300 16 2tra2tra2tra2tra2tra2g== 11621 NOERROR 0\n",
+              test_record.toText());
+}
+
+// test operator<<.  We simply confirm it appends the result of toText().
+TEST_F(TSIGRecordTest, LeftShiftOperator) {
+    ostringstream oss;
+    oss << test_record;
+    EXPECT_EQ(test_record.toText(), oss.str());
+}
+} // end namespace

+ 4 - 29
src/lib/dns/tsig.cc

@@ -24,6 +24,7 @@
 #include <exceptions/exceptions.h>
 
 #include <util/buffer.h>
+#include <util/time_utilities.h>
 
 #include <dns/rdataclass.h>
 #include <dns/rrclass.h>
@@ -41,38 +42,10 @@ using namespace isc::dns::rdata;
 
 namespace isc {
 namespace dns {
-
-// Borrowed from dnssectime.cc.  This trick should be unified somewhere.
-namespace tsig {
-namespace detail {
-int64_t (*gettimeFunction)() = NULL;
-}
-}
-
-namespace {
-int64_t
-gettimeofdayWrapper() {
-    using namespace tsig::detail;
-    if (gettimeFunction != NULL) {
-        return (gettimeFunction());
-    }
-
-    struct timeval now;
-    gettimeofday(&now, NULL);
-
-    return (static_cast<int64_t>(now.tv_sec));
-}
-}
-
 namespace {
 typedef boost::shared_ptr<HMAC> HMACPtr;
 }
 
-const RRClass&
-TSIGRecord::getClass() {
-    return (RRClass::ANY());
-}
-
 struct TSIGContext::TSIGContextImpl {
     TSIGContextImpl(const TSIGKey& key) :
         state_(INIT), key_(key), error_(Rcode::NOERROR()),
@@ -118,7 +91,7 @@ TSIGContext::sign(const uint16_t qid, const void* const data,
     // represents a value in the expected range.  (In reality, however,
     // gettimeofdayWrapper() will return a positive integer that will fit
     // in 48 bits)
-    const uint64_t now = (gettimeofdayWrapper() & 0x0000ffffffffffffULL);
+    const uint64_t now = (detail::gettimeWrapper() & 0x0000ffffffffffffULL);
 
     // For responses adjust the error code.
     if (impl_->state_ == CHECKED) {
@@ -129,6 +102,7 @@ TSIGContext::sign(const uint16_t qid, const void* const data,
     // specified in Section 4.3 of RFC2845.
     if (error == TSIGError::BAD_SIG() || error == TSIGError::BAD_KEY()) {
         ConstTSIGRecordPtr tsig(new TSIGRecord(
+                                    impl_->key_.getKeyName(),
                                     any::TSIG(impl_->key_.getAlgorithmName(),
                                               now, DEFAULT_FUDGE, 0, NULL,
                                               qid, error.getCode(), 0, NULL)));
@@ -198,6 +172,7 @@ TSIGContext::sign(const uint16_t qid, const void* const data,
     // Get the final digest, update internal state, then finish.
     vector<uint8_t> digest = hmac->sign();
     ConstTSIGRecordPtr tsig(new TSIGRecord(
+                                impl_->key_.getKeyName(),
                                 any::TSIG(impl_->key_.getAlgorithmName(),
                                           time_signed, DEFAULT_FUDGE,
                                           digest.size(), &digest[0],

+ 1 - 71
src/lib/dns/tsig.h

@@ -15,84 +15,14 @@
 #ifndef __TSIG_H
 #define __TSIG_H 1
 
-#include <boost/shared_ptr.hpp>
 #include <boost/noncopyable.hpp>
 
-#include <dns/rdataclass.h>
 #include <dns/tsigerror.h>
 #include <dns/tsigkey.h>
+#include <dns/tsigrecord.h>
 
 namespace isc {
 namespace dns {
-/// TSIG resource record.
-///
-/// A \c TSIGRecord class object represents a TSIG resource record and is
-/// responsible for conversion to and from wire format TSIG record based on
-/// the protocol specification (RFC2845).
-/// This class is provided so that other classes and applications can handle
-/// TSIG without knowing protocol details of TSIG, such as that it uses a
-/// fixed constant of TTL.
-///
-/// \note So the plan is to eventually provide a \c toWire() method and
-/// the "from wire" constructor.  They are not yet provided in this initial
-/// step.
-///
-/// \note
-/// This class could be a derived class of \c AbstractRRset.  That way
-/// it would be able to be used in a polymorphic way; for example,
-/// an application can construct a TSIG RR by itself and insert it to a
-/// \c Message object as a generic RRset.  On the other hand, it would mean
-/// this class would have to implement an \c RdataIterator (even though it
-/// can be done via straightforward forwarding) while the iterator is mostly
-/// redundant since there should be one and only one RDATA for a valid TSIG
-/// RR.  Likewise, some methods such as \c setTTL() method wouldn't be well
-/// defined due to such special rules for TSIG as using a fixed TTL.
-/// Overall, TSIG is a very special RR type that simply uses the compatible
-/// resource record format, and it will be unlikely that a user wants to
-/// handle it through a generic interface in a polymorphic way.
-/// We therefore chose to define it as a separate class.  This is also
-/// similar to why \c EDNS is a separate class.
-class TSIGRecord {
-public:
-    /// Constructor from TSIG RDATA
-    ///
-    /// \exception std::bad_alloc Resource allocation for copying the RDATA
-    /// fails
-    explicit TSIGRecord(const rdata::any::TSIG& tsig_rdata) :
-        rdata_(tsig_rdata)
-    {}
-
-    /// Return the RDATA of the TSIG RR
-    ///
-    /// \exception None
-    const rdata::any::TSIG& getRdata() const { return (rdata_); }
-
-    /// \name Protocol constants and defaults
-    ///
-    //@{
-    /// Return the RR class of TSIG
-    ///
-    /// TSIG always uses the ANY RR class.  This static method returns it,
-    /// when, though unlikely, an application wants to know which class TSIG
-    /// is supposed to use.
-    ///
-    /// \exception None
-    static const RRClass& getClass();
-
-    /// The TTL value to be used in TSIG RRs.
-    static const uint32_t TSIG_TTL = 0;
-    //@}
-
-private:
-    const rdata::any::TSIG rdata_;
-};
-
-/// A pointer-like type pointing to a \c TSIGRecord object.
-typedef boost::shared_ptr<TSIGRecord> TSIGRecordPtr;
-
-/// A pointer-like type pointing to an immutable \c TSIGRecord object.
-typedef boost::shared_ptr<const TSIGRecord> ConstTSIGRecordPtr;
-
 /// TSIG session context.
 ///
 /// The \c TSIGContext class maintains a context of a signed session of

+ 145 - 0
src/lib/dns/tsigrecord.cc

@@ -0,0 +1,145 @@
+// Copyright (C) 2011  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 <ostream>
+#include <string>
+
+#include <util/buffer.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/tsigrecord.h>
+
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+namespace {
+// Internally used constants:
+
+// Size in octets for the RR type, class TTL, RDLEN fields.
+const size_t RR_COMMON_LEN = 10;
+
+// Size in octets for the fixed part of TSIG RDATAs.
+// - Time Signed (6)
+// - Fudge (2)
+// - MAC Size (2)
+// - Original ID (2)
+// - Error (2)
+// - Other Len (2)
+const size_t RDATA_COMMON_LEN = 16;
+}
+
+namespace isc {
+namespace dns {
+TSIGRecord::TSIGRecord(const Name& key_name,
+                       const rdata::any::TSIG& tsig_rdata) :
+    key_name_(key_name), rdata_(tsig_rdata),
+    length_(RR_COMMON_LEN + RDATA_COMMON_LEN + key_name_.getLength() +
+            rdata_.getAlgorithm().getLength() +
+            rdata_.getMACSize() + rdata_.getOtherLen())
+{}
+
+namespace {
+// This is a straightforward wrapper of dynamic_cast<const any::TSIG&>.
+// We use this so that we can throw the DNSMessageFORMERR exception when
+// unexpected type of RDATA is detected in the member initialization list
+// of the constructor below.
+const any::TSIG&
+castToTSIGRdata(const rdata::Rdata& rdata) {
+    try {
+        return (dynamic_cast<const any::TSIG&>(rdata));
+    } catch (std::bad_cast&) {
+        isc_throw(DNSMessageFORMERR,
+                  "TSIG record is being constructed from "
+                  "incompatible RDATA:" << rdata.toText());
+    }
+}
+}
+
+TSIGRecord::TSIGRecord(const Name& name, const RRClass& rrclass,
+                       const RRTTL&, // we ignore TTL
+                       const rdata::Rdata& rdata,
+                       size_t length) :
+    key_name_(name), rdata_(castToTSIGRdata(rdata)), length_(length)
+{
+    if (rrclass != getClass()) {
+        isc_throw(DNSMessageFORMERR, "Unexpected TSIG RR class: " << rrclass);
+    }
+}
+
+const RRClass&
+TSIGRecord::getClass() {
+    return (RRClass::ANY());
+}
+
+const RRTTL&
+TSIGRecord::getTTL() {
+    static RRTTL ttl(TSIG_TTL);
+    return (ttl);
+}
+
+namespace {
+template <typename OUTPUT>
+void
+toWireCommon(OUTPUT& output, const rdata::any::TSIG& rdata) {
+    // RR type, class, TTL are fixed constants.
+    RRType::TSIG().toWire(output);
+    TSIGRecord::getClass().toWire(output);
+    output.writeUint32(TSIGRecord::TSIG_TTL);
+
+    // RDLEN
+    output.writeUint16(RDATA_COMMON_LEN + rdata.getAlgorithm().getLength() +
+                       rdata.getMACSize() + rdata.getOtherLen());
+
+    // TSIG RDATA
+    rdata.toWire(output);
+}
+}
+
+int
+TSIGRecord::toWire(AbstractMessageRenderer& renderer) const {
+    // If adding the TSIG would exceed the size limit, don't do it.
+    if (renderer.getLength() + length_ > renderer.getLengthLimit()) {
+        renderer.setTruncated();
+        return (0);
+    }
+
+    // key name = owner.  note that we disable compression.
+    renderer.writeName(key_name_, false);
+    toWireCommon(renderer, rdata_);
+    return (1);
+}
+
+int
+TSIGRecord::toWire(OutputBuffer& buffer) const {
+    key_name_.toWire(buffer);
+    toWireCommon(buffer, rdata_);
+    return (1);
+}
+
+std::string
+TSIGRecord::toText() const {
+    return (key_name_.toText() + " " + RRTTL(TSIG_TTL).toText() + " " +
+            getClass().toText() + " " + RRType::TSIG().toText() + " " +
+            rdata_.toText() + "\n");
+}
+
+std::ostream&
+operator<<(std::ostream& os, const TSIGRecord& record) {
+    return (os << record.toText());
+}
+} // namespace dns
+} // namespace isc

+ 285 - 0
src/lib/dns/tsigrecord.h

@@ -0,0 +1,285 @@
+// Copyright (C) 2011  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 __TSIGRECORD_H
+#define __TSIGRECORD_H 1
+
+#include <ostream>
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include <util/buffer.h>
+
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+
+namespace isc {
+namespace util {
+class OutputBuffer;
+}
+namespace dns {
+class AbstractMessageRenderer;
+
+/// TSIG resource record.
+///
+/// A \c TSIGRecord class object represents a TSIG resource record and is
+/// responsible for conversion to and from wire format TSIG record based on
+/// the protocol specification (RFC2845).
+/// This class is provided so that other classes and applications can handle
+/// TSIG without knowing protocol details of TSIG, such as that it uses a
+/// fixed constant of TTL.
+///
+/// \todo So the plan is to eventually provide  the "from wire" constructor.
+/// It's not yet provided in the current phase of development.
+///
+/// \note
+/// This class could be a derived class of \c AbstractRRset.  That way
+/// it would be able to be used in a polymorphic way; for example,
+/// an application can construct a TSIG RR by itself and insert it to a
+/// \c Message object as a generic RRset.  On the other hand, it would mean
+/// this class would have to implement an \c RdataIterator (even though it
+/// can be done via straightforward forwarding) while the iterator is mostly
+/// redundant since there should be one and only one RDATA for a valid TSIG
+/// RR.  Likewise, some methods such as \c setTTL() method wouldn't be well
+/// defined due to such special rules for TSIG as using a fixed TTL.
+/// Overall, TSIG is a very special RR type that simply uses the compatible
+/// resource record format, and it will be unlikely that a user wants to
+/// handle it through a generic interface in a polymorphic way.
+/// We therefore chose to define it as a separate class.  This is also
+/// similar to why \c EDNS is a separate class.
+class TSIGRecord {
+public:
+    ///
+    /// \name Constructors
+    ///
+    /// We use the default copy constructor, default copy assignment operator,
+    /// (and default destructor) intentionally.
+    //@{
+    /// Constructor from TSIG key name and RDATA
+    ///
+    /// \exception std::bad_alloc Resource allocation for copying the name or
+    /// RDATA fails
+    TSIGRecord(const Name& key_name, const rdata::any::TSIG& tsig_rdata);
+
+    /// Constructor from resource record (RR) parameters.
+    ///
+    /// This constructor is intended to be used in the context of parsing
+    /// an incoming DNS message that contains a TSIG.  The parser would
+    /// first extract the owner name, RR type (which TSIG) class, TTL and
+    /// the TSIG RDATA from the message.  This constructor is expected to
+    /// be given these RR parameters (except the RR type, because it must be
+    /// TSIG).
+    ///
+    /// According to RFC2845, a TSIG RR uses fixed RR class (ANY) and TTL (0).
+    /// If the RR class is different from the expected one, this
+    /// implementation considers it an invalid record and throws an exception
+    /// of class \c DNSMessageFORMERR.  On the other hand, the TTL is simply
+    /// ignored (in that sense we could even omit that parameter, but it's
+    /// still included if and when we want to change the policy).  RFC2845
+    /// is silent about what the receiver should do if it sees an unexpected
+    /// RR class or TTL in a TSIG RR.  This implementation simply follows what
+    /// BIND 9 does (it is not clear why BIND 9 employs the "inconsistent"
+    /// policy).
+    ///
+    /// Likewise, if \c rdata is not of type \c any::TSIG, an exception of
+    /// class DNSMessageFORMERR will be thrown.  When the caller is a
+    /// DNS message parser and builds \c rdata from incoming wire format
+    /// data as described above, this case happens when the RR class is
+    /// different from ANY (in the implementation, the type check takes place
+    /// before the explicit check against the RR class explained in the
+    /// previous paragraph).
+    ///
+    /// The \c length parameter is intended to be the length of the TSIG RR
+    /// (from the beginning of the owner name to the end of the RDATA) when
+    /// the caller is a DNS message parser.  Note that it is the actual length
+    /// for the RR in the format; if the owner name or the algorithm name
+    /// (in the RDATA) is compressed (although the latter should not be
+    /// compressed according to RFC3597), the length must be the size of the
+    /// compressed data.  The length is recorded inside the class and will
+    /// be returned via subsequent calls to \c getLength().  It's intended to
+    /// be used in the context TSIG verification; in the verify process
+    /// the MAC computation must be performed for the original data without
+    /// TSIG, so, to avoid parsing the entire data in the verify process
+    /// again, it's necessary to record information that can identify the
+    /// length to be digested for the MAC.  This parameter servers for that
+    /// purpose.
+    ///
+    /// \note Since the constructor doesn't take the wire format data per se,
+    /// it doesn't (and cannot) check the validity of \c length, and simply
+    /// accepts any given value.  It even accepts obviously invalid values
+    /// such as 0.  It's caller's responsibility to provide a valid value of
+    /// length, and, the verifier's responsibility to use the length safely.
+    ///
+    /// <b>DISCUSSION:</b> this design is fragile in that it introduces
+    /// a tight coupling between message parsing and TSIG verification via
+    /// the \c TSIGRecord class.  In terms of responsibility decoupling,
+    /// the ideal way to have \c TSIGRecord remember the entire wire data
+    /// along with the length of the TSIG.  Then in the TSIG verification
+    /// we could refer to the necessary potion of data solely from a
+    /// \c TSIGRecord object.  However, this approach would require expensive
+    /// heavy copy of the original data or introduce another kind of coupling
+    /// between the data holder and this class (if the original data is freed
+    /// while a \c TSIGRecord object referencing the data still exists, the
+    /// result will be catastrophic).  As a "best current compromise", we
+    /// use the current design.  We may reconsider it if it turns out to
+    /// cause a big problem or we come up with a better idea.
+    ///
+    /// \exception DNSMessageFORMERR Given RR parameters are invalid for TSIG.
+    /// \exception std::bad_alloc Internal resource allocation fails.
+    ///
+    /// \param name The owner name of the TSIG RR
+    /// \param rrclass The RR class of the RR.  Must be \c RRClass::ANY()
+    /// (see above)
+    /// \param ttl The TTL of the RR.  Expected to be a zero TTL, but
+    /// actually ignored in this implementation.
+    /// \param rdata The RDATA of the RR.  Must be of type \c any::TSIG.
+    /// \param length The size of the RR (see above)
+    TSIGRecord(const Name& name, const RRClass& rrclass, const RRTTL& ttl,
+               const rdata::Rdata& rdata, size_t length);
+    //@}
+
+    /// Return the owner name of the TSIG RR, which is the TSIG key name
+    ///
+    /// \exception None
+    const Name& getName() const { return (key_name_); }
+
+    /// Return the RDATA of the TSIG RR
+    ///
+    /// \exception None
+    const rdata::any::TSIG& getRdata() const { return (rdata_); }
+
+    /// \name Protocol constants and defaults
+    ///
+    //@{
+    /// Return the RR class of TSIG
+    ///
+    /// TSIG always uses the ANY RR class.  This static method returns it,
+    /// when, though unlikely, an application wants to know which class TSIG
+    /// is supposed to use.
+    ///
+    /// \exception None
+    static const RRClass& getClass();
+
+    /// Return the TTL value of TSIG
+    ///
+    /// TSIG always uses 0 TTL.  This static method returns it,
+    /// when, though unlikely, an application wants to know the TTL TSIG
+    /// is supposed to use.
+    ///
+    /// \exception None
+    static const RRTTL& getTTL();
+    //@}
+
+    /// Return the length of the TSIG record
+    ///
+    /// When constructed from the key name and RDATA, it is the length of
+    /// the record when it is rendered by the \c toWire() method.
+    ///
+    /// \note When constructed "from wire", that will mean the length of
+    /// the wire format data for the TSIG RR.  The length will be necessary
+    /// to verify the message once parse is completed.
+    ///
+    /// \exception None
+    size_t getLength() const { return (length_); }
+
+    /// \brief Render the \c TSIG RR in the wire format.
+    ///
+    /// This method renders the TSIG record as a form of a DNS TSIG RR
+    /// via \c renderer, which encapsulates output buffer and other rendering
+    /// contexts.
+    ///
+    /// Normally this version of \c toWire() method tries to compress the
+    /// owner name of the RR whenever possible, but this method intentionally
+    /// skips owner name compression.  This is due to a report that some
+    /// Windows clients refuse a TSIG if its owner name is compressed
+    /// (See http://marc.info/?l=bind-workers&m=126637138430819&w=2).
+    /// Reportedly this seemed to be specific to GSS-TSIG, but this
+    /// implementation skip compression regardless of the algorithm.
+    ///
+    /// If by adding the TSIG RR the message size would exceed the limit
+    /// maintained in \c renderer, this method skips rendering the RR
+    /// and returns 0 and mark \c renderer as "truncated" (so that a
+    /// subsequent call to \c isTruncated() on \c renderer will result in
+    /// \c true); otherwise it returns 1, which is the number of RR
+    /// rendered.
+    ///
+    /// \note If the caller follows the specification of adding TSIG
+    /// as described in RFC2845, this should not happen; the caller is
+    /// generally expected to leave a sufficient room in the message for
+    /// the TSIG.  But this method checks the unexpected case nevertheless.
+    ///
+    /// \exception std::bad_alloc Internal resource allocation fails (this
+    /// should be rare).
+    ///
+    /// \param renderer DNS message rendering context that encapsulates the
+    /// output buffer and name compression information.
+    /// \return 1 if the TSIG RR fits in the message size limit; otherwise 0.
+    int toWire(AbstractMessageRenderer& renderer) const;
+
+    /// \brief Render the \c TSIG RR in the wire format.
+    ///
+    /// This method is same as \c toWire(AbstractMessageRenderer&)const
+    /// except it renders the RR in an \c OutputBuffer and therefore
+    /// does not care about message size limit.
+    /// As a consequence it always returns 1.
+    int toWire(isc::util::OutputBuffer& buffer) const;
+
+    /// Convert the TSIG record to a string.
+    ///
+    /// The output format is the same as the result of \c toText() for
+    /// other normal types of RRsets (with always using the same RR class
+    /// and TTL).  It also ends with a newline.
+    ///
+    /// \exception std::bad_alloc Internal resource allocation fails (this
+    /// should be rare).
+    ///
+    /// \return A string representation of \c TSIG record
+    std::string toText() const;
+
+    /// The TTL value to be used in TSIG RRs.
+    static const uint32_t TSIG_TTL = 0;
+    //@}
+
+private:
+    const Name key_name_;
+    const rdata::any::TSIG rdata_;
+    const size_t length_;
+};
+
+/// A pointer-like type pointing to a \c TSIGRecord object.
+typedef boost::shared_ptr<TSIGRecord> TSIGRecordPtr;
+
+/// A pointer-like type pointing to an immutable \c TSIGRecord object.
+typedef boost::shared_ptr<const TSIGRecord> ConstTSIGRecordPtr;
+
+/// Insert the \c TSIGRecord as a string into stream.
+///
+/// This method convert \c record into a string and inserts it into the
+/// output stream \c os.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param record A \c TSIGRecord 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 TSIGRecord& record);
+}
+}
+
+#endif  // __TSIGRECORD_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 2 - 5
src/lib/util/time_utilities.cc

@@ -110,12 +110,9 @@ timeToText64(uint64_t value) {
 // library, it's not even declared in a header file.
 namespace detail {
 int64_t (*gettimeFunction)() = NULL;
-}
 
-namespace {
 int64_t
-gettimeofdayWrapper() {
-    using namespace detail;
+gettimeWrapper() {
     if (gettimeFunction != NULL) {
         return (gettimeFunction());
     }
@@ -132,7 +129,7 @@ timeToText32(const uint32_t value) {
     // We first adjust the time to the closest epoch based on the current time.
     // Note that the following variables must be signed in order to handle
     // time until year 2038 correctly.
-    const int64_t start = gettimeofdayWrapper() - 0x7fffffff;
+    const int64_t start = detail::gettimeWrapper() - 0x7fffffff;
     int64_t base = 0;
     int64_t t;
     while ((t = (base + value)) < start) {

+ 28 - 0
src/lib/util/time_utilities.h

@@ -15,6 +15,8 @@
 #ifndef __TIME_UTILITIES_H
 #define __TIME_UTILITIES_H 1
 
+#include <string>
+
 #include <sys/types.h>
 #include <stdint.h>
 
@@ -39,6 +41,32 @@ public:
         isc::Exception(file, line, what) {}
 };
 
+namespace detail {
+/// Return the current time in seconds
+///
+/// This function returns the "current" time in seconds from epoch
+/// (00:00:00 January 1, 1970) as a 64-bit signed integer.  The return
+/// value can represent a point of time before epoch as a negative number.
+///
+/// This function is provided to help test time conscious implementations
+/// such as DNSSEC and TSIG signatures.  It is difficult to test them with
+/// an unusual or a specifically chosen "current" via system-provided
+/// library functions to get time.  This function acts as a straightforward
+/// wrapper of such a library function, but provides test code with a hook
+/// to return an arbitrary time value: if \c isc::util::detail::gettimeFunction
+/// is set to a pointer of function that returns 64-bit signed integer,
+/// \c gettimeWrapper() calls that function instead of the system library.
+///
+/// This hook variable is specifically intended for testing purposes, so,
+/// even if it's visible outside of this library, it's not even declared in a
+/// header file.
+///
+/// If the implementation doesn't need to be tested with faked current time,
+/// it should simply use the system supplied library function instead of
+/// this one.
+int64_t gettimeWrapper();
+}
+
 ///
 /// \name DNSSEC time conversion functions.
 ///

+ 2 - 0
src/lib/util/unittests/Makefile.am

@@ -4,5 +4,7 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
 lib_LTLIBRARIES = libutil_unittests.la
 libutil_unittests_la_SOURCES = fork.h fork.cc resolver.h
 libutil_unittests_la_SOURCES += newhook.h newhook.cc
+libutil_unittests_la_SOURCES += testdata.h testdata.cc
+libutil_unittests_la_SOURCES += textdata.h
 
 CLEANFILES = *.gcno *.gcda

+ 61 - 0
src/lib/util/unittests/testdata.cc

@@ -0,0 +1,61 @@
+// Copyright (C) 2011  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 <string>
+#include <stdexcept>
+#include <fstream>
+#include <vector>
+
+#include "testdata.h"
+
+using namespace std;
+
+namespace {
+vector<string>&
+getDataPaths() {
+    static vector<string> data_path;
+    return (data_path);
+}
+}
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+void
+addTestDataPath(const string& path) {
+    getDataPaths().push_back(path);
+}
+
+void
+openTestData(const char* const datafile, ifstream& ifs) {
+    vector<string>::const_iterator it = getDataPaths().begin();
+    for (; it != getDataPaths().end(); ++it) {
+        string data_path = *it;
+        if (data_path.empty() || *data_path.rbegin() != '/') {
+            data_path.push_back('/');
+        }
+        ifs.open((data_path + datafile).c_str(), ios_base::in);
+        if (!ifs.fail()) {
+            return;
+        }
+    }
+
+    throw runtime_error("failed to open data file in data paths: " +
+                        string(datafile));
+}
+
+}
+}
+}

+ 46 - 0
src/lib/util/unittests/testdata.h

@@ -0,0 +1,46 @@
+// Copyright (C) 2011  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 __UTIL_UNITTESTS_TESTDATA_H
+#define __UTIL_UNITTESTS_TESTDATA_H 1
+
+/**
+ * @file testdata.h
+ * @short Manipulating test data files.
+ *
+ * This utility defines functions that help test case handle test data
+ * stored in a file.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+/// Add a path (directory) that \c openTestData() will search for test data
+/// files.
+void addTestDataPath(const std::string& path);
+
+/// Open a file specified by 'datafile' using the data paths registered via
+/// addTestDataPath().  On success, ifs will be ready for reading the data
+/// stored in 'datafile'.  If the data file cannot be open with any of the
+/// registered paths, a runtime_error exception will be thrown.
+void openTestData(const char* const datafile, std::ifstream& ifs);
+}
+}
+}
+
+#endif // __UTIL_UNITTESTS_TESTDATA_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 105 - 0
src/lib/util/unittests/textdata.h

@@ -0,0 +1,105 @@
+// Copyright (C) 2011  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 <istream>
+#include <string>
+#include <sstream>
+
+#include <gtest/gtest.h>
+
+#ifndef __UTIL_UNITTESTS_TEXTDATA_H
+#define __UTIL_UNITTESTS_TEXTDATA_H 1
+
+/**
+ * @file textdata.h
+ * @short Utilities for tests with text data.
+ *
+ * This utility provides convenient helper functions for unit tests using
+ * textual data.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// Line-by-line text comparison.
+///
+/// This templated function takes two standard input streams, extracts
+/// strings from them, and compares the two sets of strings line by line.
+template <typename EXPECTED_STREAM, typename ACTUAL_STREAM>
+void
+matchTextData(EXPECTED_STREAM& expected, ACTUAL_STREAM& actual) {
+    std::string actual_line;
+    std::string expected_line;
+    while (std::getline(actual, actual_line), !actual.eof()) {
+        std::getline(expected, expected_line);
+        if (expected.eof()) {
+            ASSERT_FALSE(true) << "Redundant line in actual output: "
+                               << actual_line;
+            break;
+        }
+        if (actual.bad() || actual.fail() ||
+            expected.bad() || expected.fail()) {
+            throw std::runtime_error("Unexpected error in data streams");
+        }
+        EXPECT_EQ(expected_line, actual_line);
+    }
+    while (std::getline(expected, expected_line), !expected.eof()) {
+        EXPECT_FALSE(true) << "Missing line in actual output: "
+                           << expected_line;
+    }
+}
+
+/// Similar to the fully templated version, but takes string for the second
+/// (actual) data.
+///
+/// Due to the nature of textual data, it will often be the case that test
+/// data is given as a string object.  This shortcut version helps such cases
+/// so that the test code doesn't have to create a string stream with the
+/// string data just for testing.
+template <typename EXPECTED_STREAM>
+void
+matchTextData(EXPECTED_STREAM& expected, const std::string& actual_text) {
+    std::istringstream iss(actual_text);
+    matchTextData(expected, iss);
+}
+
+/// Same for the previous version, but the first argument is string.
+template <typename ACTUAL_STREAM>
+void
+matchTextData(const std::string& expected_text, ACTUAL_STREAM& actual) {
+    std::istringstream iss(expected_text);
+    matchTextData(iss, actual);
+}
+
+/// Same for the previous two, but takes strings for both expected and
+/// actual data.
+void
+matchTextData(const std::string& expected_text,
+              const std::string& actual_text)
+{
+    std::istringstream expected_is(expected_text);
+    std::istringstream actual_is(actual_text);
+    matchTextData(expected_is, actual_is);
+}
+
+}
+}
+}
+
+#endif // __UTIL_UNITTESTS_TEXTDATA_H
+
+// Local Variables:
+// mode: c++
+// End: