Browse Source

Merge branch 'master' into trac1680

haikuo zhang 13 years ago
parent
commit
a0a39d4b6e

+ 30 - 1
ChangeLog

@@ -1,3 +1,32 @@
+394.	[defect]	jelte
+	b10-auth now catches any exceptions during response building; if any
+	datasource either throws an exception or causes an exception to be
+	thrown, the message processing code will now catch it, log a debug
+	message, and return a SERVFAIL response.
+	(Trac #1612, git b5740c6b3962a55e46325b3c8b14c9d64cf0d845)
+
+393.	[func]		jelte
+	Introduced a new class LabelSequence in libdns++, which provides
+	lightweight accessor functionality to the Name class, for more
+	efficient comparison of parts of names.
+	(Trac #1602, git b33929ed5df7c8f482d095e96e667d4a03180c78)
+
+392.	[func]*		jinmei
+	libdns++: revised the (Abstract)MessageRenderer class so that it
+	has a default internal buffer and the buffer can be temporarily
+	switched.  The constructor interface was modified, and a new
+	method setBuffer() was added.
+	(Trac #1697, git 9cabc799f2bf9a3579dae7f1f5d5467c8bb1aa40)
+
+391.	[bug]*		vorner
+	The long time unused configuration options of Xfrout "log_name",
+	"log_file", "log_severity", "log_version" and "log_max_bytes" were
+	removed, as they had no effect (Xfrout uses the global logging
+	framework).  However, if you have them set, you need to remove
+	them from the configuration file or the configuration will be
+	rejected.
+	(Trac #1090, git ef1eba02e4cf550e48e7318702cff6d67c1ec82e)
+
 bind10-devel-20120301 released on March 1, 2012
 
 390.	[bug]		vorner
@@ -55,7 +84,7 @@ bind10-devel-20120301 released on March 1, 2012
 
 382.	[func]		jelte
 	b10-auth now also experimentally supports statistics counters of
-	the rcode reponses it sends. The counters can be shown as
+	the rcode responses it sends. The counters can be shown as
 	rcode.<code name>, where code name is the lowercase textual
 	representation of the rcode (e.g. "noerror", "formerr", etc.).
 	Same note applies as for opcodes, see changelog entry 364.

+ 4 - 4
doc/guide/bind10-guide.xml

@@ -1019,7 +1019,7 @@ Unix domain sockets
 <!-- TODO -->
       <note>
         <para>
-          The development prototype release only provides the
+          The development prototype release only provides
           <command>bindctl</command> as a user interface to
           <command>b10-cmdctl</command>.
           Upcoming releases will provide another interactive command-line
@@ -1210,7 +1210,7 @@ or accounts database -->
       The port can be set by using the <option>--port</option> command line option.
       The address to listen on can be set using the <option>--address</option> command
       line argument.
-      Each HTTPS connection is stateless and timesout in 1200 seconds
+      Each HTTPS connection is stateless and times out in 1200 seconds
       by default.  This can be
       redefined by using the <option>--idle-timeout</option> command line argument.
     </para>
@@ -1661,7 +1661,7 @@ Xfrout/transfer_acl[0]	{"action": "ACCEPT"}	any	(default)</screen>
 &gt; <userinput>config commit</userinput></screen>
 
     <para>Both Xfrout and Auth will use the system wide keyring to check
-    TSIGs in the incomming messages and to sign responses.</para>
+    TSIGs in the incoming messages and to sign responses.</para>
 
     <note><simpara>
         The way to specify zone specific configuration (ACLs, etc) is
@@ -2371,7 +2371,7 @@ eth0 fe80::21e:8cff:fe9b:7349
 
           In the Logging module, you can specify the configuration
           for zero or more loggers; any that are not specified will
-          take appropriate default values..
+          take appropriate default values.
 
         </para>
 

+ 29 - 16
src/bin/auth/auth_messages.mes

@@ -73,6 +73,10 @@ attempt to parse the header of a received DNS packet has failed. (The
 reason for the failure is given in the message.) The server will drop the
 packet.
 
+% AUTH_INVALID_STATISTICS_DATA invalid specification of statistics data specified
+An error was encountered when the authoritiative server specified
+statistics data which is invalid for the auth specification file.
+
 % AUTH_LOAD_TSIG loading TSIG keys
 This is a debug message indicating that the authoritative server
 has requested the keyring holding TSIG keys from the configuration
@@ -92,6 +96,18 @@ discovered that the memory data source is disabled for the given class.
 This is a debug message reporting that the authoritative server has
 discovered that the memory data source is enabled for the given class.
 
+% AUTH_NOTIFY_QUESTIONS invalid number of questions (%1) in incoming NOTIFY
+This debug message is logged by the authoritative server when it receives
+a NOTIFY packet that contains zero or more than one question. (A valid
+NOTIFY packet contains one question.) The server will return a FORMERR
+error to the sender.
+
+% AUTH_NOTIFY_RRTYPE invalid question RR type (%1) in incoming NOTIFY
+This debug message is logged by the authoritative server when it receives
+a NOTIFY packet that an RR type of something other than SOA in the
+question section. (The RR type received is included in the message.) The
+server will return a FORMERR error to the sender.
+
 % AUTH_NO_STATS_SESSION session interface for statistics is not available
 The authoritative server had no session with the statistics module at the
 time it attempted to send it data: the attempt has been abandoned. This
@@ -102,18 +118,6 @@ This is a debug message produced by the authoritative server when it receives
 a NOTIFY packet but the XFRIN process is not running. The packet will be
 dropped and nothing returned to the sender.
 
-% AUTH_NOTIFY_RRTYPE invalid question RR type (%1) in incoming NOTIFY
-This debug message is logged by the authoritative server when it receives
-a NOTIFY packet that an RR type of something other than SOA in the
-question section. (The RR type received is included in the message.) The
-server will return a FORMERR error to the sender.
-
-% AUTH_NOTIFY_QUESTIONS invalid number of questions (%1) in incoming NOTIFY
-This debug message is logged by the authoritative server when it receives
-a NOTIFY packet that contains zero or more than one question. (A valid
-NOTIFY packet contains one question.) The server will return a FORMERR
-error to the sender.
-
 % AUTH_PACKET_PARSE_ERROR unable to parse received DNS packet: %1
 This is a debug message, generated by the authoritative server when an
 attempt to parse a received DNS packet has failed due to something other
@@ -154,6 +158,19 @@ a command from the statistics module to send it data. The 'sendstats'
 command is handled differently to other commands, which is why the debug
 message associated with it has its own code.
 
+% AUTH_RESPONSE_FAILURE exception while building response to query: %1
+This is a debug message, generated by the authoritative server when an
+attempt to create a response to a received DNS packet has failed. The
+reason for the failure is given in the log message. A SERVFAIL response
+is sent back. The most likely cause of this is an error in the data
+source implementation; it is either creating bad responses or raising
+exceptions itself.
+
+% AUTH_RESPONSE_FAILURE_UNKNOWN unknown exception while building response to query
+This debug message is similar to AUTH_RESPONSE_FAILURE, but further
+details about the error are unknown, because it was signaled by something
+which is not an exception. This is definitely a bug.
+
 % AUTH_RESPONSE_RECEIVED received response message, ignoring
 This is a debug message, this is output if the authoritative server
 receives a DNS packet with the QR bit set, i.e. a DNS response. The
@@ -260,7 +277,3 @@ This is a debug message output during the processing of a NOTIFY
 request. The zone manager component has been informed of the request,
 but has returned an error response (which is included in the message). The
 NOTIFY request will not be honored.
-
-% AUTH_INVALID_STATISTICS_DATA invalid specification of statistics data specified
-An error was encountered when the authoritiative server specified
-statistics data which is invalid for the auth specification file.

+ 33 - 25
src/bin/auth/auth_srv.cc

@@ -481,35 +481,43 @@ AuthSrv::processMessage(const IOMessage& io_message, MessagePtr message,
         return;
     }
 
-    // update per opcode statistics counter.  This can only be reliable after
-    // TSIG check succeeds.
-    impl_->counters_.inc(message->getOpcode());
-
     bool send_answer = true;
-    if (message->getOpcode() == Opcode::NOTIFY()) {
-        send_answer = impl_->processNotify(io_message, message, buffer,
-                                           tsig_context);
-    } else if (message->getOpcode() != Opcode::QUERY()) {
-        LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_UNSUPPORTED_OPCODE)
-                  .arg(message->getOpcode().toText());
-        makeErrorMessage(message, buffer, Rcode::NOTIMP(), tsig_context);
-    } else if (message->getRRCount(Message::SECTION_QUESTION) != 1) {
-        makeErrorMessage(message, buffer, Rcode::FORMERR(), tsig_context);
-    } else {
-        ConstQuestionPtr question = *message->beginQuestion();
-        const RRType &qtype = question->getType();
-        if (qtype == RRType::AXFR()) {
-            send_answer = impl_->processXfrQuery(io_message, message, buffer,
-                                                 tsig_context);
-        } else if (qtype == RRType::IXFR()) {
-            send_answer = impl_->processXfrQuery(io_message, message, buffer,
-                                                 tsig_context);
+    try {
+        // update per opcode statistics counter.  This can only be reliable
+        // after TSIG check succeeds.
+        impl_->counters_.inc(message->getOpcode());
+
+        if (message->getOpcode() == Opcode::NOTIFY()) {
+            send_answer = impl_->processNotify(io_message, message, buffer,
+                                               tsig_context);
+        } else if (message->getOpcode() != Opcode::QUERY()) {
+            LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_UNSUPPORTED_OPCODE)
+                      .arg(message->getOpcode().toText());
+            makeErrorMessage(message, buffer, Rcode::NOTIMP(), tsig_context);
+        } else if (message->getRRCount(Message::SECTION_QUESTION) != 1) {
+            makeErrorMessage(message, buffer, Rcode::FORMERR(), tsig_context);
         } else {
-            send_answer = impl_->processNormalQuery(io_message, message,
-                                                    buffer, tsig_context);
+            ConstQuestionPtr question = *message->beginQuestion();
+            const RRType &qtype = question->getType();
+            if (qtype == RRType::AXFR()) {
+                send_answer = impl_->processXfrQuery(io_message, message,
+                                                     buffer, tsig_context);
+            } else if (qtype == RRType::IXFR()) {
+                send_answer = impl_->processXfrQuery(io_message, message,
+                                                     buffer, tsig_context);
+            } else {
+                send_answer = impl_->processNormalQuery(io_message, message,
+                                                        buffer, tsig_context);
+            }
         }
+    } catch (const std::exception& ex) {
+        LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_FAILURE)
+                  .arg(ex.what());
+        makeErrorMessage(message, buffer, Rcode::SERVFAIL());
+    } catch (...) {
+        LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_FAILURE_UNKNOWN);
+        makeErrorMessage(message, buffer, Rcode::SERVFAIL());
     }
-
     impl_->resumeServer(server, message, send_answer);
 }
 

+ 7 - 5
src/bin/auth/b10-auth.8

@@ -2,12 +2,12 @@
 .\"     Title: b10-auth
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: February 28, 2012
+.\"      Date: March 1, 2012
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"
-.TH "B10\-AUTH" "8" "February 28, 2012" "BIND10" "BIND10"
+.TH "B10\-AUTH" "8" "March 1, 2012" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
@@ -161,16 +161,18 @@ argument to select the process ID to stop\&. (Note that the BIND 10 boss process
 .PP
 The statistics data collected by the
 \fBb10\-stats\fR
-daemon include:
+daemon for
+\(lqAuth\(rq
+include:
 .PP
-auth\&.queries\&.tcp
+queries\&.tcp
 .RS 4
 Total count of queries received by the
 \fBb10\-auth\fR
 server over TCP since startup\&.
 .RE
 .PP
-auth\&.queries\&.udp
+queries\&.udp
 .RS 4
 Total count of queries received by the
 \fBb10\-auth\fR

+ 4 - 4
src/bin/auth/b10-auth.xml

@@ -20,7 +20,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>February 28, 2012</date>
+    <date>March 1, 2012</date>
   </refentryinfo>
 
   <refmeta>
@@ -201,20 +201,20 @@
 
     <para>
       The statistics data collected by the <command>b10-stats</command>
-      daemon include:
+      daemon for <quote>Auth</quote> include:
     </para>
 
     <variablelist>
 
       <varlistentry>
-        <term>auth.queries.tcp</term>
+        <term>queries.tcp</term>
         <listitem><simpara>Total count of queries received by the
           <command>b10-auth</command> server over TCP since startup.
         </simpara></listitem>
       </varlistentry>
 
       <varlistentry>
-        <term>auth.queries.udp</term>
+        <term>queries.udp</term>
         <listitem><simpara>Total count of queries received by the
           <command>b10-auth</command> server over UDP since startup.
         </simpara></listitem>

+ 256 - 17
src/bin/auth/tests/auth_srv_unittest.cc

@@ -87,7 +87,11 @@ protected:
         server.setXfrinSession(&notify_session);
         server.setStatisticsSession(&statistics_session);
     }
+
     virtual void processMessage() {
+        // If processMessage has been called before, parse_message needs
+        // to be reset. If it hasn't, there's no harm in doing so
+        parse_message->clear(Message::PARSE);
         server.processMessage(*io_message, parse_message, response_obuffer,
                               &dnsserv);
     }
@@ -120,6 +124,17 @@ protected:
             }
         }
     }
+
+    // Convenience method for tests that expect to return SERVFAIL
+    // It calls processMessage, checks if there is an answer, and
+    // check the header for default SERVFAIL data
+    void processAndCheckSERVFAIL() {
+        processMessage();
+        EXPECT_TRUE(dnsserv.hasAnswer());
+        headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+                    opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+    }
+
     IOService ios_;
     DNSService dnss_;
     MockSession statistics_session;
@@ -479,17 +494,17 @@ TEST_F(AuthSrvTest, AXFRSendFail) {
 }
 
 TEST_F(AuthSrvTest, AXFRDisconnectFail) {
-    // In our usage disconnect() shouldn't fail.  So we'll see the exception
-    // should it be thrown.
+    // In our usage disconnect() shouldn't fail. But even if it does,
+    // it should not disrupt service (so processMessage should have caught it)
     xfrout.disableSend();
     xfrout.disableDisconnect();
     UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
                                        Name("example.com"), RRClass::IN(),
                                        RRType::AXFR());
     createRequestPacket(request_message, IPPROTO_TCP);
-    EXPECT_THROW(server.processMessage(*io_message, parse_message,
-                                       response_obuffer, &dnsserv),
-                 XfroutError);
+    EXPECT_NO_THROW(server.processMessage(*io_message, parse_message,
+                                          response_obuffer, &dnsserv));
+    // Since the disconnect failed, we should still be 'connected'
     EXPECT_TRUE(xfrout.isConnected());
     // XXX: we need to re-enable disconnect.  otherwise an exception would be
     // thrown via the destructor of the server.
@@ -537,17 +552,16 @@ TEST_F(AuthSrvTest, IXFRSendFail) {
 }
 
 TEST_F(AuthSrvTest, IXFRDisconnectFail) {
-    // In our usage disconnect() shouldn't fail.  So we'll see the exception
-    // should it be thrown.
+    // In our usage disconnect() shouldn't fail, but even if it does,
+    // procesMessage() should catch it.
     xfrout.disableSend();
     xfrout.disableDisconnect();
     UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
                                        Name("example.com"), RRClass::IN(),
                                        RRType::IXFR());
     createRequestPacket(request_message, IPPROTO_TCP);
-    EXPECT_THROW(server.processMessage(*io_message, parse_message,
-                                       response_obuffer, &dnsserv),
-                 XfroutError);
+    EXPECT_NO_THROW(server.processMessage(*io_message, parse_message,
+                                          response_obuffer, &dnsserv));
     EXPECT_TRUE(xfrout.isConnected());
     // XXX: we need to re-enable disconnect.  otherwise an exception would be
     // thrown via the destructor of the server.
@@ -747,7 +761,8 @@ updateConfig(AuthSrv* server, const char* const config_data,
 
     ConstElementPtr result = config_answer->get("result");
     EXPECT_EQ(Element::list, result->getType());
-    EXPECT_EQ(expect_success ? 0 : 1, result->get(0)->intValue());
+    EXPECT_EQ(expect_success ? 0 : 1, result->get(0)->intValue()) <<
+        "Bad result from updateConfig: " << result->str();
 }
 
 // Install a Sqlite3 data source with testing data.
@@ -987,11 +1002,10 @@ getDummyUnknownSocket() {
     return (socket);
 }
 
-// Submit unexpected type of query and check it throws isc::Unexpected
+// Submit unexpected type of query and check it is ignored
 TEST_F(AuthSrvTest, queryCounterUnexpected) {
     // This code isn't exception safe, but we'd rather keep the code
-    // simpler and more readable as this is only for tests and if it throws
-    // the program would immediately terminate anyway.
+    // simpler and more readable as this is only for tests
 
     // Create UDP query packet.
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
@@ -1007,9 +1021,7 @@ TEST_F(AuthSrvTest, queryCounterUnexpected) {
                                request_renderer.getLength(),
                                getDummyUnknownSocket(), *endpoint);
 
-    EXPECT_THROW(server.processMessage(*io_message, parse_message,
-                                       response_obuffer, &dnsserv),
-                 isc::Unexpected);
+    EXPECT_FALSE(dnsserv.hasAnswer());
 }
 
 TEST_F(AuthSrvTest, stop) {
@@ -1038,4 +1050,231 @@ TEST_F(AuthSrvTest, listenAddresses) {
                                 "Released tokens");
 }
 
+//
+// Tests for catching exceptions in various stages of the query processing
+//
+// These tests work by defining two proxy classes, that act as an in-memory
+// client by default, but can throw exceptions at various points.
+//
+namespace {
+
+/// A the possible methods to throw in, either in FakeInMemoryClient or
+/// FakeZoneFinder
+enum ThrowWhen {
+    THROW_NEVER,
+    THROW_AT_FIND_ZONE,
+    THROW_AT_GET_ORIGIN,
+    THROW_AT_GET_CLASS,
+    THROW_AT_FIND,
+    THROW_AT_FIND_ALL,
+    THROW_AT_FIND_NSEC3
+};
+
+/// convenience function to check whether and what to throw
+void
+checkThrow(ThrowWhen method, ThrowWhen throw_at, bool isc_exception) {
+    if (method == throw_at) {
+        if (isc_exception) {
+            isc_throw(isc::Exception, "foo");
+        } else {
+            throw std::exception();
+        }
+    }
+}
+
+/// \brief proxy class for the ZoneFinder returned by the InMemoryClient
+///        proxied by FakeInMemoryClient
+///
+/// See the documentation for FakeInMemoryClient for more information,
+/// all methods simply check whether they should throw, and if not, call
+/// their proxied equivalent.
+class FakeZoneFinder : public isc::datasrc::ZoneFinder {
+public:
+    FakeZoneFinder(isc::datasrc::ZoneFinderPtr zone_finder,
+                   ThrowWhen throw_when,
+                   bool isc_exception) :
+        real_zone_finder_(zone_finder),
+        throw_when_(throw_when),
+        isc_exception_(isc_exception)
+    {}
+
+    virtual isc::dns::Name
+    getOrigin() const {
+        checkThrow(THROW_AT_GET_ORIGIN, throw_when_, isc_exception_);
+        return (real_zone_finder_->getOrigin());
+    }
+
+    virtual isc::dns::RRClass
+    getClass() const {
+        checkThrow(THROW_AT_GET_CLASS, throw_when_, isc_exception_);
+        return (real_zone_finder_->getClass());
+    }
+
+    virtual isc::datasrc::ZoneFinder::FindResult
+    find(const isc::dns::Name& name,
+         const isc::dns::RRType& type,
+         isc::datasrc::ZoneFinder::FindOptions options)
+    {
+        checkThrow(THROW_AT_FIND, throw_when_, isc_exception_);
+        return (real_zone_finder_->find(name, type, options));
+    }
+
+    virtual FindResult
+    findAll(const isc::dns::Name& name,
+            std::vector<isc::dns::ConstRRsetPtr> &target,
+            const FindOptions options = FIND_DEFAULT)
+    {
+        checkThrow(THROW_AT_FIND_ALL, throw_when_, isc_exception_);
+        return (real_zone_finder_->findAll(name, target, options));
+    };
+
+    virtual FindNSEC3Result
+    findNSEC3(const isc::dns::Name& name, bool recursive) {
+        checkThrow(THROW_AT_FIND_NSEC3, throw_when_, isc_exception_);
+        return (real_zone_finder_->findNSEC3(name, recursive));
+    };
+
+    virtual isc::dns::Name
+    findPreviousName(const isc::dns::Name& query) const {
+        return (real_zone_finder_->findPreviousName(query));
+    }
+
+private:
+    isc::datasrc::ZoneFinderPtr real_zone_finder_;
+    ThrowWhen throw_when_;
+    bool isc_exception_;
+};
+
+/// \brief Proxy InMemoryClient that can throw exceptions at specified times
+///
+/// It is based on the memory client since that one is easy to override
+/// (with setInMemoryClient) with the current design of AuthSrv.
+class FakeInMemoryClient : public isc::datasrc::InMemoryClient {
+public:
+    /// \brief Create a proxy memory client
+    ///
+    /// \param real_client The real in-memory client to proxy
+    /// \param throw_when if set to any value other than never, that is
+    ///        the method that will throw an exception (either in this
+    ///        class or the related FakeZoneFinder)
+    /// \param isc_exception if true, throw isc::Exception, otherwise,
+    ///                      throw std::exception
+    FakeInMemoryClient(AuthSrv::InMemoryClientPtr real_client,
+                       ThrowWhen throw_when,
+                       bool isc_exception) :
+        real_client_(real_client),
+        throw_when_(throw_when),
+        isc_exception_(isc_exception)
+    {}
+
+    /// \brief proxy call for findZone
+    ///
+    /// if this instance was constructed with throw_when set to find_zone,
+    /// this method will throw. Otherwise, it will return a FakeZoneFinder
+    /// instance which will throw at the method specified at the
+    /// construction of this instance.
+    virtual FindResult
+    findZone(const isc::dns::Name& name) const {
+        checkThrow(THROW_AT_FIND_ZONE, throw_when_, isc_exception_);
+        const FindResult result = real_client_->findZone(name);
+        return (FindResult(result.code, isc::datasrc::ZoneFinderPtr(
+                                        new FakeZoneFinder(result.zone_finder,
+                                        throw_when_,
+                                        isc_exception_))));
+    }
+
+private:
+    AuthSrv::InMemoryClientPtr real_client_;
+    ThrowWhen throw_when_;
+    bool isc_exception_;
+};
+
+} // end anonymous namespace for throwing proxy classes
+
+// Test for the tests
+//
+// Set the proxies to never throw, this should have the same result as
+// queryWithInMemoryClientNoDNSSEC, and serves to test the two proxy classes
+TEST_F(AuthSrvTest, queryWithInMemoryClientProxy) {
+    // Set real inmem client to proxy
+    updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
+
+    AuthSrv::InMemoryClientPtr fake_client(
+        new FakeInMemoryClient(server.getInMemoryClient(rrclass),
+                               THROW_NEVER,
+                               false));
+
+    ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    server.setInMemoryClient(rrclass, fake_client);
+
+    createDataFromFile("nsec3query_nodnssec_fromWire.wire");
+    server.processMessage(*io_message, parse_message, response_obuffer,
+                          &dnsserv);
+
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::NOERROR(),
+                opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
+}
+
+// Convenience function for the rest of the tests, set up a proxy
+// to throw in the given method
+// If isc_exception is true, it will throw isc::Exception, otherwise
+// it will throw std::exception
+void
+setupThrow(AuthSrv* server, const char *config, ThrowWhen throw_when,
+                   bool isc_exception)
+{
+    // Set real inmem client to proxy
+    updateConfig(server, config, true);
+
+    // Set it to throw on findZone(), this should result in
+    // SERVFAIL on any exception
+    AuthSrv::InMemoryClientPtr fake_client(
+        new FakeInMemoryClient(
+            server->getInMemoryClient(isc::dns::RRClass::IN()),
+            throw_when,
+            isc_exception));
+
+    ASSERT_NE(AuthSrv::InMemoryClientPtr(),
+              server->getInMemoryClient(isc::dns::RRClass::IN()));
+    server->setInMemoryClient(isc::dns::RRClass::IN(), fake_client);
+}
+
+TEST_F(AuthSrvTest, queryWithThrowingProxyServfails) {
+    // Test the common cases, all of which should simply return SERVFAIL
+    // Use THROW_NEVER as end marker
+    ThrowWhen throws[] = { THROW_AT_FIND_ZONE,
+                           THROW_AT_GET_ORIGIN,
+                           THROW_AT_FIND,
+                           THROW_AT_FIND_NSEC3,
+                           THROW_NEVER };
+    UnitTestUtil::createDNSSECRequestMessage(request_message, opcode,
+                                             default_qid, Name("foo.example."),
+                                             RRClass::IN(), RRType::TXT());
+    for (ThrowWhen* when(throws); *when != THROW_NEVER; ++when) {
+        createRequestPacket(request_message, IPPROTO_UDP);
+        setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, *when, true);
+        processAndCheckSERVFAIL();
+        // To be sure, check same for non-isc-exceptions
+        createRequestPacket(request_message, IPPROTO_UDP);
+        setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, *when, false);
+        processAndCheckSERVFAIL();
+    }
+}
+
+// Throw isc::Exception in getClass(). (Currently?) getClass is not called
+// in the processMessage path, so this should result in a normal answer
+TEST_F(AuthSrvTest, queryWithInMemoryClientProxyGetClass) {
+    createDataFromFile("nsec3query_nodnssec_fromWire.wire");
+    setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, THROW_AT_GET_CLASS, true);
+
+    // getClass is not called so it should just answer
+    server.processMessage(*io_message, parse_message, response_obuffer,
+                          &dnsserv);
+
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::NOERROR(),
+                opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
+}
+
 }

+ 6 - 4
src/bin/bind10/bind10.8

@@ -2,12 +2,12 @@
 .\"     Title: bind10
 .\"    Author: [see the "AUTHORS" section]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: February 28, 2012
+.\"      Date: March 1, 2012
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"
-.TH "BIND10" "8" "February 28, 2012" "BIND10" "BIND10"
+.TH "BIND10" "8" "March 1, 2012" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
@@ -297,9 +297,11 @@ will exit\&.
 .PP
 The statistics data collected by the
 \fBb10\-stats\fR
-daemon include:
+daemon for
+\(lqBoss\(rq
+include:
 .PP
-bind10\&.boot_time
+boot_time
 .RS 4
 The date and time that the
 \fBbind10\fR

+ 3 - 3
src/bin/bind10/bind10.xml

@@ -20,7 +20,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>February 28, 2012</date>
+    <date>March 1, 2012</date>
   </refentryinfo>
 
   <refmeta>
@@ -433,13 +433,13 @@ xfrin
 
     <para>
       The statistics data collected by the <command>b10-stats</command>
-      daemon include:
+      daemon for <quote>Boss</quote> include:
     </para>
 
     <variablelist>
 
       <varlistentry>
-        <term>bind10.boot_time</term>
+        <term>boot_time</term>
         <listitem><para>
           The date and time that the <command>bind10</command>
           process started.

+ 16 - 14
src/bin/stats/b10-stats.8

@@ -2,12 +2,12 @@
 .\"     Title: b10-stats
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: February 28, 2012
+.\"      Date: March 1, 2012
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"
-.TH "B10\-STATS" "8" "February 28, 2012" "BIND10" "BIND10"
+.TH "B10\-STATS" "8" "March 1, 2012" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
@@ -90,25 +90,22 @@ simply indicates that the daemon is running\&.
 .PP
 The
 \fBb10\-stats\fR
-daemon contains these statistics:
+daemon contains these
+\(lqStats\(rq
+statistics:
 .PP
-report_time
-.RS 4
-The latest report date and time in ISO 8601 format\&.
-.RE
-.PP
-stats\&.boot_time
+boot_time
 .RS 4
 The date and time when this daemon was started in ISO 8601 format\&. This is a constant which can\'t be reset except by restarting
 \fBb10\-stats\fR\&.
 .RE
 .PP
-stats\&.last_update_time
+last_update_time
 .RS 4
 The date and time (in ISO 8601 format) when this daemon last received data from another component\&.
 .RE
 .PP
-stats\&.lname
+lname
 .RS 4
 This is the name used for the
 \fBb10\-msgq\fR
@@ -116,14 +113,19 @@ command\-control channel\&. (This is a constant which can\'t be reset except by
 \fBb10\-stats\fR\&.)
 .RE
 .PP
-stats\&.start_time
+report_time
+.RS 4
+The latest report date and time in ISO 8601 format\&.
+.RE
+.PP
+start_time
 .RS 4
 This is the date and time (in ISO 8601 format) when this daemon started collecting data\&.
 .RE
 .PP
-stats\&.timestamp
+timestamp
 .RS 4
-The current date and time represented in seconds since UNIX epoch (1970\-01\-01T0 0:00:00Z) with precision (delimited with a period) up to one hundred thousandth of second\&.
+The current date and time represented in seconds since UNIX epoch (1970\-01\-01T00:00:00Z) with precision (delimited with a period) up to one hundred thousandth of second\&.
 .RE
 .PP
 See other manual pages for explanations for their statistics that are kept track by

+ 15 - 14
src/bin/stats/b10-stats.xml

@@ -20,7 +20,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>February 28, 2012</date>
+    <date>March 1, 2012</date>
   </refentryinfo>
 
   <refmeta>
@@ -146,20 +146,15 @@
     <title>STATISTICS DATA</title>
 
     <para>
-      The <command>b10-stats</command> daemon contains these statistics:
+      The <command>b10-stats</command> daemon contains these
+      <quote>Stats</quote> statistics:
     </para>
 
     <variablelist>
 
-      <varlistentry>
-        <term>report_time</term>
-<!-- TODO: why not named stats.report_time? -->
-        <listitem><simpara>The latest report date and time in
-          ISO 8601 format.</simpara></listitem>
-      </varlistentry>
 
       <varlistentry>
-        <term>stats.boot_time</term>
+        <term>boot_time</term>
         <listitem><simpara>The date and time when this daemon was
           started in ISO 8601 format.
           This is a constant which can't be reset except by restarting
@@ -168,14 +163,14 @@
       </varlistentry>
 
       <varlistentry>
-        <term>stats.last_update_time</term>
+        <term>last_update_time</term>
         <listitem><simpara>The date and time (in ISO 8601 format)
           when this daemon last received data from another component.
         </simpara></listitem>
       </varlistentry>
 
       <varlistentry>
-        <term>stats.lname</term>
+        <term>lname</term>
         <listitem><simpara>This is the name used for the
           <command>b10-msgq</command> command-control channel.
           (This is a constant which can't be reset except by restarting
@@ -184,16 +179,22 @@
       </varlistentry>
 
       <varlistentry>
-        <term>stats.start_time</term>
+        <term>report_time</term>
+        <listitem><simpara>The latest report date and time in
+          ISO 8601 format.</simpara></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>start_time</term>
         <listitem><simpara>This is the date and time (in ISO 8601 format)
           when this daemon started collecting data.
         </simpara></listitem>
       </varlistentry>
 
       <varlistentry>
-        <term>stats.timestamp</term>
+        <term>timestamp</term>
         <listitem><simpara>The current date and time represented in
-          seconds since UNIX epoch (1970-01-01T0 0:00:00Z) with
+          seconds since UNIX epoch (1970-01-01T00:00:00Z) with
           precision (delimited with a period) up to
           one hundred thousandth of second.</simpara></listitem>
       </varlistentry>

+ 0 - 27
src/bin/xfrout/b10-xfrout.xml

@@ -115,33 +115,6 @@
       See the <citetitle>BIND 10 Guide</citetitle> for configuration examples.
       The default is an empty list, that is, no zone specific configuration.
     </para>
-    <para>
-      <varname>log_name</varname>
-<!-- TODO -->
-    </para>
-    <para>
-      <varname>log_file</varname>
-<!-- TODO -->
-      The location of the log file if using a file channel.
-      If undefined, then the file channel is closed.
-      The default is
-      <filename>/usr/local/var/bind10-devel/log/Xfrout.log</filename>.
-    </para>
-    <para>
-      <varname>log_severity</varname>
-<!-- TODO -->
-      The default is "debug".
-    </para>
-    <para>
-      <varname>log_versions</varname>
-<!-- TODO -->
-      The default is 5.
-    </para>
-    <para>
-      <varname>log_max_bytes</varname>
-<!-- TODO -->
-      The default is 1048576.
-    </para>
 
 <!-- TODO: log configurations not documented yet in here. jreed
      has some but waiting on decisions ... -->

+ 0 - 30
src/bin/xfrout/xfrout.spec.pre.in

@@ -9,36 +9,6 @@
          "item_default": 10
        },
        {
-         "item_name": "log_name",
-         "item_type": "string",
-         "item_optional": false,
-         "item_default": "Xfrout"
-       },
-       {
-         "item_name": "log_file",
-         "item_type": "string",
-         "item_optional": false,
-         "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/log/Xfrout.log"
-       },
-       {
-         "item_name": "log_severity",
-         "item_type": "string",
-         "item_optional": false,
-         "item_default": "debug"
-       },
-       {
-         "item_name": "log_versions",
-         "item_type": "integer",
-         "item_optional": false,
-         "item_default": 5
-       },
-       {
-         "item_name": "log_max_bytes",
-         "item_type": "integer",
-         "item_optional": false,
-         "item_default": 1048576
-       },
-       {
          "item_name": "transfer_acl",
          "item_type": "list",
          "item_optional": false,

+ 3 - 0
src/cppcheck-suppress.lst

@@ -10,6 +10,8 @@ duplicateExpression:src/lib/dns/tests/name_unittest.cc:569
 duplicateExpression:src/lib/dns/tests/name_unittest.cc:580
 duplicateExpression:src/lib/dns/tests/rrttl_unittest.cc:164
 duplicateExpression:src/lib/dns/tests/rrttl_unittest.cc:175
+duplicateExpression:src/lib/dns/tests/name_unittest.cc:568
+duplicateExpression:src/lib/dns/tests/name_unittest.cc:579
 
 // Intentional self-comparisons
 uselessCallsCompare:src/lib/dns/tests/rdata_dhcid_unittest.cc:96
@@ -17,3 +19,4 @@ uselessCallsCompare:src/lib/dns/tests/rdata_in_a_unittest.cc:98
 uselessCallsCompare:src/lib/dns/tests/rdata_in_aaaa_unittest.cc:94
 uselessCallsCompare:src/lib/dns/tests/rdata_mx_unittest.cc:104
 uselessCallsCompare:src/lib/dns/tests/rdata_unittest.cc:254
+uselessCallsCompare:src/lib/dns/tests/rdata_unittest.cc:253

+ 6 - 4
src/lib/bench/tests/benchmark_unittest.cc

@@ -70,9 +70,9 @@ TEST(BenchMarkTest, run) {
     const int sleep_time = 50000; // will sleep for 50ms
     const struct timespec sleep_timespec = { 0, sleep_time * 1000 };
     // we cannot expect particular accuracy on the measured duration, so
-    // we'll include some conservative margin (25%) and perform range
+    // we'll include some conservative margin (50%) and perform range
     // comparison below.
-    const int duration_margin = 12500; // 12.5ms
+    const int duration_margin = 25000; // 25ms
     const int ONE_MILLION = 1000000;
 
     // Prerequisite check: since the tests in this case may depend on subtle
@@ -80,6 +80,8 @@ TEST(BenchMarkTest, run) {
     // where sleeping doesn't work as this test expects.  So we check the
     // conditions before the tests, and if it fails skip the tests at the
     // risk of overlooking possible bugs.
+    // We do this with a tighter margin than the checks themselves
+    const int duration_soft_margin = 12500; // 12.5ms
     struct timeval check_begin, check_end;
     gettimeofday(&check_begin, NULL);
     nanosleep(&sleep_timespec, 0);
@@ -93,8 +95,8 @@ TEST(BenchMarkTest, run) {
         --check_end.tv_sec;
     }
     if (check_end.tv_sec != 0 ||
-        sleep_time - duration_margin > check_end.tv_usec ||
-        sleep_time + duration_margin < check_end.tv_usec) {
+        sleep_time - duration_soft_margin > check_end.tv_usec ||
+        sleep_time + duration_soft_margin < check_end.tv_usec) {
         cerr << "Prerequisite check failed.  skipping test" << endl;
         return;
     }

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

@@ -21,6 +21,7 @@ libdatasrc_la_SOURCES += static_datasrc.h static_datasrc.cc
 libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc
 libdatasrc_la_SOURCES += query.h query.cc
 libdatasrc_la_SOURCES += cache.h cache.cc
+libdatasrc_la_SOURCES += rbnode_rrset.h
 libdatasrc_la_SOURCES += rbtree.h
 libdatasrc_la_SOURCES += zonetable.h zonetable.cc
 libdatasrc_la_SOURCES += zone.h

+ 9 - 3
src/lib/datasrc/memory_datasrc.cc

@@ -34,6 +34,7 @@
 
 #include <datasrc/memory_datasrc.h>
 #include <datasrc/rbtree.h>
+#include <datasrc/rbnode_rrset.h>
 #include <datasrc/logger.h>
 #include <datasrc/iterator.h>
 #include <datasrc/data_source.h>
@@ -419,14 +420,19 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
      * access is without the impl_-> and it will get inlined anyway.
      */
     // Implementation of InMemoryZoneFinder::add
-    result::Result add(const ConstRRsetPtr& rrset, ZoneData& zone_data) {
+    result::Result add(const ConstRRsetPtr& rawrrset, ZoneData& zone_data) {
         // Sanitize input.  This will cause an exception to be thrown
         // if the input RRset is empty.
-        addValidation(rrset);
+        addValidation(rawrrset);
 
         // OK, can add the RRset.
         LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ADD_RRSET).
-            arg(rrset->getName()).arg(rrset->getType()).arg(origin_);
+            arg(rawrrset->getName()).arg(rawrrset->getType()).arg(origin_);
+
+        // ... although instead of loading the RRset directly, we encapsulate
+        // it within an RBNodeRRset.  This contains additional information that
+        // speeds up queries.
+        ConstRRsetPtr rrset(new internal::RBNodeRRset(rawrrset));
 
         if (rrset->getType() == RRType::NSEC3()) {
             return (addNSEC3(rrset, zone_data));

+ 209 - 0
src/lib/datasrc/rbnode_rrset.h

@@ -0,0 +1,209 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __RBNODE_RRSET_H
+#define __RBNODE_RRSET_H
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+#include <util/buffer.h>
+
+#include <string>
+
+namespace isc {
+namespace datasrc {
+namespace internal {
+
+/// \brief Special RRset for optimizing memory datasource requirement
+///
+/// To speed up the performance of the in-memory data source, at load time
+/// associate relevant "additional section" data with each RRset in the
+/// data source.
+///
+/// This class, derived from AbstractRRset, holds a "const" pointer to the
+/// underlying RRset object.  All calls to methods on the class are passed to
+/// the underlying object.  However, there are some restrictions:
+///
+/// - Calls to methods that change attributes of the underlying RRset (such as
+///   TTL or Name) cause an exception to be thrown.  The in-memory data source
+///   does not allow modification of these attributes.  In theory, it is a bad
+///   practice in that it doesn't preserve the assumed behavior of the base
+///   class.  In practice, however, it should be acceptable because this
+///   class is effectively hidden from applications and will only be given
+///   to them as a const pointer to the base class via find() variants.
+///   So the application cannot call non const methods anyway unless it
+///   intentionally breaks the constness.
+///
+/// - Calls that add the pointer to the associated RRSIG to the RRset are
+///   allowed (even though the pointer is to a "const" RRset).  The reason here
+///   is that RRSIGs are added to the in-memory data source after the
+///   RBNodeRRset objects have been created.  Thus there has to be the
+///   capability of modifying this information.
+///
+/// The class is not derived from RRset itself to simplify coding: part of the
+/// loading of the memory data source is handled in the BIND 10 "libdns++"
+/// code, which creates RRsets and passes them to the data source code.  This
+/// does not have to be altered if encapsulation, rather than inheritance, is
+/// used.
+///
+/// \note This class is exposed in this separate header file so that test code
+/// can refer to its definition, and only for that purpose.  Otherwise this is
+/// essentially a private class of the in-memory data source implementation,
+/// and an application shouldn't directly refer to this class.
+/// 
+// Note: non-Doxygen-documented methods are documented in the base class.
+
+class RBNodeRRset : public isc::dns::AbstractRRset {
+
+private:
+    // Note: The copy constructor and the assignment operator are intentionally
+    // defined as private as we would normally not duplicate a RBNodeRRset.
+    // (We use the "private" method instead of inheriting from
+    // boost::noncopyable so as to avoid multiple inheritance.)
+    RBNodeRRset(const RBNodeRRset& source);
+    RBNodeRRset& operator=(const RBNodeRRset& source);
+
+public:
+    /// \brief Usual Constructor
+    ///
+    /// Creates an RBNodeRRset from the pointer to the RRset passed to it.
+    ///
+    /// \param rrset Pointer to underlying RRset encapsulated by this object.
+    explicit RBNodeRRset(const isc::dns::ConstRRsetPtr& rrset) : rrset_(rrset)
+    {}
+
+    /// \brief Destructor
+    virtual ~RBNodeRRset() {}
+
+    // Getter and Setter Methods
+    //
+    // The getter methods pass the call through to the underlying RRset.  The
+    // setter methods thrown an exception - this specialisation of the RRset
+    // object does not expect the underlying RRset to be modified.
+
+    virtual unsigned int getRdataCount() const {
+        return (rrset_->getRdataCount());
+    }
+
+    virtual const isc::dns::Name& getName() const {
+        return (rrset_->getName());
+    }
+
+    virtual const isc::dns::RRClass& getClass() const {
+        return (rrset_->getClass());
+    }
+
+    virtual const isc::dns::RRType& getType() const {
+        return (rrset_->getType());
+    }
+
+    virtual const isc::dns::RRTTL& getTTL() const {
+        return (rrset_->getTTL());
+    }
+
+    virtual void setName(const isc::dns::Name&) {
+        isc_throw(isc::NotImplemented, "RBNodeRRset::setName() not supported");
+    }
+
+    virtual void setTTL(const isc::dns::RRTTL&) {
+        isc_throw(isc::NotImplemented, "RBNodeRRset::setTTL() not supported");
+    }
+
+    virtual std::string toText() const {
+        return (rrset_->toText());
+    }
+
+    virtual unsigned int toWire(
+            isc::dns::AbstractMessageRenderer& renderer) const {
+        return (rrset_->toWire(renderer));
+    }
+
+    virtual unsigned int toWire(isc::util::OutputBuffer& buffer) const {
+        return (rrset_->toWire(buffer));
+    }
+
+    virtual void addRdata(isc::dns::rdata::ConstRdataPtr) {
+        isc_throw(isc::NotImplemented,
+                  "RBNodeRRset::addRdata() not supported");
+    }
+
+    virtual void addRdata(const isc::dns::rdata::Rdata&) {
+        isc_throw(isc::NotImplemented,
+                  "RBNodeRRset::addRdata() not supported");
+    }
+
+    virtual isc::dns::RdataIteratorPtr getRdataIterator() const {
+        return (rrset_->getRdataIterator());
+    }
+
+    virtual isc::dns::RRsetPtr getRRsig() const {
+        return (rrset_->getRRsig());
+    }
+
+    // With all the RRsig methods, we have the problem that we store the
+    // underlying RRset using a ConstRRsetPtr - a pointer to a "const" RRset -
+    // but we need to modify it by adding or removing an RRSIG.  We overcome
+    // this by temporarily violating the "const" nature of the RRset to add the
+    // data.
+
+    virtual void addRRsig(const isc::dns::rdata::ConstRdataPtr& rdata) {
+        AbstractRRset* p = const_cast<AbstractRRset*>(rrset_.get());
+        p->addRRsig(rdata);
+    }
+
+    virtual void addRRsig(const isc::dns::rdata::RdataPtr& rdata) {
+        AbstractRRset* p = const_cast<AbstractRRset*>(rrset_.get());
+        p->addRRsig(rdata);
+    }
+
+    virtual void addRRsig(const AbstractRRset& sigs) {
+        AbstractRRset* p = const_cast<AbstractRRset*>(rrset_.get());
+        p->addRRsig(sigs);
+    }
+
+    virtual void addRRsig(const isc::dns::ConstRRsetPtr& sigs) {
+        AbstractRRset* p = const_cast<AbstractRRset*>(rrset_.get());
+        p->addRRsig(sigs);
+    }
+
+    virtual void addRRsig(const isc::dns::RRsetPtr& sigs) {
+        AbstractRRset* p = const_cast<AbstractRRset*>(rrset_.get());
+        p->addRRsig(sigs);
+    }
+
+    virtual void removeRRsig() {
+        AbstractRRset* p = const_cast<AbstractRRset*>(rrset_.get());
+        p->removeRRsig();
+    }
+
+    /// \brief Return underlying RRset pointer
+    ///
+    /// ... mainly for testing.
+    isc::dns::ConstRRsetPtr getUnderlyingRRset() const {
+        return (rrset_);
+    }
+
+private:
+    isc::dns::ConstRRsetPtr rrset_;     ///< Underlying RRset
+};
+
+}   // namespace internal
+}   // namespace datasrc
+}   // namespace isc
+
+#endif  // __RBNODE_RRSET_H

+ 7 - 4
src/lib/datasrc/tests/Makefile.am

@@ -79,6 +79,7 @@ run_unittests_sqlite3_LDADD = $(common_ldadd)
 # In-memory datasource tests
 run_unittests_memory_SOURCES = $(common_sources)
 run_unittests_memory_SOURCES += memory_datasrc_unittest.cc
+run_unittests_memory_SOURCES += rbnode_rrset_unittest.cc
 run_unittests_memory_SOURCES += $(top_srcdir)/src/lib/datasrc/memory_datasrc.cc
 
 run_unittests_memory_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
@@ -109,19 +110,21 @@ endif
 endif
 
 EXTRA_DIST =  testdata/brokendb.sqlite3
+EXTRA_DIST += testdata/diffs.sqlite3
+EXTRA_DIST += testdata/example2.com
+EXTRA_DIST += testdata/example2.com.sqlite3
 EXTRA_DIST += testdata/example.com.signed
 EXTRA_DIST += testdata/example.org
 EXTRA_DIST += testdata/example.org.nsec3-signed
 EXTRA_DIST += testdata/example.org.nsec3-signed-noparam
 EXTRA_DIST += testdata/example.org.sqlite3
-EXTRA_DIST += testdata/example2.com
-EXTRA_DIST += testdata/example2.com.sqlite3
 EXTRA_DIST += testdata/mkbrokendb.c
 EXTRA_DIST += testdata/root.zone
+EXTRA_DIST += testdata/rrset_toWire1
+EXTRA_DIST += testdata/rrset_toWire2
+EXTRA_DIST += testdata/rwtest.sqlite3
 EXTRA_DIST += testdata/sql1.example.com.signed
 EXTRA_DIST += testdata/sql2.example.com.signed
 EXTRA_DIST += testdata/test-root.sqlite3
 EXTRA_DIST += testdata/test.sqlite3
 EXTRA_DIST += testdata/test.sqlite3.nodiffs
-EXTRA_DIST += testdata/rwtest.sqlite3
-EXTRA_DIST += testdata/diffs.sqlite3

+ 16 - 7
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -172,14 +172,23 @@ TEST_F(InMemoryClientTest, iterator) {
     EXPECT_EQ(result::SUCCESS, zone->add(aRRsetA));
     EXPECT_EQ(result::SUCCESS, zone->add(aRRsetAAAA));
     EXPECT_EQ(result::SUCCESS, zone->add(subRRsetA));
-    // Check it with full zone, one by one.
-    // It should be in ascending order in case of InMemory data source
-    // (isn't guaranteed in general)
+
+    // Check it with full zone.
+    vector<ConstRRsetPtr> expected_rrsets;
+    expected_rrsets.push_back(aRRsetA);
+    expected_rrsets.push_back(aRRsetAAAA);
+    expected_rrsets.push_back(subRRsetA);
+
     iterator = memory_client.getIterator(Name("a"));
-    EXPECT_EQ(aRRsetA, iterator->getNextRRset());
-    EXPECT_EQ(aRRsetAAAA, iterator->getNextRRset());
-    EXPECT_EQ(subRRsetA, iterator->getNextRRset());
-    EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
+    vector<ConstRRsetPtr> actual_rrsets;
+    ConstRRsetPtr actual;
+    while ((actual = iterator->getNextRRset()) != NULL) {
+        actual_rrsets.push_back(actual);
+    }
+
+    rrsetsCheck(expected_rrsets.begin(), expected_rrsets.end(),
+                actual_rrsets.begin(), actual_rrsets.end());
+
 }
 
 TEST_F(InMemoryClientTest, iterator_separate_rrs) {

+ 258 - 0
src/lib/datasrc/tests/rbnode_rrset_unittest.cc

@@ -0,0 +1,258 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <stdexcept>
+
+#include <exceptions/exceptions.h>
+#include <dns/rdataclass.h>
+#include <datasrc/rbnode_rrset.h>
+#include <testutils/dnsmessage_test.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+
+using isc::UnitTestUtil;
+
+using namespace isc;
+using namespace isc::datasrc;
+using namespace isc::datasrc::internal;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::testutils;
+using namespace isc::util;
+using namespace std;
+
+// These tests are very similar to those for RRset - indeed, this file was
+// created from those tests.  However, the significant difference in behaviour
+// between RRset and RBNodeRRset - that the "set" methods in the latter mostly
+// result in exceptions being thrown - preclude use of full type
+// parameterisation of the tests.
+
+namespace {
+const char* const RRSIG_TXT =
+    "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+    "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+    "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+    "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+    "f49t+sXKPzbipN9g+s1ZPiIyofc=";
+
+class RBNodeRRsetTest : public ::testing::Test {
+protected:
+    RBNodeRRsetTest() :
+        test_name("test.example.com"),
+        test_domain("example.com"),
+        test_nsname("ns.example.com"),
+        rrset_a(ConstRRsetPtr(new RRset(
+                test_name, RRClass::IN(), RRType::A(), RRTTL(3600)))),
+        rrset_a_empty(ConstRRsetPtr(new RRset(
+                      test_name, RRClass::IN(), RRType::A(), RRTTL(3600)))),
+        rrset_ns(ConstRRsetPtr(new RRset(
+                 test_domain, RRClass::IN(), RRType::NS(), RRTTL(86400)))),
+        rrset_ch_txt(ConstRRsetPtr(new RRset(
+                     test_domain, RRClass::CH(), RRType::TXT(), RRTTL(0)))),
+        rrset_siga(new RRset(test_name, RRClass::IN(), RRType::RRSIG(),
+                   RRTTL(3600)))
+
+    {
+        // Add a couple of Rdata elements to the A RRset.  The easiest way to
+        // do this is to override the "const" restrictions.  As this is a test,
+        // we don't feel too bad about doing so.
+        AbstractRRset* a_rrset =
+            const_cast<AbstractRRset*>(rrset_a.getUnderlyingRRset().get());
+        a_rrset->addRdata(in::A("192.0.2.1"));
+        a_rrset->addRdata(in::A("192.0.2.2"));
+
+        // Create the RRSIG corresponding to the rrset_a record.  The RDATA
+        // won't match the A record it covers, although it is internally
+        // self-consistent.
+        AbstractRRset* sig_rrset =
+            const_cast<AbstractRRset*>(rrset_siga.get());
+        sig_rrset->addRdata(generic::RRSIG(RRSIG_TXT));
+    }
+
+    const Name test_name;
+    const Name test_domain;
+    const Name test_nsname;
+
+    RBNodeRRset rrset_a;
+    RBNodeRRset rrset_a_empty;
+    const RBNodeRRset rrset_ns;
+    const RBNodeRRset rrset_ch_txt;
+
+    ConstRRsetPtr rrset_siga;
+};
+
+TEST_F(RBNodeRRsetTest, getRdataCount) {
+    EXPECT_EQ(0, rrset_a_empty.getRdataCount());
+    EXPECT_EQ(2, rrset_a.getRdataCount());
+}
+
+TEST_F(RBNodeRRsetTest, getName) {
+    EXPECT_EQ(test_name, rrset_a.getName());
+    EXPECT_EQ(test_domain, rrset_ns.getName());
+}
+
+TEST_F(RBNodeRRsetTest, getClass) {
+    EXPECT_EQ(RRClass("IN"), rrset_a.getClass());
+    EXPECT_EQ(RRClass("CH"), rrset_ch_txt.getClass());
+}
+
+TEST_F(RBNodeRRsetTest, getType) {
+    EXPECT_EQ(RRType("A"), rrset_a.getType());
+    EXPECT_EQ(RRType("NS"), rrset_ns.getType());
+    EXPECT_EQ(RRType("TXT"), rrset_ch_txt.getType());
+}
+
+TEST_F(RBNodeRRsetTest, getTTL) {
+    EXPECT_EQ(RRTTL(3600), rrset_a.getTTL());
+    EXPECT_EQ(RRTTL(86400), rrset_ns.getTTL());
+    EXPECT_EQ(RRTTL(0), rrset_ch_txt.getTTL());
+}
+
+TEST_F(RBNodeRRsetTest, setName) {
+    EXPECT_THROW(rrset_a.setName(test_nsname), NotImplemented);
+}
+
+TEST_F(RBNodeRRsetTest, setTTL) {
+    EXPECT_THROW(rrset_a.setTTL(RRTTL(86400)), NotImplemented);
+}
+
+TEST_F(RBNodeRRsetTest, toText) {
+    EXPECT_EQ("test.example.com. 3600 IN A 192.0.2.1\n"
+              "test.example.com. 3600 IN A 192.0.2.2\n",
+              rrset_a.toText());
+
+    // toText() cannot be performed for an empty RRset.
+    EXPECT_THROW(rrset_a_empty.toText(), EmptyRRset);
+}
+
+// Note: although the next two tests are essentially the same and used common
+// test code, they use different test data: the MessageRenderer produces
+// compressed wire data whereas the OutputBuffer does not.
+
+template <typename T>
+void
+performToWireTest(T& dataHolder, const RBNodeRRset& rrset,
+                  const RBNodeRRset& rrset_empty, const char* testdata)
+{
+    rrset.toWire(dataHolder);
+
+    std::vector<unsigned char> wiredata;
+    UnitTestUtil::readWireData(testdata, wiredata);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, dataHolder.getData(),
+                        dataHolder.getLength(), &wiredata[0], wiredata.size());
+
+    // toWire() cannot be performed for an empty RRset.
+    dataHolder.clear();
+    EXPECT_THROW(rrset_empty.toWire(dataHolder), EmptyRRset);
+}
+
+TEST_F(RBNodeRRsetTest, toWireRenderer) {
+    MessageRenderer renderer;
+    performToWireTest(renderer, rrset_a, rrset_a_empty, "rrset_toWire2");
+}
+
+TEST_F(RBNodeRRsetTest, toWireBuffer) {
+    OutputBuffer buffer(0);
+    performToWireTest(buffer, rrset_a, rrset_a_empty, "rrset_toWire1");
+}
+
+TEST_F(RBNodeRRsetTest, addRdata) {
+    EXPECT_THROW(rrset_a.addRdata(in::A("192.0.2.3")), NotImplemented);
+
+    // Check the same goes for trying to add the wrong type of data
+    EXPECT_THROW(rrset_a.addRdata(generic::NS(test_nsname)), NotImplemented);
+}
+
+TEST_F(RBNodeRRsetTest, addRdataPtr) {
+    EXPECT_THROW(rrset_a_empty.addRdata(createRdata(rrset_a_empty.getType(),
+                                                    rrset_a_empty.getClass(),
+                                                    "192.0.2.1")),
+                 NotImplemented);
+}
+
+TEST_F(RBNodeRRsetTest, getRDataIterator) {
+    RdataIteratorPtr it = rrset_a.getRdataIterator();
+    for (int i = 0; i < 2; ++i) {
+        ASSERT_FALSE(it->isLast());
+        ASSERT_EQ(0, it->getCurrent().compare(in::A("192.0.2.1")));
+
+        it->next();
+        ASSERT_FALSE(it->isLast());
+        ASSERT_EQ(0, it->getCurrent().compare(in::A("192.0.2.2")));
+
+        it->next();
+        ASSERT_TRUE(it->isLast());
+
+        // Should be able repeat the iteration by calling first().
+        it->first();
+    }
+}
+
+// test operator<<.  We simply confirm it appends the result of toText().
+TEST_F(RBNodeRRsetTest, LeftShiftOperator) {
+    ostringstream oss;
+    oss << rrset_a;
+    EXPECT_EQ("test.example.com. 3600 IN A 192.0.2.1\n"
+              "test.example.com. 3600 IN A 192.0.2.2\n", oss.str());
+}
+
+// addRRSIG tests.
+TEST_F(RBNodeRRsetTest, addRRsigConstRdataPointer) {
+    EXPECT_FALSE(rrset_a.getRRsig());
+    ConstRdataPtr data = createRdata(rrset_siga->getType(),
+                                     rrset_siga->getClass(), RRSIG_TXT);
+    rrset_a.addRRsig(data);
+    rrsetCheck(rrset_siga, rrset_a.getRRsig());
+}
+
+TEST_F(RBNodeRRsetTest, addRRsigRdataPointer) {
+    EXPECT_FALSE(rrset_a.getRRsig());
+    RdataPtr data = createRdata(rrset_siga->getType(), rrset_siga->getClass(),
+                                RRSIG_TXT);
+    rrset_a.addRRsig(data);
+    rrsetCheck(rrset_siga, rrset_a.getRRsig());
+}
+
+TEST_F(RBNodeRRsetTest, addRRsigAbstractRRset) {
+    EXPECT_FALSE(rrset_a.getRRsig());
+    rrset_a.addRRsig(*(rrset_siga.get()));
+    rrsetCheck(rrset_siga, rrset_a.getRRsig());
+}
+
+TEST_F(RBNodeRRsetTest, addRRsigConstantRRsetPointer) {
+    EXPECT_FALSE(rrset_a.getRRsig());
+    rrset_a.addRRsig(rrset_siga);
+    rrsetCheck(rrset_siga, rrset_a.getRRsig());
+}
+
+TEST_F(RBNodeRRsetTest, addRRsigRRsetPointer) {
+    EXPECT_FALSE(rrset_a.getRRsig());
+    RRsetPtr rrsig(new RRset(test_name, RRClass::IN(), RRType::RRSIG(),
+                   RRTTL(3600)));
+    rrsig->addRdata(generic::RRSIG(RRSIG_TXT));
+    rrset_a.addRRsig(rrsig);
+    rrsetCheck(rrset_siga, rrset_a.getRRsig());
+}
+
+TEST_F(RBNodeRRsetTest, removeRRsig) {
+    EXPECT_FALSE(rrset_a.getRRsig());
+    rrset_a.addRRsig(*(rrset_siga.get()));
+    EXPECT_TRUE(rrset_a.getRRsig());
+    rrset_a.removeRRsig();
+    EXPECT_FALSE(rrset_a.getRRsig());
+}
+
+}   // Anonymous namespace

+ 23 - 0
src/lib/datasrc/tests/testdata/rrset_toWire1

@@ -0,0 +1,23 @@
+#
+# Rendering an IN/A RRset containing 2 RRs:
+# test.example.com. 3600 IN A 192.0.2.1
+# test.example.com. 3600 IN A 192.0.2.2
+#
+#(4) t  e  s  t (7) e  x  a  m  p  l  e (3) c  o  m  .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# type/class: A = 1, IN = 1
+00 01 00 01
+# TTL: 3600
+00 00 0e 10
+#6  7
+# RDLENGTH: 4
+00 04
+# RDATA: 192.0.2.1
+c0 00 02 01
+#
+# 2nd RR: mostly the same except the RDATA
+04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+00 01 00 01
+00 00 0e 10
+00 04
+c0 00 02 02

+ 26 - 0
src/lib/datasrc/tests/testdata/rrset_toWire2

@@ -0,0 +1,26 @@
+#
+# Rendering an IN/A RRset and NS RRset as follows:
+# test.example.com. 3600 IN A 192.0.2.1
+# test.example.com. 3600 IN A 192.0.2.2
+# example.com. 1D IN NS ns.example.com.
+# Names will be compressed when possible.
+#
+# 0  1  2  3  4  5
+#(4) t  e  s  t (7) e  x  a  m  p  l  e (3) c  o  m  .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# type/class: A = 1, IN = 1
+00 01 00 01
+# TTL: 3600
+00 00 0e 10
+#6  7
+# RDLENGTH: 4
+00 04
+# RDATA: 192.0.2.1
+c0 00 02 01
+#
+# 2nd RR: the owner name is compresed
+c0 00
+00 01 00 01
+00 00 0e 10
+00 04
+c0 00 02 02

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

@@ -91,6 +91,7 @@ libdns___la_LDFLAGS = -no-undefined -version-info 1:0:1
 libdns___la_SOURCES =
 libdns___la_SOURCES += edns.h edns.cc
 libdns___la_SOURCES += exceptions.h exceptions.cc
+libdns___la_SOURCES += labelsequence.h labelsequence.cc
 libdns___la_SOURCES += masterload.h masterload.cc
 libdns___la_SOURCES += message.h message.cc
 libdns___la_SOURCES += messagerenderer.h messagerenderer.cc
@@ -140,6 +141,7 @@ libdns___includedir = $(includedir)/dns
 libdns___include_HEADERS = \
 	edns.h \
 	exceptions.h \
+	labelsequence.h \
 	message.h \
 	messagerenderer.h \
 	name.h \

+ 77 - 0
src/lib/dns/labelsequence.cc

@@ -0,0 +1,77 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/labelsequence.h>
+#include <exceptions/exceptions.h>
+
+#include <iostream>
+namespace isc {
+namespace dns {
+
+const char*
+LabelSequence::getData(size_t *len) const {
+    // If the labelsequence is absolute, the current last_label_ falls
+    // out of the vector (since it points to the 'label' after the
+    // root label, which doesn't exist; in that case, return
+    // the length for the 'previous' label (the root label) plus
+    // one (for the root label zero octet)
+    if (isAbsolute()) {
+        *len = name_.offsets_[last_label_ - 1] - name_.offsets_[first_label_] + 1;
+    } else {
+        *len = name_.offsets_[last_label_] - name_.offsets_[first_label_];
+    }
+    return (&name_.ndata_[name_.offsets_[first_label_]]);
+}
+
+bool
+LabelSequence::equals(const LabelSequence& other, bool case_sensitive) const {
+    size_t len, other_len;
+    const char* data = getData(&len);
+    const char* other_data = other.getData(&other_len);
+
+    if (len != other_len) {
+        return (false);
+    }
+    if (case_sensitive) {
+        return (strncasecmp(data, other_data, len) == 0);
+    } else {
+        return (strncmp(data, other_data, len) == 0);
+    }
+}
+
+void
+LabelSequence::stripLeft(size_t i) {
+    if (i >= getLabelCount()) {
+        isc_throw(OutOfRange, "Cannot strip to zero or less labels; " << i <<
+                              " (labelcount: " << getLabelCount() << ")");
+    }
+    first_label_ += i;
+}
+
+void
+LabelSequence::stripRight(size_t i) {
+    if (i >= getLabelCount()) {
+        isc_throw(OutOfRange, "Cannot strip to zero or less labels; " << i <<
+                              " (labelcount: " << getLabelCount() << ")");
+    }
+    last_label_ -= i;
+}
+
+bool
+LabelSequence::isAbsolute() const {
+    return (last_label_ == name_.offsets_.size());
+}
+
+} // end namespace dns
+} // end namespace isc

+ 136 - 0
src/lib/dns/labelsequence.h

@@ -0,0 +1,136 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __LABELSEQUENCE_H
+#define __LABELSEQUENCE_H 1
+
+#include <dns/name.h>
+#include <util/buffer.h>
+
+namespace isc {
+namespace dns {
+
+/// \brief Light-weight Accessor to Name object
+///
+/// The purpose of this class is to easily match Names and parts of Names,
+/// without needing to copy the underlying data on each label strip.
+///
+/// It can only work on existing Name objects, and the Name object MUST
+/// remain in scope during the entire lifetime of its associated
+/// LabelSequence(s).
+///
+/// Upon creation of a LabelSequence, it records the offsets of the
+/// labels in the wireformat data of the Name. When stripLeft() or
+/// stripRight() is called on the LabelSequence, no changes in the
+/// Name's data occur, but the internal pointers of the
+/// LabelSequence are modified.
+///
+/// LabelSequences can be compared to other LabelSequences, and their
+/// data can be requested (which then points to part of the original
+/// data of the associated Name object).
+///
+class LabelSequence {
+public:
+    /// \brief Constructs a LabelSequence for the given name
+    ///
+    /// \note The associated Name MUST remain in scope during the lifetime
+    /// of this LabelSequence, since getData() refers to data from the
+    /// Name object (the only data the LabelSequence stores are pointers
+    /// to the labels in the Name object).
+    ///
+    /// \param name The Name to construct a LabelSequence for
+    LabelSequence(const Name& name): name_(name),
+                                     first_label_(0),
+                                     last_label_(name.getLabelCount())
+    {}
+
+    /// \brief Return the wire-format data for this LabelSequence
+    ///
+    /// The data, is returned as a pointer to the original wireformat
+    /// data of the original Name object, and the given len value is
+    /// set to the number of octets that match this labelsequence.
+    ///
+    /// \note The data pointed to is only valid if the original Name
+    /// object is still in scope
+    ///
+    /// \param len Pointer to a size_t where the length of the data
+    ///        will be stored (in number of octets)
+    /// \return Pointer to the wire-format data of this label sequence
+    const char* getData(size_t* len) const;
+
+    /// \brief Compares two label sequences.
+    ///
+    /// Performs a (optionally case-insensitive) comparison between this
+    /// LabelSequence and another LabelSequence.
+    ///
+    /// \param other The LabelSequence to compare with
+    /// \param case_sensitive If true, comparison is case-insensitive
+    /// \return true if The label sequences consist are the same length,
+    ///         and contain the same data.
+    bool equals(const LabelSequence& other, bool case_sensitive = false) const;
+
+    /// \brief Remove labels from the front of this LabelSequence
+    ///
+    /// \note No actual memory is changed, this operation merely updates the
+    /// internal pointers based on the offsets in the Name object.
+    ///
+    /// \exeption OutOfRange if i is greater than or equal to the number
+    ///           of labels currently pointed to by this LabelSequence
+    ///
+    /// \param i The number of labels to remove.
+    void stripLeft(size_t i);
+
+    /// \brief Remove labels from the end of this LabelSequence
+    ///
+    /// \note No actual memory is changed, this operation merely updates the
+    /// internal pointers based on the offsets in the Name object.
+    ///
+    /// \exeption OutOfRange if i is greater than or equal to the number
+    ///           of labels currently pointed to by this LabelSequence
+    ///
+    /// \param i The number of labels to remove.
+    void stripRight(size_t i);
+
+    /// \brief Returns the current number of labels for this LabelSequence
+    ///
+    /// \return The number of labels
+    size_t getLabelCount() const { return last_label_ - first_label_; }
+
+    /// \brief Returns the original Name object associated with this
+    ///        LabelSequence
+    ///
+    /// While the Name should still be in scope during the lifetime of
+    /// the LabelSequence, it can still be useful to have access to it,
+    /// for instance in helper functions that are only passed the
+    /// LabelSequence itself.
+    ///
+    /// \return Reference to the original Name object
+    const Name& getName() const { return name_; }
+
+    /// \brief Checks whether the label sequence is absolute
+    ///
+    /// \return true if the last label is the root label
+    bool isAbsolute() const;
+
+private:
+    const Name& name_;
+    size_t first_label_;
+    size_t last_label_;
+};
+
+
+} // end namespace dns
+} // end namespace isc
+
+#endif

+ 6 - 0
src/lib/dns/name.h

@@ -210,6 +210,11 @@ private:
 /// names as a special case.
 ///
 class Name {
+    // LabelSequences use knowledge about the internal data structure
+    // of this class for efficiency (they use the offsets_ vector and
+    // the ndata_ string)
+    friend class LabelSequence;
+
     ///
     /// \name Constructors and Destructor
     ///
@@ -298,6 +303,7 @@ public:
         }
         return (ndata_[pos]);
     }
+
     /// \brief Gets the length of the <code>Name</code> in its wire format.
     ///
     /// This method never throws an exception.

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

@@ -19,6 +19,7 @@ if HAVE_GTEST
 TESTS += run_unittests
 run_unittests_SOURCES = unittest_util.h unittest_util.cc
 run_unittests_SOURCES += edns_unittest.cc
+run_unittests_SOURCES += labelsequence_unittest.cc
 run_unittests_SOURCES += messagerenderer_unittest.cc
 run_unittests_SOURCES += name_unittest.cc
 run_unittests_SOURCES += nsec3hash_unittest.cc

+ 253 - 0
src/lib/dns/tests/labelsequence_unittest.cc

@@ -0,0 +1,253 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/labelsequence.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns;
+
+class LabelSequenceTest : public ::testing::Test {
+public:
+    LabelSequenceTest() : n1("example.org"), n2("example.com"),
+                          n3("example.org"), n4("foo.bar.test.example"),
+                          n5("example.ORG"), n6("ExAmPlE.org"),
+                          n7("."), n8("foo.example.org.bar"),
+                          ls1(n1), ls2(n2), ls3(n3), ls4(n4), ls5(n5),
+                          ls6(n6), ls7(n7), ls8(n8)
+    {};
+    // Need to keep names in scope for at least the lifetime of
+    // the labelsequences
+    Name n1, n2, n3, n4, n5, n6, n7, n8;
+
+    LabelSequence ls1, ls2, ls3, ls4, ls5, ls6, ls7, ls8;
+};
+
+// Basic equality tests
+TEST_F(LabelSequenceTest, equals_sensitive) {
+    EXPECT_TRUE(ls1.equals(ls1));
+    EXPECT_FALSE(ls1.equals(ls2));
+    EXPECT_TRUE(ls1.equals(ls3));
+    EXPECT_FALSE(ls1.equals(ls4));
+    EXPECT_FALSE(ls1.equals(ls5));
+    EXPECT_FALSE(ls1.equals(ls6));
+    EXPECT_FALSE(ls1.equals(ls7));
+    EXPECT_FALSE(ls1.equals(ls8));
+
+    EXPECT_FALSE(ls2.equals(ls1));
+    EXPECT_TRUE(ls2.equals(ls2));
+    EXPECT_FALSE(ls2.equals(ls3));
+    EXPECT_FALSE(ls2.equals(ls4));
+    EXPECT_FALSE(ls2.equals(ls5));
+    EXPECT_FALSE(ls2.equals(ls6));
+    EXPECT_FALSE(ls2.equals(ls7));
+    EXPECT_FALSE(ls2.equals(ls8));
+
+    EXPECT_FALSE(ls4.equals(ls1));
+    EXPECT_FALSE(ls4.equals(ls2));
+    EXPECT_FALSE(ls4.equals(ls3));
+    EXPECT_TRUE(ls4.equals(ls4));
+    EXPECT_FALSE(ls4.equals(ls5));
+    EXPECT_FALSE(ls4.equals(ls6));
+    EXPECT_FALSE(ls4.equals(ls7));
+    EXPECT_FALSE(ls4.equals(ls8));
+
+    EXPECT_FALSE(ls5.equals(ls1));
+    EXPECT_FALSE(ls5.equals(ls2));
+    EXPECT_FALSE(ls5.equals(ls3));
+    EXPECT_FALSE(ls5.equals(ls4));
+    EXPECT_TRUE(ls5.equals(ls5));
+    EXPECT_FALSE(ls5.equals(ls6));
+    EXPECT_FALSE(ls5.equals(ls7));
+    EXPECT_FALSE(ls5.equals(ls8));
+}
+
+TEST_F(LabelSequenceTest, equals_insensitive) {
+    EXPECT_TRUE(ls1.equals(ls1, true));
+    EXPECT_FALSE(ls1.equals(ls2, true));
+    EXPECT_TRUE(ls1.equals(ls3, true));
+    EXPECT_FALSE(ls1.equals(ls4, true));
+    EXPECT_TRUE(ls1.equals(ls5, true));
+    EXPECT_TRUE(ls1.equals(ls6, true));
+    EXPECT_FALSE(ls1.equals(ls7, true));
+
+    EXPECT_FALSE(ls2.equals(ls1, true));
+    EXPECT_TRUE(ls2.equals(ls2, true));
+    EXPECT_FALSE(ls2.equals(ls3, true));
+    EXPECT_FALSE(ls2.equals(ls4, true));
+    EXPECT_FALSE(ls2.equals(ls5, true));
+    EXPECT_FALSE(ls2.equals(ls6, true));
+    EXPECT_FALSE(ls2.equals(ls7, true));
+
+    EXPECT_TRUE(ls3.equals(ls1, true));
+    EXPECT_FALSE(ls3.equals(ls2, true));
+    EXPECT_TRUE(ls3.equals(ls3, true));
+    EXPECT_FALSE(ls3.equals(ls4, true));
+    EXPECT_TRUE(ls3.equals(ls5, true));
+    EXPECT_TRUE(ls3.equals(ls6, true));
+    EXPECT_FALSE(ls3.equals(ls7, true));
+
+    EXPECT_FALSE(ls4.equals(ls1, true));
+    EXPECT_FALSE(ls4.equals(ls2, true));
+    EXPECT_FALSE(ls4.equals(ls3, true));
+    EXPECT_TRUE(ls4.equals(ls4, true));
+    EXPECT_FALSE(ls4.equals(ls5, true));
+    EXPECT_FALSE(ls4.equals(ls6, true));
+    EXPECT_FALSE(ls4.equals(ls7, true));
+
+    EXPECT_TRUE(ls5.equals(ls1, true));
+    EXPECT_FALSE(ls5.equals(ls2, true));
+    EXPECT_TRUE(ls5.equals(ls3, true));
+    EXPECT_FALSE(ls5.equals(ls4, true));
+    EXPECT_TRUE(ls5.equals(ls5, true));
+    EXPECT_TRUE(ls5.equals(ls6, true));
+    EXPECT_FALSE(ls5.equals(ls7, true));
+}
+
+void
+getDataCheck(const char* expected_data, size_t expected_len,
+             const LabelSequence& ls)
+{
+    size_t len;
+    const char* data = ls.getData(&len);
+    ASSERT_EQ(expected_len, len) << "Expected data: " << expected_data <<
+                                    " name: " << ls.getName().toText();
+    for (size_t i = 0; i < len; ++i) {
+        EXPECT_EQ(expected_data[i], data[i]) << "Difference at pos " << i <<
+                                                ": Expected data: " <<
+                                                expected_data <<
+                                                " name: " <<
+                                                ls.getName().toText();;
+    }
+}
+
+TEST_F(LabelSequenceTest, getData) {
+    getDataCheck("\007example\003org\000", 13, ls1);
+    getDataCheck("\007example\003com\000", 13, ls2);
+    getDataCheck("\007example\003org\000", 13, ls3);
+    getDataCheck("\003foo\003bar\004test\007example\000", 22, ls4);
+    getDataCheck("\007example\003ORG\000", 13, ls5);
+    getDataCheck("\007ExAmPlE\003org\000", 13, ls6);
+    getDataCheck("\000", 1, ls7);
+};
+
+TEST_F(LabelSequenceTest, stripLeft) {
+    EXPECT_TRUE(ls1.equals(ls3));
+    ls1.stripLeft(0);
+    getDataCheck("\007example\003org\000", 13, ls1);
+    EXPECT_TRUE(ls1.equals(ls3));
+    ls1.stripLeft(1);
+    getDataCheck("\003org\000", 5, ls1);
+    EXPECT_FALSE(ls1.equals(ls3));
+    ls1.stripLeft(1);
+    getDataCheck("\000", 1, ls1);
+    EXPECT_TRUE(ls1.equals(ls7));
+
+    ls2.stripLeft(2);
+    getDataCheck("\000", 1, ls2);
+    EXPECT_TRUE(ls2.equals(ls7));
+}
+
+TEST_F(LabelSequenceTest, stripRight) {
+    EXPECT_TRUE(ls1.equals(ls3));
+    ls1.stripRight(1);
+    getDataCheck("\007example\003org", 12, ls1);
+    EXPECT_FALSE(ls1.equals(ls3));
+    ls1.stripRight(1);
+    getDataCheck("\007example", 8, ls1);
+    EXPECT_FALSE(ls1.equals(ls3));
+
+    ASSERT_FALSE(ls1.equals(ls2));
+    ls2.stripRight(2);
+    getDataCheck("\007example", 8, ls2);
+    EXPECT_TRUE(ls1.equals(ls2));
+}
+
+TEST_F(LabelSequenceTest, stripOutOfRange) {
+    EXPECT_THROW(ls1.stripLeft(100), isc::OutOfRange);
+    EXPECT_THROW(ls1.stripLeft(5), isc::OutOfRange);
+    EXPECT_THROW(ls1.stripLeft(4), isc::OutOfRange);
+    EXPECT_THROW(ls1.stripLeft(3), isc::OutOfRange);
+    getDataCheck("\007example\003org\000", 13, ls1);
+
+    EXPECT_THROW(ls1.stripRight(100), isc::OutOfRange);
+    EXPECT_THROW(ls1.stripRight(5), isc::OutOfRange);
+    EXPECT_THROW(ls1.stripRight(4), isc::OutOfRange);
+    EXPECT_THROW(ls1.stripRight(3), isc::OutOfRange);
+    getDataCheck("\007example\003org\000", 13, ls1);
+}
+
+TEST_F(LabelSequenceTest, getLabelCount) {
+    EXPECT_EQ(3, ls1.getLabelCount());
+    ls1.stripLeft(0);
+    EXPECT_EQ(3, ls1.getLabelCount());
+    ls1.stripLeft(1);
+    EXPECT_EQ(2, ls1.getLabelCount());
+    ls1.stripLeft(1);
+    EXPECT_EQ(1, ls1.getLabelCount());
+
+    EXPECT_EQ(3, ls2.getLabelCount());
+    ls2.stripRight(1);
+    EXPECT_EQ(2, ls2.getLabelCount());
+    ls2.stripRight(1);
+    EXPECT_EQ(1, ls2.getLabelCount());
+
+    EXPECT_EQ(3, ls3.getLabelCount());
+    ls3.stripRight(2);
+    EXPECT_EQ(1, ls3.getLabelCount());
+
+    EXPECT_EQ(5, ls4.getLabelCount());
+    ls4.stripRight(3);
+    EXPECT_EQ(2, ls4.getLabelCount());
+
+    EXPECT_EQ(3, ls5.getLabelCount());
+    ls5.stripLeft(2);
+    EXPECT_EQ(1, ls5.getLabelCount());
+}
+
+TEST_F(LabelSequenceTest, comparePart) {
+    EXPECT_FALSE(ls1.equals(ls8));
+
+    // strip root label from example.org.
+    ls1.stripRight(1);
+    // strip foo from foo.example.org.bar.
+    ls8.stripLeft(1);
+    // strip bar. (i.e. bar and root) too
+    ls8.stripRight(2);
+
+    EXPECT_TRUE(ls1.equals(ls8));
+
+    // Data comparison
+    size_t len;
+    const char* data = ls1.getData(&len);
+    getDataCheck(data, len, ls8);
+}
+
+TEST_F(LabelSequenceTest, isAbsolute) {
+    ASSERT_TRUE(ls1.isAbsolute());
+
+    ls1.stripLeft(1);
+    ASSERT_TRUE(ls1.isAbsolute());
+    ls1.stripRight(1);
+    ASSERT_FALSE(ls1.isAbsolute());
+
+    ASSERT_TRUE(ls2.isAbsolute());
+    ls2.stripRight(1);
+    ASSERT_FALSE(ls2.isAbsolute());
+
+    ASSERT_TRUE(ls3.isAbsolute());
+    ls3.stripLeft(2);
+    ASSERT_TRUE(ls3.isAbsolute());
+}

+ 18 - 0
src/lib/dns/tests/unittest_util.cc

@@ -191,3 +191,21 @@ UnitTestUtil::createRequestMessage(Message& message,
     message.addQuestion(Question(name, rrclass, rrtype));
 }
 
+void
+UnitTestUtil::createDNSSECRequestMessage(Message& message,
+                                         const Opcode& opcode,
+                                         const uint16_t qid,
+                                         const Name& name,
+                                         const RRClass& rrclass,
+                                         const RRType& rrtype)
+{
+    message.clear(Message::RENDER);
+    message.setOpcode(opcode);
+    message.setRcode(Rcode::NOERROR());
+    message.setQid(qid);
+    message.addQuestion(Question(name, rrclass, rrtype));
+    EDNSPtr edns(new EDNS());
+    edns->setUDPSize(4096);
+    edns->setDNSSECAwareness(true);
+    message.setEDNS(edns);
+}

+ 16 - 0
src/lib/dns/tests/unittest_util.h

@@ -93,6 +93,22 @@ public:
                          const isc::dns::Name& name,
                          const isc::dns::RRClass& rrclass,
                          const isc::dns::RRType& rrtype);
+
+    ///
+    /// Populate a DNSSEC request message
+    ///
+    /// Create a request message in 'request_message' using the
+    /// opcode 'opcode' and the name/class/type query tuple specified in
+    /// 'name', 'rrclass' and 'rrtype.
+    /// EDNS will be added with DO=1 and bufsize 4096
+    static void
+    createDNSSECRequestMessage(isc::dns::Message& request_message,
+                               const isc::dns::Opcode& opcode,
+                               const uint16_t qid,
+                               const isc::dns::Name& name,
+                               const isc::dns::RRClass& rrclass,
+                               const isc::dns::RRType& rrtype);
+
 };
 }
 #endif // __UNITTEST_UTIL_H

+ 0 - 3
tests/system/bindctl/nsx1/b10-config.db.template.in

@@ -3,8 +3,5 @@
    "listen_on": [{"address": "10.53.0.1", "port": 53210}],
    "database_file": "@abs_builddir@/zone.sqlite3",
    "statistics-interval": 1
- },
- "Xfrout": {
-   "log_file": "@abs_builddir@/Xfrout.log"
  }
 }

+ 0 - 3
tests/system/glue/nsx1/b10-config.db.in

@@ -2,8 +2,5 @@
  "Auth": {
    "listen_on": [{"address": "10.53.0.1", "port": 53210}],
    "database_file": "@abs_builddir@/zone.sqlite3"
- },
- "Xfrout": {
-   "log_file": "@abs_builddir@/Xfrout.log"
  }
 }