Browse Source

Merge branch 'master' into trac1130

Ocean Wang 13 years ago
parent
commit
01d8d0f132
48 changed files with 5944 additions and 1611 deletions
  1. 15 1
      ChangeLog
  2. 479 70
      doc/guide/bind10-guide.html
  3. 14 6
      doc/guide/bind10-guide.xml
  4. 931 204
      doc/guide/bind10-messages.html
  5. 2877 1200
      doc/guide/bind10-messages.xml
  6. 14 2
      src/bin/bind10/bind10.8
  7. 24 6
      src/bin/resolver/b10-resolver.8
  8. 4 2
      src/bin/resolver/b10-resolver.xml
  9. 83 14
      src/bin/stats/b10-stats.8
  10. 4 1
      src/bin/xfrin/b10-xfrin.8
  11. 1 1
      src/bin/xfrin/b10-xfrin.xml
  12. 1 1
      src/lib/datasrc/Makefile.am
  13. 37 0
      src/lib/datasrc/client.h
  14. 112 3
      src/lib/datasrc/database.cc
  15. 103 2
      src/lib/datasrc/database.h
  16. 20 2
      src/lib/datasrc/datasrc_messages.mes
  17. 61 0
      src/lib/datasrc/iterator.h
  18. 112 22
      src/lib/datasrc/memory_datasrc.cc
  19. 8 0
      src/lib/datasrc/memory_datasrc.h
  20. 86 22
      src/lib/datasrc/sqlite3_accessor.cc
  21. 9 1
      src/lib/datasrc/sqlite3_accessor.h
  22. 1 0
      src/lib/datasrc/static_datasrc.cc
  23. 1 0
      src/lib/datasrc/tests/Makefile.am
  24. 3 3
      src/lib/datasrc/tests/cache_unittest.cc
  25. 47 0
      src/lib/datasrc/tests/client_unittest.cc
  26. 249 17
      src/lib/datasrc/tests/database_unittest.cc
  27. 39 0
      src/lib/datasrc/tests/memory_datasrc_unittest.cc
  28. 78 29
      src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
  29. 1 0
      src/lib/datasrc/tests/static_unittest.cc
  30. 2 0
      src/lib/dns/Makefile.am
  31. 155 0
      src/lib/dns/rdata/generic/minfo_14.cc
  32. 82 0
      src/lib/dns/rdata/generic/minfo_14.h
  33. 2 2
      src/lib/dns/rdata/in_1/srv_33.h
  34. 1 0
      src/lib/dns/tests/Makefile.am
  35. 184 0
      src/lib/dns/tests/rdata_minfo_unittest.cc
  36. 12 0
      src/lib/dns/tests/testdata/Makefile.am
  37. 3 0
      src/lib/dns/tests/testdata/rdata_minfo_fromWire1.spec
  38. 7 0
      src/lib/dns/tests/testdata/rdata_minfo_fromWire2.spec
  39. 6 0
      src/lib/dns/tests/testdata/rdata_minfo_fromWire3.spec
  40. 6 0
      src/lib/dns/tests/testdata/rdata_minfo_fromWire4.spec
  41. 5 0
      src/lib/dns/tests/testdata/rdata_minfo_fromWire5.spec
  42. 5 0
      src/lib/dns/tests/testdata/rdata_minfo_fromWire6.spec
  43. 5 0
      src/lib/dns/tests/testdata/rdata_minfo_toWire1.spec
  44. 6 0
      src/lib/dns/tests/testdata/rdata_minfo_toWire2.spec
  45. 7 0
      src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed1.spec
  46. 8 0
      src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed2.spec
  47. 12 0
      src/lib/exceptions/exceptions.h
  48. 22 0
      src/lib/util/python/gen_wiredata.py.in

+ 15 - 1
ChangeLog

@@ -1,3 +1,17 @@
+bind10-devel-20110819 released on August 19, 2011
+
+281.	[func]		jelte
+	Added a new type for configuration data: "named set". This allows for
+	similar configuration as the current "list" type, but with strings
+	instead of indices as identifiers. The intended use is for instance
+	/foo/zones/example.org/bar instead of /foo/zones[2]/bar. Currently
+	this new type is not in use yet.
+	(Trac #926, git 06aeefc4787c82db7f5443651f099c5af47bd4d6)
+
+280.	[func]		jerry
+	libdns++: Implement the MINFO rrtype according to RFC1035.
+	(Trac #1113, git 7a9a19d6431df02d48a7bc9de44f08d9450d3a37)
+
 279.	[func]		jerry
 	libdns++: Implement the AFSDB rrtype according to RFC1183.
 	(Trac #1114, git ce052cd92cd128ea3db5a8f154bd151956c2920c)
@@ -33,7 +47,7 @@
 	returns is str or byte.
 	(Trac #1021, git 486bf91e0ecc5fbecfe637e1e75ebe373d42509b)
 
-273.    [func]		vorner
+273.	[func]		vorner
 	It is possible to specify ACL for the xfrout module. It is in the ACL
 	configuration key and has the usual ACL syntax. It currently supports
 	only the source address. Default ACL accepts everything.

File diff suppressed because it is too large
+ 479 - 70
doc/guide/bind10-guide.html


+ 14 - 6
doc/guide/bind10-guide.xml

@@ -1370,7 +1370,7 @@ what is XfroutClient xfr_client??
 
     <para>
       The main <command>bind10</command> process can be configured
-      to select to run either the authoritative or resolver.
+      to select to run either the authoritative or resolver or both.
       By default, it starts the authoritative service.
 <!-- TODO: later both -->
 
@@ -1390,22 +1390,28 @@ what is XfroutClient xfr_client??
     </para>
 
     <para>
-      The resolver also needs to be configured to listen on an address
-      and port:
+      By default, the resolver listens on port 53 for 127.0.0.1 and ::1.
+      The following example shows how it can be configured to
+      listen on an additional address (and port):
 
       <screen>
-&gt; <userinput>config set Resolver/listen_on [{ "address": "127.0.0.1", "port": 53 }]</userinput>
+&gt; <userinput>config add Resolver/listen_on</userinput>
+&gt; <userinput>config set Resolver/listen_on[<replaceable>2</replaceable>]/address "192.168.1.1"</userinput>
+&gt; <userinput>config set Resolver/listen_on[<replaceable>2</replaceable>]/port 53</userinput>
 &gt; <userinput>config commit</userinput>
 </screen>
     </para>
 
-<!-- TODO: later the above will have some defaults -->
+     <simpara>(Replace the <quote><replaceable>2</replaceable></quote>
+       as needed; run <quote><userinput>config show
+       Resolver/listen_on</userinput></quote> if needed.)</simpara>
+<!-- TODO: this example should not include the port, ticket #1185 -->
 
     <section>
       <title>Access Control</title>
 
       <para>
-        The <command>b10-resolver</command> daemon only accepts
+        By default, the <command>b10-resolver</command> daemon only accepts
         DNS queries from the localhost (127.0.0.1 and ::1).
         The <option>Resolver/query_acl</option> configuration may
 	be used to reject, drop, or allow specific IPs or networks.
@@ -1437,6 +1443,8 @@ url="bind10-messages.html#RESOLVER_QUERY_DROPPED">RESOLVER_QUERY_DROPPED</ulink>
 <!-- TODO:
 /0 is for any address in that address family
 does that need any address too?
+
+TODO: tsig
 -->
 
       <para>

File diff suppressed because it is too large
+ 931 - 204
doc/guide/bind10-messages.html


File diff suppressed because it is too large
+ 2877 - 1200
doc/guide/bind10-messages.xml


+ 14 - 2
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: March 31, 2011
+.\"      Date: August 11, 2011
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"
-.TH "BIND10" "8" "March 31, 2011" "BIND10" "BIND10"
+.TH "BIND10" "8" "August 11, 2011" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
@@ -107,6 +107,18 @@ Display more about what is going on for
 \fBbind10\fR
 and its child processes\&.
 .RE
+.SH "STATISTICS DATA"
+.PP
+The statistics data collected by the
+\fBb10\-stats\fR
+daemon include:
+.PP
+bind10\&.boot_time
+.RS 4
+The date and time that the
+\fBbind10\fR
+process started\&. This is represented in ISO 8601 format\&.
+.RE
 .SH "SEE ALSO"
 .PP
 

+ 24 - 6
src/bin/resolver/b10-resolver.8

@@ -2,12 +2,12 @@
 .\"     Title: b10-resolver
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: February 17, 2011
+.\"      Date: August 17, 2011
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"
-.TH "B10\-RESOLVER" "8" "February 17, 2011" "BIND10" "BIND10"
+.TH "B10\-RESOLVER" "8" "August 17, 2011" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
@@ -54,7 +54,7 @@ must be either a valid numeric user ID or a valid user name\&. By default the da
 .PP
 \fB\-v\fR
 .RS 4
-Enabled verbose mode\&. This enables diagnostic messages to STDERR\&.
+Enable verbose mode\&. This sets logging to the maximum debugging level\&.
 .RE
 .SH "CONFIGURATION AND COMMANDS"
 .PP
@@ -77,6 +77,25 @@ string and
 number\&. The defaults are address ::1 port 53 and address 127\&.0\&.0\&.1 port 53\&.
 .PP
 
+
+
+
+
+
+\fIquery_acl\fR
+is a list of query access control rules\&. The list items are the
+\fIaction\fR
+string and the
+\fIfrom\fR
+or
+\fIkey\fR
+strings\&. The possible actions are ACCEPT, REJECT and DROP\&. The
+\fIfrom\fR
+is a remote (source) IPv4 or IPv6 address or special keyword\&. The
+\fIkey\fR
+is a TSIG key name\&. The default configuration accepts queries from 127\&.0\&.0\&.1 and ::1\&.
+.PP
+
 \fIretries\fR
 is the number of times to retry (resend query) after a query timeout (\fItimeout_query\fR)\&. The default is 3\&.
 .PP
@@ -88,7 +107,7 @@ to use directly as root servers to start resolving\&. The list items are the
 \fIaddress\fR
 string and
 \fIport\fR
-number\&. If empty, a hardcoded address for F\-root (192\&.5\&.5\&.241) is used\&.
+number\&. By default, a hardcoded address for l\&.root\-servers\&.net (199\&.7\&.83\&.42 or 2001:500:3::42) is used\&.
 .PP
 
 \fItimeout_client\fR
@@ -121,8 +140,7 @@ BIND 10 Guide\&.
 .PP
 The
 \fBb10\-resolver\fR
-daemon was first coded in September 2010\&. The initial implementation only provided forwarding\&. Iteration was introduced in January 2011\&.
-
+daemon was first coded in September 2010\&. The initial implementation only provided forwarding\&. Iteration was introduced in January 2011\&. Caching was implemented in February 2011\&. Access control was introduced in June 2011\&.
 .SH "COPYRIGHT"
 .br
 Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")

+ 4 - 2
src/bin/resolver/b10-resolver.xml

@@ -20,7 +20,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>August 16, 2011</date>
+    <date>August 17, 2011</date>
   </refentryinfo>
 
   <refmeta>
@@ -178,8 +178,10 @@ once that is merged you can for instance do 'config add Resolver/forward_address
       root servers to start resolving.
       The list items are the <varname>address</varname> string
       and <varname>port</varname> number.
-      If empty, a hardcoded address for F-root (192.5.5.241) is used.
+      By default, a hardcoded address for l.root-servers.net
+      (199.7.83.42 or 2001:500:3::42) is used.
     </para>
+<!-- TODO: this is broken, see ticket #1184 -->
 
     <para>
       <varname>timeout_client</varname> is the number of milliseconds

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

@@ -1,22 +1,13 @@
 '\" t
 .\"     Title: b10-stats
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
-.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
-.\"      Date: Oct 15, 2010
+.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
+.\"      Date: August 11, 2011
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"
-.TH "B10\-STATS" "8" "Oct 15, 2010" "BIND10" "BIND10"
-.\" -----------------------------------------------------------------
-.\" * Define some portability stuff
-.\" -----------------------------------------------------------------
-.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.\" http://bugs.debian.org/507673
-.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
-.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.ie \n(.g .ds Aq \(aq
-.el       .ds Aq '
+.TH "B10\-STATS" "8" "August 11, 2011" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
@@ -47,7 +38,7 @@ and so on\&. It waits for coming data from other modules, then other modules sen
 \fBb10\-stats\fR
 invokes "sendstats" command for
 \fBbind10\fR
-after its initial starting because it\*(Aqs sure to collect statistics data from
+after its initial starting because it\'s sure to collect statistics data from
 \fBbind10\fR\&.
 .SH "OPTIONS"
 .PP
@@ -59,6 +50,84 @@ This
 \fBb10\-stats\fR
 switches to verbose mode\&. It sends verbose messages to STDOUT\&.
 .RE
+.SH "CONFIGURATION AND COMMANDS"
+.PP
+The
+\fBb10\-stats\fR
+command does not have any configurable settings\&.
+.PP
+The configuration commands are:
+.PP
+
+
+\fBremove\fR
+removes the named statistics name and data\&.
+.PP
+
+
+\fBreset\fR
+will reset all statistics data to default values except for constant names\&. This may re\-add previously removed statistics names\&.
+.PP
+
+\fBset\fR
+.PP
+
+\fBshow\fR
+will send the statistics data in JSON format\&. By default, it outputs all the statistics data it has collected\&. An optional item name may be specified to receive individual output\&.
+.PP
+
+\fBshutdown\fR
+will shutdown the
+\fBb10\-stats\fR
+process\&. (Note that the
+\fBbind10\fR
+parent may restart it\&.)
+.PP
+
+\fBstatus\fR
+simply indicates that the daemon is running\&.
+.SH "STATISTICS DATA"
+.PP
+The
+\fBb10\-stats\fR
+daemon contains these statistics:
+.PP
+report_time
+.RS 4
+The latest report date and time in ISO 8601 format\&.
+.RE
+.PP
+stats\&.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
+.RS 4
+The date and time (in ISO 8601 format) when this daemon last received data from another component\&.
+.RE
+.PP
+stats\&.lname
+.RS 4
+This is the name used for the
+\fBb10\-msgq\fR
+command\-control channel\&. (This is a constant which can\'t be reset except by restarting
+\fBb10\-stats\fR\&.)
+.RE
+.PP
+stats\&.start_time
+.RS 4
+This is the date and time (in ISO 8601 format) when this daemon started collecting data\&.
+.RE
+.PP
+stats\&.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\&.
+.RE
+.PP
+See other manual pages for explanations for their statistics that are kept track by
+\fBb10\-stats\fR\&.
 .SH "FILES"
 .PP
 /usr/local/share/bind10\-devel/stats\&.spec
@@ -82,7 +151,7 @@ BIND 10 Guide\&.
 .PP
 The
 \fBb10\-stats\fR
-daemon was initially designed and implemented by Naoki Kambe of JPRS in Oct 2010\&.
+daemon was initially designed and implemented by Naoki Kambe of JPRS in October 2010\&.
 .SH "COPYRIGHT"
 .br
 Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")

+ 4 - 1
src/bin/xfrin/b10-xfrin.8

@@ -71,6 +71,9 @@ is a list of zones known to the
 daemon\&. The list items are:
 \fIname\fR
 (the zone name),
+\fIclass\fR
+(defaults to
+\(lqIN\(rq),
 \fImaster_addr\fR
 (the zone master to transfer from),
 \fImaster_port\fR
@@ -125,7 +128,7 @@ to define the class (defaults to
 \fImaster\fR
 to define the IP address of the authoritative server to transfer from, and
 \fIport\fR
-to define the port number on the authoritative server (defaults to 53)\&. If the address or port is not specified, it will use the values previously defined in the
+to define the port number on the authoritative server (defaults to 53)\&. If the address or port is not specified, it will use the value previously defined in the
 \fIzones\fR
 configuration\&.
 .PP

+ 1 - 1
src/bin/xfrin/b10-xfrin.xml

@@ -169,7 +169,7 @@ in separate zonemgr process.
       and <varname>port</varname> to define the port number on the
       authoritative server (defaults to 53).
       If the address or port is not specified, it will use the
-      values previously defined in the <varname>zones</varname>
+      value previously defined in the <varname>zones</varname>
       configuration.
      </para>
 <!-- TODO: later hostname for master? -->

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

@@ -21,7 +21,7 @@ libdatasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
 libdatasrc_la_SOURCES += zone.h
 libdatasrc_la_SOURCES += result.h
 libdatasrc_la_SOURCES += logger.h logger.cc
-libdatasrc_la_SOURCES += client.h
+libdatasrc_la_SOURCES += client.h iterator.h
 libdatasrc_la_SOURCES += database.h database.cc
 libdatasrc_la_SOURCES += sqlite3_accessor.h sqlite3_accessor.cc
 nodist_libdatasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc

+ 37 - 0
src/lib/datasrc/client.h

@@ -16,12 +16,19 @@
 #define __DATA_SOURCE_CLIENT_H 1
 
 #include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
 
 #include <datasrc/zone.h>
 
 namespace isc {
 namespace datasrc {
 
+// The iterator.h is not included on purpose, most application won't need it
+class ZoneIterator;
+typedef boost::shared_ptr<ZoneIterator> ZoneIteratorPtr;
+
 /// \brief The base class of data source clients.
 ///
 /// This is an abstract base class that defines the common interface for
@@ -143,6 +150,36 @@ public:
     /// \param name A domain name for which the search is performed.
     /// \return A \c FindResult object enclosing the search result (see above).
     virtual FindResult findZone(const isc::dns::Name& name) const = 0;
+
+    /// \brief Returns an iterator to the given zone
+    ///
+    /// This allows for traversing the whole zone. The returned object can
+    /// provide the RRsets one by one.
+    ///
+    /// This throws DataSourceError when the zone does not exist in the
+    /// datasource.
+    ///
+    /// The default implementation throws isc::NotImplemented. This allows
+    /// for easy and fast deployment of minimal custom data sources, where
+    /// the user/implementator doesn't have to care about anything else but
+    /// the actual queries. Also, in some cases, it isn't possible to traverse
+    /// the zone from logic point of view (eg. dynamically generated zone
+    /// data).
+    ///
+    /// It is not fixed if a concrete implementation of this method can throw
+    /// anything else.
+    ///
+    /// \param name The name of zone apex to be traversed. It doesn't do
+    ///     nearest match as findZone.
+    /// \return Pointer to the iterator.
+    virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name) const {
+        // This is here to both document the parameter in doxygen (therefore it
+        // needs a name) and avoid unused parameter warning.
+        static_cast<void>(name);
+
+        isc_throw(isc::NotImplemented,
+                  "Data source doesn't support iteration");
+    }
 };
 }
 }

+ 112 - 3
src/lib/datasrc/database.cc

@@ -15,10 +15,12 @@
 #include <vector>
 
 #include <datasrc/database.h>
+#include <datasrc/data_source.h>
+#include <datasrc/iterator.h>
 
 #include <exceptions/exceptions.h>
 #include <dns/name.h>
-#include <dns/rrttl.h>
+#include <dns/rrclass.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 
@@ -27,7 +29,10 @@
 
 #include <boost/foreach.hpp>
 
-using isc::dns::Name;
+#include <string>
+
+using namespace isc::dns;
+using std::string;
 
 namespace isc {
 namespace datasrc {
@@ -109,7 +114,7 @@ void addOrCreate(isc::dns::RRsetPtr& rrset,
             if (ttl < rrset->getTTL()) {
                 rrset->setTTL(ttl);
             }
-            logger.info(DATASRC_DATABASE_FIND_TTL_MISMATCH)
+            logger.warn(DATASRC_DATABASE_FIND_TTL_MISMATCH)
                 .arg(db.getDBName()).arg(name).arg(cls)
                 .arg(type).arg(rrset->getTTL());
         }
@@ -401,5 +406,109 @@ DatabaseClient::Finder::getClass() const {
     return isc::dns::RRClass::IN();
 }
 
+namespace {
+
+/*
+ * This needs, beside of converting all data from textual representation, group
+ * together rdata of the same RRsets. To do this, we hold one row of data ahead
+ * of iteration. When we get a request to provide data, we create it from this
+ * data and load a new one. If it is to be put to the same rrset, we add it.
+ * Otherwise we just return what we have and keep the row as the one ahead
+ * for next time.
+ */
+class DatabaseIterator : public ZoneIterator {
+public:
+    DatabaseIterator(const DatabaseAccessor::IteratorContextPtr& context,
+             const RRClass& rrclass) :
+        context_(context),
+        class_(rrclass),
+        ready_(true)
+    {
+        // Prepare data for the next time
+        getData();
+    }
+
+    virtual isc::dns::ConstRRsetPtr getNextRRset() {
+        if (!ready_) {
+            isc_throw(isc::Unexpected, "Iterating past the zone end");
+        }
+        if (!data_ready_) {
+            // At the end of zone
+            ready_ = false;
+            LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+                      DATASRC_DATABASE_ITERATE_END);
+            return (ConstRRsetPtr());
+        }
+        string name_str(name_), rtype_str(rtype_), ttl(ttl_);
+        Name name(name_str);
+        RRType rtype(rtype_str);
+        RRsetPtr rrset(new RRset(name, class_, rtype, RRTTL(ttl)));
+        while (data_ready_ && name_ == name_str && rtype_str == rtype_) {
+            if (ttl_ != ttl) {
+                if (ttl < ttl_) {
+                    ttl_ = ttl;
+                    rrset->setTTL(RRTTL(ttl));
+                }
+                LOG_WARN(logger, DATASRC_DATABASE_ITERATE_TTL_MISMATCH).
+                    arg(name_).arg(class_).arg(rtype_).arg(rrset->getTTL());
+            }
+            rrset->addRdata(rdata::createRdata(rtype, class_, rdata_));
+            getData();
+        }
+        LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE_NEXT).
+            arg(rrset->getName()).arg(rrset->getType());
+        return (rrset);
+    }
+private:
+    // Load next row of data
+    void getData() {
+        string data[DatabaseAccessor::COLUMN_COUNT];
+        data_ready_ = context_->getNext(data, DatabaseAccessor::COLUMN_COUNT);
+        name_ = data[DatabaseAccessor::NAME_COLUMN];
+        rtype_ = data[DatabaseAccessor::TYPE_COLUMN];
+        ttl_ = data[DatabaseAccessor::TTL_COLUMN];
+        rdata_ = data[DatabaseAccessor::RDATA_COLUMN];
+    }
+
+    // The context
+    const DatabaseAccessor::IteratorContextPtr context_;
+    // Class of the zone
+    RRClass class_;
+    // Status
+    bool ready_, data_ready_;
+    // Data of the next row
+    string name_, rtype_, rdata_, ttl_;
+};
+
+}
+
+ZoneIteratorPtr
+DatabaseClient::getIterator(const isc::dns::Name& name) const {
+    // Get the zone
+    std::pair<bool, int> zone(database_->getZone(name));
+    if (!zone.first) {
+        // No such zone, can't continue
+        isc_throw(DataSourceError, "Zone " + name.toText() +
+                  " can not be iterated, because it doesn't exist "
+                  "in this data source");
+    }
+    // Request the context
+    DatabaseAccessor::IteratorContextPtr
+        context(database_->getAllRecords(name, zone.second));
+    // It must not return NULL, that's a bug of the implementation
+    if (context == DatabaseAccessor::IteratorContextPtr()) {
+        isc_throw(isc::Unexpected, "Iterator context null at " +
+                  name.toText());
+    }
+    // Create the iterator and return it
+    // TODO: Once #1062 is merged with this, we need to get the
+    // actual zone class from the connection, as the DatabaseClient
+    // doesn't know it and the iterator needs it (so it wouldn't query
+    // it each time)
+    LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE).
+        arg(name);
+    return (ZoneIteratorPtr(new DatabaseIterator(context, RRClass::IN())));
+}
+
 }
 }

+ 103 - 2
src/lib/datasrc/database.h

@@ -18,6 +18,7 @@
 #include <datasrc/client.h>
 
 #include <dns/name.h>
+#include <exceptions/exceptions.h>
 
 namespace isc {
 namespace datasrc {
@@ -76,6 +77,86 @@ public:
     virtual std::pair<bool, int> getZone(const isc::dns::Name& name) const = 0;
 
     /**
+     * \brief This holds the internal context of ZoneIterator for databases
+     *
+     * While the ZoneIterator implementation from DatabaseClient does all the
+     * translation from strings to DNS classes and validation, this class
+     * holds the pointer to where the database is at reading the data.
+     *
+     * It can either hold shared pointer to the connection which created it
+     * and have some kind of statement inside (in case single database
+     * connection can handle multiple concurrent SQL statements) or it can
+     * create a new connection (or, if it is more convenient, the connection
+     * itself can inherit both from DatabaseConnection and IteratorContext
+     * and just clone itself).
+     */
+    class IteratorContext : public boost::noncopyable {
+    public:
+        /**
+         * \brief Destructor
+         *
+         * Virtual destructor, so any descendand class is destroyed correctly.
+         */
+        virtual ~IteratorContext() { }
+
+        /**
+         * \brief Function to provide next resource record
+         *
+         * This function should provide data about the next resource record
+         * from the iterated zone. The data are not converted yet.
+         *
+         * \note The order of RRs is not strictly set, but the RRs for single
+         * RRset must not be interleaved with any other RRs (eg. RRsets must be
+         * "together").
+         *
+         * \param columns The data will be returned through here. The order
+         *     is specified by the RecordColumns enum.
+         * \param Size of the columns array. Must be equal to COLUMN_COUNT,
+         *     otherwise DataSourceError is thrown.
+         * \todo Do we consider databases where it is stored in binary blob
+         *     format?
+         * \throw DataSourceError if there's database-related error. If the
+         *     exception (or any other in case of derived class) is thrown,
+         *     the iterator can't be safely used any more.
+         */
+        virtual bool getNext(std::string columns[], size_t column_data) = 0;
+    };
+
+    typedef boost::shared_ptr<IteratorContext> IteratorContextPtr;
+
+    /**
+     * \brief Creates an iterator context for the whole zone.
+     *
+     * This should create a new iterator context to be used by
+     * DatabaseConnection's ZoneIterator. It can be created based on the name
+     * or the ID (returned from getZone()), what is more comfortable for the
+     * database implementation. Both are provided (and are guaranteed to match,
+     * the DatabaseClient first looks up the zone ID and then calls this).
+     *
+     * The default implementation throws isc::NotImplemented, to allow
+     * "minimal" implementations of the connection not supporting optional
+     * functionality.
+     *
+     * \param name The name of the zone.
+     * \param id The ID of the zone, returned from getZone().
+     * \return Newly created iterator context. Must not be NULL.
+     */
+    virtual IteratorContextPtr getAllRecords(const isc::dns::Name& name,
+                                             int id) const
+    {
+        /*
+         * This is a compromise. We need to document the parameters in doxygen,
+         * so they need a name, but then it complains about unused parameter.
+         * This is a NOP that "uses" the parameters.
+         */
+        static_cast<void>(name);
+        static_cast<void>(id);
+
+        isc_throw(isc::NotImplemented,
+                  "This database datasource can't be iterated");
+    }
+
+    /**
      * \brief Starts a new search for records of the given name in the given zone
      *
      * The data searched by this call can be retrieved with subsequent calls to
@@ -145,11 +226,12 @@ public:
         SIGTYPE_COLUMN = 2, ///< For RRSIG records, this contains the RRTYPE
                             ///< the RRSIG covers. In the current implementation,
                             ///< this field is ignored.
-        RDATA_COLUMN = 3    ///< Full text representation of the record's RDATA
+        RDATA_COLUMN = 3,   ///< Full text representation of the record's RDATA
+        NAME_COLUMN = 4     ///< The domain name of this RR
     };
 
     /// The number of fields the columns array passed to getNextRecord should have
-    static const size_t COLUMN_COUNT = 4;
+    static const size_t COLUMN_COUNT = 5;
 
     /**
      * \brief Returns a string identifying this dabase backend
@@ -356,6 +438,25 @@ public:
      */
     virtual FindResult findZone(const isc::dns::Name& name) const;
 
+    /**
+     * \brief Get the zone iterator
+     *
+     * The iterator allows going through the whole zone content. If the
+     * underlying DatabaseConnection is implemented correctly, it should
+     * be possible to have multiple ZoneIterators at once and query data
+     * at the same time.
+     *
+     * \exception DataSourceError if the zone doesn't exist.
+     * \exception isc::NotImplemented if the underlying DatabaseConnection
+     *     doesn't implement iteration. But in case it is not implemented
+     *     and the zone doesn't exist, DataSourceError is thrown.
+     * \exception Anything else the underlying DatabaseConnection might
+     *     want to throw.
+     * \param name The origin of the zone to iterate.
+     * \return Shared pointer to the iterator (it will never be NULL)
+     */
+    virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name) const;
+
 private:
     /// \brief Our database.
     const boost::shared_ptr<DatabaseAccessor> database_;

+ 20 - 2
src/lib/datasrc/datasrc_messages.mes

@@ -75,8 +75,9 @@ name and type in the database.
 
 % DATASRC_DATABASE_FIND_TTL_MISMATCH TTL values differ in %1 for elements of %2/%3/%4, setting to %5
 The datasource backend provided resource records for the given RRset with
-different TTL values. The TTL of the RRSET is set to the lowest value, which
-is printed in the log message.
+different TTL values. This isn't allowed on the wire and is considered
+an error, so we set it to the lowest value we found (but we don't modify the
+database). The data in database should be checked and fixed.
 
 % DATASRC_DATABASE_FIND_UNCAUGHT_ERROR uncaught general error retrieving data from datasource %1: %2
 There was an uncaught general exception while reading data from a datasource.
@@ -117,6 +118,23 @@ The data returned by the database backend contained data for the given domain
 name, and it either matches the type or has a relevant type. The RRset that is
 returned is printed.
 
+% DATASRC_DATABASE_ITERATE iterating zone %1
+The program is reading the whole zone, eg. not searching for data, but going
+through each of the RRsets there.
+
+% DATASRC_DATABASE_ITERATE_END iterating zone finished
+While iterating through the zone, the program reached end of the data.
+
+% DATASRC_DATABASE_ITERATE_NEXT next RRset in zone is %1/%2
+While iterating through the zone, the program extracted next RRset from it.
+The name and RRtype of the RRset is indicated in the message.
+
+% DATASRC_DATABASE_ITERATE_TTL_MISMATCH TTL values differ for RRs of %1/%2/%3, setting to %4
+While iterating through the zone, the time to live for RRs of the given RRset
+were found to be different. This isn't allowed on the wire and is considered
+an error, so we set it to the lowest value we found (but we don't modify the
+database). The data in database should be checked and fixed.
+
 % DATASRC_DO_QUERY handling query for '%1/%2'
 A debug message indicating that a query for the given name and RR type is being
 processed.

+ 61 - 0
src/lib/datasrc/iterator.h

@@ -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 <dns/rrset.h>
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace datasrc {
+
+/**
+ * \brief Read-only iterator to a zone.
+ *
+ * You can get an instance of (descendand of) ZoneIterator from
+ * DataSourceClient::getIterator() method. The actual concrete implementation
+ * will be different depending on the actual data source used. This is the
+ * abstract interface.
+ *
+ * There's no way to start iterating from the beginning again or return.
+ */
+class ZoneIterator : public boost::noncopyable {
+public:
+    /**
+     * \brief Destructor
+     *
+     * Virtual destructor. It is empty, but ensures the right destructor from
+     * descendant is called.
+     */
+    virtual ~ ZoneIterator() { }
+
+    /**
+     * \brief Get next RRset from the zone.
+     *
+     * This returns the next RRset in the zone as a shared pointer. The
+     * shared pointer is used to allow both accessing in-memory data and
+     * automatic memory management.
+     *
+     * Any special order is not guaranteed.
+     *
+     * While this can potentially throw anything (including standard allocation
+     * errors), it should be rare.
+     *
+     * \return Pointer to the next RRset or NULL pointer when the iteration
+     *     gets to the end of the zone.
+     */
+    virtual isc::dns::ConstRRsetPtr getNextRRset() = 0;
+};
+
+}
+}

+ 112 - 22
src/lib/datasrc/memory_datasrc.cc

@@ -25,6 +25,8 @@
 #include <datasrc/memory_datasrc.h>
 #include <datasrc/rbtree.h>
 #include <datasrc/logger.h>
+#include <datasrc/iterator.h>
+#include <datasrc/data_source.h>
 
 using namespace std;
 using namespace isc::dns;
@@ -32,6 +34,27 @@ using namespace isc::dns;
 namespace isc {
 namespace datasrc {
 
+namespace {
+// Some type aliases
+/*
+ * Each domain consists of some RRsets. They will be looked up by the
+ * RRType.
+ *
+ * The use of map is questionable with regard to performance - there'll
+ * be usually only few RRsets in the domain, so the log n benefit isn't
+ * much and a vector/array might be faster due to its simplicity and
+ * continuous memory location. But this is unlikely to be a performance
+ * critical place and map has better interface for the lookups, so we use
+ * that.
+ */
+typedef map<RRType, ConstRRsetPtr> Domain;
+typedef Domain::value_type DomainPair;
+typedef boost::shared_ptr<Domain> DomainPtr;
+// The tree stores domains
+typedef RBTree<Domain> DomainTree;
+typedef RBNode<Domain> DomainNode;
+}
+
 // Private data and hidden methods of InMemoryZoneFinder
 struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
     // Constructor
@@ -44,25 +67,6 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
         DomainPtr origin_domain(new Domain);
         origin_data_->setData(origin_domain);
     }
-
-    // Some type aliases
-    /*
-     * Each domain consists of some RRsets. They will be looked up by the
-     * RRType.
-     *
-     * The use of map is questionable with regard to performance - there'll
-     * be usually only few RRsets in the domain, so the log n benefit isn't
-     * much and a vector/array might be faster due to its simplicity and
-     * continuous memory location. But this is unlikely to be a performance
-     * critical place and map has better interface for the lookups, so we use
-     * that.
-     */
-    typedef map<RRType, ConstRRsetPtr> Domain;
-    typedef Domain::value_type DomainPair;
-    typedef boost::shared_ptr<Domain> DomainPtr;
-    // The tree stores domains
-    typedef RBTree<Domain> DomainTree;
-    typedef RBNode<Domain> DomainNode;
     static const DomainNode::Flags DOMAINFLAG_WILD = DomainNode::FLAG_USER1;
 
     // Information about the zone
@@ -634,7 +638,7 @@ InMemoryZoneFinder::load(const string& filename) {
     LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_LOAD).arg(getOrigin()).
         arg(filename);
     // Load it into a temporary tree
-    InMemoryZoneFinderImpl::DomainTree tmp;
+    DomainTree tmp;
     masterLoad(filename.c_str(), getOrigin(), getClass(),
         boost::bind(&InMemoryZoneFinderImpl::addFromLoad, impl_, _1, &tmp));
     // If it went well, put it inside
@@ -700,8 +704,94 @@ InMemoryClient::addZone(ZoneFinderPtr zone_finder) {
 InMemoryClient::FindResult
 InMemoryClient::findZone(const isc::dns::Name& name) const {
     LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_FIND_ZONE).arg(name);
-    return (FindResult(impl_->zone_table.findZone(name).code,
-                       impl_->zone_table.findZone(name).zone));
+    ZoneTable::FindResult result(impl_->zone_table.findZone(name));
+    return (FindResult(result.code, result.zone));
+}
+
+namespace {
+
+class MemoryIterator : public ZoneIterator {
+private:
+    RBTreeNodeChain<Domain> chain_;
+    Domain::const_iterator dom_iterator_;
+    const DomainTree& tree_;
+    const DomainNode* node_;
+    bool ready_;
+public:
+    MemoryIterator(const DomainTree& tree, const Name& origin) :
+        tree_(tree),
+        ready_(true)
+    {
+        // Find the first node (origin) and preserve the node chain for future
+        // searches
+        DomainTree::Result result(tree_.find<void*>(origin, &node_, chain_,
+                                                    NULL, NULL));
+        // It can't happen that the origin is not in there
+        if (result != DomainTree::EXACTMATCH) {
+            isc_throw(Unexpected,
+                      "In-memory zone corrupted, missing origin node");
+        }
+        // Initialize the iterator if there's somewhere to point to
+        if (node_ != NULL && node_->getData() != DomainPtr()) {
+            dom_iterator_ = node_->getData()->begin();
+        }
+    }
+
+    virtual ConstRRsetPtr getNextRRset() {
+        if (!ready_) {
+            isc_throw(Unexpected, "Iterating past the zone end");
+        }
+        /*
+         * This cycle finds the first nonempty node with yet unused RRset.
+         * If it is NULL, we run out of nodes. If it is empty, it doesn't
+         * contain any RRsets. If we are at the end, just get to next one.
+         */
+        while (node_ != NULL && (node_->getData() == DomainPtr() ||
+                                 dom_iterator_ == node_->getData()->end())) {
+            node_ = tree_.nextNode(chain_);
+            // If there's a node, initialize the iterator and check next time
+            // if the map is empty or not
+            if (node_ != NULL && node_->getData() != NULL) {
+                dom_iterator_ = node_->getData()->begin();
+            }
+        }
+        if (node_ == NULL) {
+            // That's all, folks
+            ready_ = false;
+            return (ConstRRsetPtr());
+        }
+        // The iterator points to the next yet unused RRset now
+        ConstRRsetPtr result(dom_iterator_->second);
+        // This one is used, move it to the next time for next call
+        ++dom_iterator_;
+
+        return (result);
+    }
+};
+
+} // End of anonymous namespace
+
+ZoneIteratorPtr
+InMemoryClient::getIterator(const Name& name) const {
+    ZoneTable::FindResult result(impl_->zone_table.findZone(name));
+    if (result.code != result::SUCCESS) {
+        isc_throw(DataSourceError, "No such zone: " + name.toText());
+    }
+
+    const InMemoryZoneFinder*
+        zone(dynamic_cast<const InMemoryZoneFinder*>(result.zone.get()));
+    if (zone == NULL) {
+        /*
+         * TODO: This can happen only during some of the tests and only as
+         * a temporary solution. This should be fixed by #1159 and then
+         * this cast and check shouldn't be necessary. We don't have
+         * test for handling a "can not happen" condition.
+         */
+        isc_throw(Unexpected, "The zone at " + name.toText() +
+                  " is not InMemoryZoneFinder");
+    }
+    return (ZoneIteratorPtr(new MemoryIterator(zone->impl_->domains_, name)));
 }
+
 } // end of namespace datasrc
 } // end of namespace dns

+ 8 - 0
src/lib/datasrc/memory_datasrc.h

@@ -182,6 +182,11 @@ private:
     struct InMemoryZoneFinderImpl;
     InMemoryZoneFinderImpl* impl_;
     //@}
+    // The friend here is for InMemoryClient::getIterator. The iterator
+    // needs to access the data inside the zone, so the InMemoryClient
+    // extracts the pointer to data and puts it into the iterator.
+    // The access is read only.
+    friend class InMemoryClient;
 };
 
 /// \brief A data source client that holds all necessary data in memory.
@@ -258,6 +263,9 @@ public:
     /// For other details see \c DataSourceClient::findZone().
     virtual FindResult findZone(const isc::dns::Name& name) const;
 
+    /// \brief Implementation of the getIterator method
+    virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name) const;
+
 private:
     // TODO: Do we still need the PImpl if nobody should manipulate this class
     // directly any more (it should be handled through DataSourceClient)?

+ 86 - 22
src/lib/datasrc/sqlite3_accessor.cc

@@ -19,6 +19,8 @@
 #include <datasrc/data_source.h>
 #include <util/filename.h>
 
+#include <boost/lexical_cast.hpp>
+
 namespace isc {
 namespace datasrc {
 
@@ -136,9 +138,13 @@ const char* const SCHEMA_LIST[] = {
 
 const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1 AND rdclass = ?2";
 
-const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata "
+const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata, name "
     "FROM records WHERE zone_id=?1 AND name=?2";
 
+const char* const q_iterate_str = "SELECT rdtype, ttl, sigtype, rdata, name FROM records "
+                                  "WHERE zone_id = ?1 "
+                                  "ORDER BY name, rdtype";
+
 /* TODO: Prune the statements, not everything will be needed maybe?
 const char* const q_record_str = "SELECT rdtype, ttl, sigtype, rdata "
     "FROM records WHERE zone_id=?1 AND name=?2 AND "
@@ -313,30 +319,18 @@ SQLite3Database::getZone(const isc::dns::Name& name) const {
         result = std::pair<bool, int>(true,
                                       sqlite3_column_int(dbparameters_->
                                                          q_zone_, 0));
-    } else {
+        return (result);
+    } else if (rc == SQLITE_DONE) {
         result = std::pair<bool, int>(false, 0);
+        // Free resources
+        sqlite3_reset(dbparameters_->q_zone_);
+        return (result);
     }
-    // Free resources
-    sqlite3_reset(dbparameters_->q_zone_);
-
-    return (result);
-}
 
-void
-SQLite3Database::searchForRecords(int zone_id, const std::string& name) {
-    resetSearch();
-    if (sqlite3_bind_int(dbparameters_->q_any_, 1, zone_id) != SQLITE_OK) {
-        isc_throw(DataSourceError,
-                  "Error in sqlite3_bind_int() for zone_id " <<
-                  zone_id << ": " << sqlite3_errmsg(dbparameters_->db_));
-    }
-    // use transient since name is a ref and may disappear
-    if (sqlite3_bind_text(dbparameters_->q_any_, 2, name.c_str(), -1,
-                               SQLITE_TRANSIENT) != SQLITE_OK) {
-        isc_throw(DataSourceError,
-                  "Error in sqlite3_bind_text() for name " <<
-                  name << ": " << sqlite3_errmsg(dbparameters_->db_));
-    }
+    isc_throw(DataSourceError, "Unexpected failure in sqlite3_step: " <<
+                               sqlite3_errmsg(dbparameters_->db_));
+    // Compilers might not realize isc_throw always throws
+    return (std::pair<bool, int>(false, 0));
 }
 
 namespace {
@@ -368,6 +362,76 @@ convertToPlainChar(const unsigned char* ucp,
 }
 }
 
+// TODO: Once we want to have iterator returned from searchForRecords, this
+// class can be reused. It should be modified to take the sqlite3 statement
+// instead of creating it in constructor, it doesn't have to care which one
+// it is, just provide data from it.
+class SQLite3Database::Context : public DatabaseAccessor::IteratorContext {
+public:
+    Context(const boost::shared_ptr<const SQLite3Database>& database, int id) :
+        database_(database),
+        statement(NULL)
+    {
+        // We create the statement now and then just keep getting data from it
+        statement = prepare(database->dbparameters_->db_, q_iterate_str);
+        if (sqlite3_bind_int(statement, 1, id) != SQLITE_OK) {
+            isc_throw(SQLite3Error, "Could not bind " << id <<
+                      " to SQL statement (iterate)");
+        }
+    }
+    bool getNext(std::string data[], size_t size) {
+        if (size != COLUMN_COUNT) {
+            isc_throw(DataSourceError, "getNext received size of " << size <<
+                      ", not " << COLUMN_COUNT);
+        }
+        // If there's another row, get it
+        int rc(sqlite3_step(statement));
+        if (rc == SQLITE_ROW) {
+            for (size_t i(0); i < size; ++ i) {
+                data[i] = convertToPlainChar(sqlite3_column_text(statement, i),
+                                             database_->dbparameters_);
+            }
+            return (true);
+        } else if (rc != SQLITE_DONE) {
+            isc_throw(DataSourceError,
+                      "Unexpected failure in sqlite3_step: " <<
+                      sqlite3_errmsg(database_->dbparameters_->db_));
+        }
+        return (false);
+    }
+    virtual ~Context() {
+        if (statement) {
+            sqlite3_finalize(statement);
+        }
+    }
+
+private:
+    boost::shared_ptr<const SQLite3Database> database_;
+    sqlite3_stmt *statement;
+};
+
+DatabaseAccessor::IteratorContextPtr
+SQLite3Database::getAllRecords(const isc::dns::Name&, int id) const {
+    return (IteratorContextPtr(new Context(shared_from_this(), id)));
+}
+
+void
+SQLite3Database::searchForRecords(int zone_id, const std::string& name) {
+    resetSearch();
+    if (sqlite3_bind_int(dbparameters_->q_any_, 1, zone_id) != SQLITE_OK) {
+        isc_throw(DataSourceError,
+                  "Error in sqlite3_bind_int() for zone_id " <<
+                  zone_id << ": " << sqlite3_errmsg(dbparameters_->db_));
+    }
+    // use transient since name is a ref and may disappear
+    if (sqlite3_bind_text(dbparameters_->q_any_, 2, name.c_str(), -1,
+                               SQLITE_TRANSIENT) != SQLITE_OK) {
+        isc_throw(DataSourceError,
+                  "Error in sqlite3_bind_text() for name " <<
+                  name << ": " << sqlite3_errmsg(dbparameters_->db_));
+    }
+}
+
 bool
 SQLite3Database::getNextRecord(std::string columns[], size_t column_count) {
     if (column_count != COLUMN_COUNT) {

+ 9 - 1
src/lib/datasrc/sqlite3_accessor.h

@@ -20,6 +20,7 @@
 
 #include <exceptions/exceptions.h>
 
+#include <boost/enable_shared_from_this.hpp>
 #include <string>
 
 namespace isc {
@@ -51,7 +52,8 @@ struct SQLite3Parameters;
  * According to the design, it doesn't interpret the data in any way, it just
  * provides unified access to the DB.
  */
-class SQLite3Database : public DatabaseAccessor {
+class SQLite3Database : public DatabaseAccessor,
+    public boost::enable_shared_from_this<SQLite3Database> {
 public:
     /**
      * \brief Constructor
@@ -89,6 +91,9 @@ public:
      */
     virtual std::pair<bool, int> getZone(const isc::dns::Name& name) const;
 
+    /// \brief Implementation of DatabaseAbstraction::getAllRecords
+    virtual IteratorContextPtr getAllRecords(const isc::dns::Name&,
+                                                  int id) const;
     /**
      * \brief Start a new search for the given name in the given zone.
      *
@@ -151,6 +156,9 @@ private:
     void open(const std::string& filename);
     /// \brief Closes the database
     void close();
+    /// \brief SQLite3 implementation of IteratorContext
+    class Context;
+    friend class Context;
     const std::string database_name_;
 };
 

+ 1 - 0
src/lib/datasrc/static_datasrc.cc

@@ -70,6 +70,7 @@ StaticDataSrcImpl::StaticDataSrcImpl() :
     authors = RRsetPtr(new RRset(authors_name, RRClass::CH(),
                                  RRType::TXT(), RRTTL(0)));
     authors->addRdata(generic::TXT("Chen Zhengzhang")); // Jerry
+    authors->addRdata(generic::TXT("Dmitriy Volodin"));
     authors->addRdata(generic::TXT("Evan Hunt"));
     authors->addRdata(generic::TXT("Haidong Wang")); // Ocean
     authors->addRdata(generic::TXT("Han Feng"));

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

@@ -29,6 +29,7 @@ run_unittests_SOURCES += zonetable_unittest.cc
 run_unittests_SOURCES += memory_datasrc_unittest.cc
 run_unittests_SOURCES += logger_unittest.cc
 run_unittests_SOURCES += database_unittest.cc
+run_unittests_SOURCES += client_unittest.cc
 run_unittests_SOURCES += sqlite3_accessor_unittest.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

+ 3 - 3
src/lib/datasrc/tests/cache_unittest.cc

@@ -202,15 +202,15 @@ TEST_F(CacheTest, retrieveFail) {
 }
 
 TEST_F(CacheTest, expire) {
-    // Insert "foo" with a duration of 2 seconds; sleep 3.  The
+    // Insert "foo" with a duration of 1 seconds; sleep 2.  The
     // record should not be returned from the cache even though it's
     // at the top of the cache.
     RRsetPtr aaaa(new RRset(Name("foo"), RRClass::IN(), RRType::AAAA(),
                             RRTTL(0)));
     aaaa->addRdata(in::AAAA("2001:db8:3:bb::5"));
-    cache.addPositive(aaaa, 0, 2);
+    cache.addPositive(aaaa, 0, 1);
 
-    sleep(3);
+    sleep(2);
 
     RRsetPtr r;
     uint32_t f;

+ 47 - 0
src/lib/datasrc/tests/client_unittest.cc

@@ -0,0 +1,47 @@
+// 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 <datasrc/client.h>
+
+#include <dns/name.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::datasrc;
+using isc::dns::Name;
+
+namespace {
+
+/*
+ * The DataSourceClient can't be created as it has pure virtual methods.
+ * So we implement them as NOPs and test the other methods.
+ */
+class NopClient : public DataSourceClient {
+public:
+    virtual FindResult findZone(const isc::dns::Name&) const {
+        return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+    }
+};
+
+class ClientTest : public ::testing::Test {
+public:
+    NopClient client_;
+};
+
+// The default implementation is NotImplemented
+TEST_F(ClientTest, defaultIterator) {
+    EXPECT_THROW(client_.getIterator(Name(".")), isc::NotImplemented);
+}
+
+}

+ 249 - 17
src/lib/datasrc/tests/database_unittest.cc

@@ -22,6 +22,7 @@
 #include <datasrc/database.h>
 #include <datasrc/zone.h>
 #include <datasrc/data_source.h>
+#include <datasrc/iterator.h>
 
 #include <testutils/dnsmessage_test.h>
 
@@ -30,30 +31,169 @@
 using namespace isc::datasrc;
 using namespace std;
 using namespace boost;
-using isc::dns::Name;
+using namespace isc::dns;
 
 namespace {
 
 /*
- * A virtual database database that pretends it contains single zone --
- * example.org.
+ * An accessor with minimum implementation, keeping the original
+ * "NotImplemented" methods.
  */
-class MockAccessor : public DatabaseAccessor {
+class NopAccessor : public DatabaseAccessor {
 public:
-    MockAccessor() : search_running_(false),
-                       database_name_("mock_database")
-    {
-        fillData();
-    }
+    NopAccessor() : database_name_("mock_database")
+    { }
 
     virtual std::pair<bool, int> getZone(const Name& name) const {
         if (name == Name("example.org")) {
             return (std::pair<bool, int>(true, 42));
+        } else if (name == Name("null.example.org")) {
+            return (std::pair<bool, int>(true, 13));
+        } else if (name == Name("empty.example.org")) {
+            return (std::pair<bool, int>(true, 0));
+        } else if (name == Name("bad.example.org")) {
+            return (std::pair<bool, int>(true, -1));
         } else {
             return (std::pair<bool, int>(false, 0));
         }
     }
 
+    virtual const std::string& getDBName() const {
+        return (database_name_);
+    }
+
+    // These are just to compile, they won't be called
+    virtual void searchForRecords(int, const std::string&) { }
+    virtual bool getNextRecord(string*, size_t) {
+        return (false);
+    }
+    virtual void resetSearch() { }
+private:
+    const std::string database_name_;
+
+};
+
+/*
+ * A virtual database connection that pretends it contains single zone --
+ * example.org.
+ *
+ * It has the same getZone method as NopConnection, but it provides
+ * implementation of the optional functionality.
+ */
+class MockAccessor : public NopAccessor {
+public:
+    MockAccessor() : cur_record(0), search_running_(false)
+    {
+        fillData();
+    }
+private:
+    class MockIteratorContext : public IteratorContext {
+    private:
+        int step;
+    public:
+        MockIteratorContext() :
+            step(0)
+        { }
+        virtual bool getNext(string data[], size_t size) {
+            if (size != DatabaseAccessor::COLUMN_COUNT) {
+                isc_throw(DataSourceError, "Wrong column count in getNextRecord");
+            }
+            switch (step ++) {
+                case 0:
+                    data[DatabaseAccessor::NAME_COLUMN] = "example.org";
+                    data[DatabaseAccessor::TYPE_COLUMN] = "SOA";
+                    data[DatabaseAccessor::TTL_COLUMN] = "300";
+                    data[DatabaseAccessor::RDATA_COLUMN] = "ns1.example.org. admin.example.org. "
+                        "1234 3600 1800 2419200 7200";
+                    return (true);
+                case 1:
+                    data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
+                    data[DatabaseAccessor::TYPE_COLUMN] = "A";
+                    data[DatabaseAccessor::TTL_COLUMN] = "300";
+                    data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.1";
+                    return (true);
+                case 2:
+                    data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
+                    data[DatabaseAccessor::TYPE_COLUMN] = "A";
+                    data[DatabaseAccessor::TTL_COLUMN] = "300";
+                    data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.2";
+                    return (true);
+                case 3:
+                    data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
+                    data[DatabaseAccessor::TYPE_COLUMN] = "AAAA";
+                    data[DatabaseAccessor::TTL_COLUMN] = "300";
+                    data[DatabaseAccessor::RDATA_COLUMN] = "2001:db8::1";
+                    return (true);
+                case 4:
+                    data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
+                    data[DatabaseAccessor::TYPE_COLUMN] = "AAAA";
+                    data[DatabaseAccessor::TTL_COLUMN] = "300";
+                    data[DatabaseAccessor::RDATA_COLUMN] = "2001:db8::2";
+                    return (true);
+                default:
+                    ADD_FAILURE() <<
+                        "Request past the end of iterator context";
+                case 5:
+                    return (false);
+            }
+        }
+    };
+    class EmptyIteratorContext : public IteratorContext {
+    public:
+        virtual bool getNext(string[], size_t size) {
+            if (size != DatabaseAccessor::COLUMN_COUNT) {
+                isc_throw(DataSourceError, "Wrong column count in getNextRecord");
+            }
+            return (false);
+        }
+    };
+    class BadIteratorContext : public IteratorContext {
+    private:
+        int step;
+    public:
+        BadIteratorContext() :
+            step(0)
+        { }
+        virtual bool getNext(string data[], size_t size) {
+            if (size != DatabaseAccessor::COLUMN_COUNT) {
+                isc_throw(DataSourceError, "Wrong column count in getNextRecord");
+            }
+            switch (step ++) {
+                case 0:
+                    data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
+                    data[DatabaseAccessor::TYPE_COLUMN] = "A";
+                    data[DatabaseAccessor::TTL_COLUMN] = "300";
+                    data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.1";
+                    return (true);
+                case 1:
+                    data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
+                    data[DatabaseAccessor::TYPE_COLUMN] = "A";
+                    data[DatabaseAccessor::TTL_COLUMN] = "301";
+                    data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.2";
+                    return (true);
+                default:
+                    ADD_FAILURE() <<
+                        "Request past the end of iterator context";
+                case 2:
+                    return (false);
+            }
+        }
+    };
+public:
+    virtual IteratorContextPtr getAllRecords(const Name&, int id) const {
+        if (id == 42) {
+            return (IteratorContextPtr(new MockIteratorContext()));
+        } else if (id == 13) {
+            return (IteratorContextPtr());
+        } else if (id == 0) {
+            return (IteratorContextPtr(new EmptyIteratorContext()));
+        } else if (id == -1) {
+            return (IteratorContextPtr(new BadIteratorContext()));
+        } else {
+            isc_throw(isc::Unexpected, "Unknown zone ID");
+        }
+    }
+
     virtual void searchForRecords(int zone_id, const std::string& name) {
         search_running_ = true;
 
@@ -114,10 +254,6 @@ public:
     bool searchRunning() const {
         return (search_running_);
     }
-
-    virtual const std::string& getDBName() const {
-        return (database_name_);
-    }
 private:
     std::map<std::string, std::vector< std::vector<std::string> > > records;
     // used as internal index for getNextRecord()
@@ -135,8 +271,6 @@ private:
     // hardcode some exceptions into getNextRecord
     std::string searched_name_;
 
-    const std::string database_name_;
-
     // Adds one record to the current name in the database
     // The actual data will not be added to 'records' until
     // addCurName() is called
@@ -157,6 +291,11 @@ private:
     // so we can immediately start adding new records.
     void addCurName(const std::string& name) {
         ASSERT_EQ(0, records.count(name));
+        // Append the name to all of them
+        for (std::vector<std::vector<std::string> >::iterator
+             i(cur_name.begin()); i != cur_name.end(); ++ i) {
+            i->push_back(name);
+        }
         records[name] = cur_name;
         cur_name.clear();
     }
@@ -306,6 +445,13 @@ private:
     }
 };
 
+// This tests the default getAllRecords behaviour, throwing NotImplemented
+TEST(DatabaseConnectionTest, getAllRecords) {
+    // The parameters don't matter
+    EXPECT_THROW(NopAccessor().getAllRecords(Name("."), 1),
+                 isc::NotImplemented);
+}
+
 class DatabaseClientTest : public ::testing::Test {
 public:
     DatabaseClientTest() {
@@ -380,7 +526,94 @@ TEST_F(DatabaseClientTest, noAccessorException) {
                  isc::InvalidParameter);
 }
 
-namespace {
+// If the zone doesn't exist, exception is thrown
+TEST_F(DatabaseClientTest, noZoneIterator) {
+    EXPECT_THROW(client_->getIterator(Name("example.com")), DataSourceError);
+}
+
+// If the zone doesn't exist and iteration is not implemented, it still throws
+// the exception it doesn't exist
+TEST_F(DatabaseClientTest, noZoneNotImplementedIterator) {
+    EXPECT_THROW(DatabaseClient(boost::shared_ptr<DatabaseAccessor>(
+        new NopAccessor())).getIterator(Name("example.com")),
+                 DataSourceError);
+}
+
+TEST_F(DatabaseClientTest, notImplementedIterator) {
+    EXPECT_THROW(DatabaseClient(shared_ptr<DatabaseAccessor>(
+        new NopAccessor())).getIterator(Name("example.org")),
+                 isc::NotImplemented);
+}
+
+// Pretend a bug in the connection and pass NULL as the context
+// Should not crash, but gracefully throw
+TEST_F(DatabaseClientTest, nullIteratorContext) {
+    EXPECT_THROW(client_->getIterator(Name("null.example.org")),
+                 isc::Unexpected);
+}
+
+// It doesn't crash or anything if the zone is completely empty
+TEST_F(DatabaseClientTest, emptyIterator) {
+    ZoneIteratorPtr it(client_->getIterator(Name("empty.example.org")));
+    EXPECT_EQ(ConstRRsetPtr(), it->getNextRRset());
+    // This is past the end, it should throw
+    EXPECT_THROW(it->getNextRRset(), isc::Unexpected);
+}
+
+// Iterate trough a zone
+TEST_F(DatabaseClientTest, iterator) {
+    ZoneIteratorPtr it(client_->getIterator(Name("example.org")));
+    ConstRRsetPtr rrset(it->getNextRRset());
+    ASSERT_NE(ConstRRsetPtr(), rrset);
+    EXPECT_EQ(Name("example.org"), rrset->getName());
+    EXPECT_EQ(RRClass::IN(), rrset->getClass());
+    EXPECT_EQ(RRType::SOA(), rrset->getType());
+    EXPECT_EQ(RRTTL(300), rrset->getTTL());
+    RdataIteratorPtr rit(rrset->getRdataIterator());
+    ASSERT_FALSE(rit->isLast());
+    rit->next();
+    EXPECT_TRUE(rit->isLast());
+
+    rrset = it->getNextRRset();
+    ASSERT_NE(ConstRRsetPtr(), rrset);
+    EXPECT_EQ(Name("x.example.org"), rrset->getName());
+    EXPECT_EQ(RRClass::IN(), rrset->getClass());
+    EXPECT_EQ(RRType::A(), rrset->getType());
+    EXPECT_EQ(RRTTL(300), rrset->getTTL());
+    rit = rrset->getRdataIterator();
+    ASSERT_FALSE(rit->isLast());
+    EXPECT_EQ("192.0.2.1", rit->getCurrent().toText());
+    rit->next();
+    ASSERT_FALSE(rit->isLast());
+    EXPECT_EQ("192.0.2.2", rit->getCurrent().toText());
+    rit->next();
+    EXPECT_TRUE(rit->isLast());
+
+    rrset = it->getNextRRset();
+    ASSERT_NE(ConstRRsetPtr(), rrset);
+    EXPECT_EQ(Name("x.example.org"), rrset->getName());
+    EXPECT_EQ(RRClass::IN(), rrset->getClass());
+    EXPECT_EQ(RRType::AAAA(), rrset->getType());
+    EXPECT_EQ(RRTTL(300), rrset->getTTL());
+    EXPECT_EQ(ConstRRsetPtr(), it->getNextRRset());
+    rit = rrset->getRdataIterator();
+    ASSERT_FALSE(rit->isLast());
+    EXPECT_EQ("2001:db8::1", rit->getCurrent().toText());
+    rit->next();
+    ASSERT_FALSE(rit->isLast());
+    EXPECT_EQ("2001:db8::2", rit->getCurrent().toText());
+    rit->next();
+    EXPECT_TRUE(rit->isLast());
+}
+
+// This has inconsistent TTL in the set (the rest, like nonsense in
+// the data is handled in rdata itself).
+TEST_F(DatabaseClientTest, badIterator) {
+    // It should not throw, but get the lowest one of them
+    ZoneIteratorPtr it(client_->getIterator(Name("bad.example.org")));
+    EXPECT_EQ(it->getNextRRset()->getTTL(), isc::dns::RRTTL(300));
+}
+
 // checks if the given rrset matches the
 // given name, class, type and rdatas
 void
@@ -433,7 +666,6 @@ doFindTest(shared_ptr<DatabaseClient::Finder> finder,
         EXPECT_EQ(isc::dns::RRsetPtr(), result.rrset);
     }
 }
-} // end anonymous namespace
 
 TEST_F(DatabaseClientTest, find) {
     shared_ptr<DatabaseClient::Finder> finder(getFinder());

+ 39 - 0
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -29,6 +29,8 @@
 #include <dns/masterload.h>
 
 #include <datasrc/memory_datasrc.h>
+#include <datasrc/data_source.h>
+#include <datasrc/iterator.h>
 
 #include <gtest/gtest.h>
 
@@ -138,6 +140,43 @@ TEST_F(InMemoryClientTest, add_find_Zone) {
                   getOrigin());
 }
 
+TEST_F(InMemoryClientTest, iterator) {
+    // Just some preparations of data
+    boost::shared_ptr<InMemoryZoneFinder>
+        zone(new InMemoryZoneFinder(RRClass::IN(), Name("a")));
+    RRsetPtr aRRsetA(new RRset(Name("a"), RRClass::IN(), RRType::A(),
+                                  RRTTL(300)));
+    aRRsetA->addRdata(rdata::in::A("192.0.2.1"));
+    RRsetPtr aRRsetAAAA(new RRset(Name("a"), RRClass::IN(), RRType::AAAA(),
+                                  RRTTL(300)));
+    aRRsetAAAA->addRdata(rdata::in::AAAA("2001:db8::1"));
+    aRRsetAAAA->addRdata(rdata::in::AAAA("2001:db8::2"));
+    RRsetPtr subRRsetA(new RRset(Name("sub.x.a"), RRClass::IN(), RRType::A(),
+                                  RRTTL(300)));
+    subRRsetA->addRdata(rdata::in::A("192.0.2.2"));
+    EXPECT_EQ(result::SUCCESS, memory_client.addZone(zone));
+    // First, the zone is not there, so it should throw
+    EXPECT_THROW(memory_client.getIterator(Name("b")), DataSourceError);
+    // This zone is not there either, even when there's a zone containing this
+    EXPECT_THROW(memory_client.getIterator(Name("x.a")), DataSourceError);
+    // Now, an empty zone
+    ZoneIteratorPtr iterator(memory_client.getIterator(Name("a")));
+    EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
+    // It throws Unexpected when we are past the end
+    EXPECT_THROW(iterator->getNextRRset(), isc::Unexpected);
+    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)
+    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());
+}
+
 TEST_F(InMemoryClientTest, getZoneCount) {
     EXPECT_EQ(0, memory_client.getZoneCount());
     memory_client.addZone(

+ 78 - 29
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc

@@ -76,8 +76,8 @@ public:
     void initAccessor(const std::string& filename, const RRClass& rrclass) {
         db.reset(new SQLite3Database(filename, rrclass));
     }
-    // The tested dbection
-    boost::scoped_ptr<SQLite3Database> db;
+    // The tested db
+    boost::shared_ptr<SQLite3Database> db;
 };
 
 // This zone exists in the data, so it should be found
@@ -103,6 +103,47 @@ TEST_F(SQLite3Access, noClass) {
     EXPECT_FALSE(db->getZone(Name("example.com")).first);
 }
 
+// This tests the iterator context
+TEST_F(SQLite3Access, iterator) {
+    // Our test zone is conveniently small, but not empty
+    initAccessor(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
+
+    // Get the iterator context
+    DatabaseAccessor::IteratorContextPtr
+        context(db->getAllRecords(Name("example2.com"), 1));
+    ASSERT_NE(DatabaseAccessor::IteratorContextPtr(),
+              context);
+
+    const size_t size(5);
+    std::string data[size];
+    // Get and check the first and only record
+    EXPECT_TRUE(context->getNext(data, size));
+    EXPECT_EQ("example2.com.", data[4]);
+    EXPECT_EQ("SOA", data[0]);
+    EXPECT_EQ("master.example2.com. admin.example2.com. "
+              "1234 3600 1800 2419200 7200", data[3]);
+    EXPECT_EQ("3600", data[1]);
+    // Check there's no other
+    EXPECT_FALSE(context->getNext(data, size));
+}
+
+TEST_F(SQLite3Access, iteratorColumnCount) {
+    // Our test zone is conveniently small, but not empty
+    initAccessor(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
+
+    // Get the iterator context
+    DatabaseAccessor::IteratorContextPtr
+        context(db->getAllRecords(Name("example2.com"), 1));
+    ASSERT_NE(DatabaseAccessor::IteratorContextPtr(),
+              context);
+
+    EXPECT_THROW(context->getNext(NULL, 0), DataSourceError);
+    std::string data[6];
+    EXPECT_THROW(context->getNext(data, 4), DataSourceError);
+    EXPECT_THROW(context->getNext(data, 6), DataSourceError);
+    EXPECT_NO_THROW(context->getNext(data, 5));
+}
+
 TEST(SQLite3Open, getDBNameExample2) {
     SQLite3Database db(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
     EXPECT_EQ(SQLITE_DBNAME_EXAMPLE2, db.getDBName());
@@ -120,12 +161,14 @@ checkRecordRow(const std::string columns[],
                const std::string& field0,
                const std::string& field1,
                const std::string& field2,
-               const std::string& field3)
+               const std::string& field3,
+               const std::string& field4)
 {
     EXPECT_EQ(field0, columns[0]);
     EXPECT_EQ(field1, columns[1]);
     EXPECT_EQ(field2, columns[2]);
     EXPECT_EQ(field3, columns[3]);
+    EXPECT_EQ(field4, columns[4]);
 }
 
 TEST_F(SQLite3Access, getRecords) {
@@ -140,83 +183,89 @@ TEST_F(SQLite3Access, getRecords) {
 
     // without search, getNext() should return false
     EXPECT_FALSE(db->getNextRecord(columns, column_count));
-    checkRecordRow(columns, "", "", "", "");
+    checkRecordRow(columns, "", "", "", "", "");
 
     db->searchForRecords(zone_id, "foo.bar.");
     EXPECT_FALSE(db->getNextRecord(columns, column_count));
-    checkRecordRow(columns, "", "", "", "");
+    checkRecordRow(columns, "", "", "", "", "");
 
     db->searchForRecords(zone_id, "");
     EXPECT_FALSE(db->getNextRecord(columns, column_count));
-    checkRecordRow(columns, "", "", "", "");
+    checkRecordRow(columns, "", "", "", "", "");
 
     // Should error on a bad number of columns
-    EXPECT_THROW(db->getNextRecord(columns, 3), DataSourceError);
-    EXPECT_THROW(db->getNextRecord(columns, 5), DataSourceError);
+    EXPECT_THROW(db->getNextRecord(columns, 4), DataSourceError);
+    EXPECT_THROW(db->getNextRecord(columns, 6), DataSourceError);
 
     // now try some real searches
     db->searchForRecords(zone_id, "foo.example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "CNAME", "3600", "",
-                   "cnametest.example.org.");
+                   "cnametest.example.org.", "foo.example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "RRSIG", "3600", "CNAME",
                    "CNAME 5 3 3600 20100322084538 20100220084538 33495 "
-                   "example.com. FAKEFAKEFAKEFAKE");
+                   "example.com. FAKEFAKEFAKEFAKE", "foo.example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "NSEC", "7200", "",
-                   "mail.example.com. CNAME RRSIG NSEC");
+                   "mail.example.com. CNAME RRSIG NSEC", "foo.example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "RRSIG", "7200", "NSEC",
                    "NSEC 5 3 7200 20100322084538 20100220084538 33495 "
-                   "example.com. FAKEFAKEFAKEFAKE");
+                   "example.com. FAKEFAKEFAKEFAKE", "foo.example.com.");
     EXPECT_FALSE(db->getNextRecord(columns, column_count));
     // with no more records, the array should not have been modified
     checkRecordRow(columns, "RRSIG", "7200", "NSEC",
                    "NSEC 5 3 7200 20100322084538 20100220084538 33495 "
-                   "example.com. FAKEFAKEFAKEFAKE");
+                   "example.com. FAKEFAKEFAKEFAKE", "foo.example.com.");
 
     db->searchForRecords(zone_id, "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "SOA", "3600", "",
                    "master.example.com. admin.example.com. "
-                   "1234 3600 1800 2419200 7200");
+                   "1234 3600 1800 2419200 7200", "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "RRSIG", "3600", "SOA",
                    "SOA 5 2 3600 20100322084538 20100220084538 "
-                   "33495 example.com. FAKEFAKEFAKEFAKE");
+                   "33495 example.com. FAKEFAKEFAKEFAKE", "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
-    checkRecordRow(columns, "NS", "1200", "", "dns01.example.com.");
+    checkRecordRow(columns, "NS", "1200", "", "dns01.example.com.",
+                   "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
-    checkRecordRow(columns, "NS", "3600", "", "dns02.example.com.");
+    checkRecordRow(columns, "NS", "3600", "", "dns02.example.com.",
+                   "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
-    checkRecordRow(columns, "NS", "1800", "", "dns03.example.com.");
+    checkRecordRow(columns, "NS", "1800", "", "dns03.example.com.",
+                   "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "RRSIG", "3600", "NS",
                    "NS 5 2 3600 20100322084538 20100220084538 "
-                   "33495 example.com. FAKEFAKEFAKEFAKE");
+                   "33495 example.com. FAKEFAKEFAKEFAKE",
+                   "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
-    checkRecordRow(columns, "MX", "3600", "", "10 mail.example.com.");
+    checkRecordRow(columns, "MX", "3600", "", "10 mail.example.com.",
+                   "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "MX", "3600", "",
-                   "20 mail.subzone.example.com.");
+                   "20 mail.subzone.example.com.", "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "RRSIG", "3600", "MX",
                    "MX 5 2 3600 20100322084538 20100220084538 "
-                   "33495 example.com. FAKEFAKEFAKEFAKE");
+                   "33495 example.com. FAKEFAKEFAKEFAKE", "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "NSEC", "7200", "",
-                   "cname-ext.example.com. NS SOA MX RRSIG NSEC DNSKEY");
+                   "cname-ext.example.com. NS SOA MX RRSIG NSEC DNSKEY",
+                   "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "RRSIG", "7200", "NSEC",
                    "NSEC 5 2 7200 20100322084538 20100220084538 "
-                   "33495 example.com. FAKEFAKEFAKEFAKE");
+                   "33495 example.com. FAKEFAKEFAKEFAKE", "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "DNSKEY", "3600", "",
                    "256 3 5 AwEAAcOUBllYc1hf7ND9uDy+Yz1BF3sI0m4q NGV7W"
                    "cTD0WEiuV7IjXgHE36fCmS9QsUxSSOV o1I/FMxI2PJVqTYHkX"
                    "FBS7AzLGsQYMU7UjBZ SotBJ6Imt5pXMu+lEDNy8TOUzG3xm7g"
-                   "0qcbW YF6qCEfvZoBtAqi5Rk7Mlrqs8agxYyMx");
+                   "0qcbW YF6qCEfvZoBtAqi5Rk7Mlrqs8agxYyMx", "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "DNSKEY", "3600", "",
                    "257 3 5 AwEAAe5WFbxdCPq2jZrZhlMj7oJdff3W7syJ tbvzg"
@@ -226,20 +275,20 @@ TEST_F(SQLite3Access, getRecords) {
                    "qiODyNZYQ+ZrLmF0KIJ2yPN3iO6Zq 23TaOrVTjB7d1a/h31OD"
                    "fiHAxFHrkY3t3D5J R9Nsl/7fdRmSznwtcSDgLXBoFEYmw6p86"
                    "Acv RyoYNcL1SXjaKVLG5jyU3UR+LcGZT5t/0xGf oIK/aKwEN"
-                   "rsjcKZZj660b1M=");
+                   "rsjcKZZj660b1M=", "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "RRSIG", "3600", "DNSKEY",
                    "DNSKEY 5 2 3600 20100322084538 20100220084538 "
-                   "4456 example.com. FAKEFAKEFAKEFAKE");
+                   "4456 example.com. FAKEFAKEFAKEFAKE", "example.com.");
     ASSERT_TRUE(db->getNextRecord(columns, column_count));
     checkRecordRow(columns, "RRSIG", "3600", "DNSKEY",
                    "DNSKEY 5 2 3600 20100322084538 20100220084538 "
-                   "33495 example.com. FAKEFAKEFAKEFAKE");
+                   "33495 example.com. FAKEFAKEFAKEFAKE", "example.com.");
     EXPECT_FALSE(db->getNextRecord(columns, column_count));
     // getnextrecord returning false should mean array is not altered
     checkRecordRow(columns, "RRSIG", "3600", "DNSKEY",
                    "DNSKEY 5 2 3600 20100322084538 20100220084538 "
-                   "33495 example.com. FAKEFAKEFAKEFAKE");
+                   "33495 example.com. FAKEFAKEFAKEFAKE", "example.com.");
 }
 
 } // end anonymous namespace

+ 1 - 0
src/lib/datasrc/tests/static_unittest.cc

@@ -53,6 +53,7 @@ protected:
 
         // NOTE: in addition, the order of the following items matter.
         authors_data.push_back("Chen Zhengzhang");
+        authors_data.push_back("Dmitriy Volodin");
         authors_data.push_back("Evan Hunt");
         authors_data.push_back("Haidong Wang");
         authors_data.push_back("Han Feng");

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

@@ -51,6 +51,8 @@ EXTRA_DIST += rdata/generic/soa_6.cc
 EXTRA_DIST += rdata/generic/soa_6.h
 EXTRA_DIST += rdata/generic/txt_16.cc
 EXTRA_DIST += rdata/generic/txt_16.h
+EXTRA_DIST += rdata/generic/minfo_14.cc
+EXTRA_DIST += rdata/generic/minfo_14.h
 EXTRA_DIST += rdata/generic/afsdb_18.cc
 EXTRA_DIST += rdata/generic/afsdb_18.h
 EXTRA_DIST += rdata/hs_4/a_1.cc

+ 155 - 0
src/lib/dns/rdata/generic/minfo_14.cc

@@ -0,0 +1,155 @@
+// 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 <sstream>
+
+#include <util/buffer.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::dns;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief Constructor from string.
+///
+/// \c minfo_str must be formatted as follows:
+/// \code <rmailbox name> <emailbox name>
+/// \endcode
+/// where both fields must represent a valid domain name.
+///
+/// An example of valid string is:
+/// \code "rmail.example.com. email.example.com." \endcode
+///
+/// <b>Exceptions</b>
+///
+/// \exception InvalidRdataText The number of RDATA fields (must be 2) is
+/// incorrect.
+/// \exception std::bad_alloc Memory allocation for names fails.
+/// \exception Other The constructor of the \c Name class will throw if the
+/// names in the string is invalid.
+MINFO::MINFO(const std::string& minfo_str) :
+    // We cannot construct both names in the initialization list due to the
+    // necessary text processing, so we have to initialize them with a dummy
+    // name and replace them later.
+    rmailbox_(Name::ROOT_NAME()), emailbox_(Name::ROOT_NAME())
+{
+    istringstream iss(minfo_str);
+    string rmailbox_str, emailbox_str;
+    iss >> rmailbox_str >> emailbox_str;
+
+    // Validation: A valid MINFO RR must have exactly two fields.
+    if (iss.bad() || iss.fail()) {
+        isc_throw(InvalidRdataText, "Invalid MINFO text: " << minfo_str);
+    }
+    if (!iss.eof()) {
+        isc_throw(InvalidRdataText, "Invalid MINFO text (redundant field): "
+                  << minfo_str);
+    }
+
+    rmailbox_ = Name(rmailbox_str);
+    emailbox_ = Name(emailbox_str);
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// This constructor doesn't check the validity of the second parameter (rdata
+/// length) for parsing.
+/// If necessary, the caller will check consistency.
+///
+/// \exception std::bad_alloc Memory allocation for names fails.
+/// \exception Other The constructor of the \c Name class will throw if the
+/// names in the wire is invalid.
+MINFO::MINFO(InputBuffer& buffer, size_t) :
+    rmailbox_(buffer), emailbox_(buffer)
+{}
+
+/// \brief Copy constructor.
+///
+/// \exception std::bad_alloc Memory allocation fails in copying internal
+/// member variables (this should be very rare).
+MINFO::MINFO(const MINFO& other) :
+    Rdata(), rmailbox_(other.rmailbox_), emailbox_(other.emailbox_)
+{}
+
+/// \brief Convert the \c MINFO to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c MINFO(const std::string&))).
+///
+/// \exception std::bad_alloc Internal resource allocation fails.
+///
+/// \return A \c string object that represents the \c MINFO object.
+std::string
+MINFO::toText() const {
+    return (rmailbox_.toText() + " " + emailbox_.toText());
+}
+
+/// \brief Render the \c MINFO in the wire format without name compression.
+///
+/// \exception std::bad_alloc Internal resource allocation fails.
+///
+/// \param buffer An output buffer to store the wire data.
+void
+MINFO::toWire(OutputBuffer& buffer) const {
+    rmailbox_.toWire(buffer);
+    emailbox_.toWire(buffer);
+}
+
+MINFO&
+MINFO::operator=(const MINFO& source) {
+    rmailbox_ = source.rmailbox_;
+    emailbox_ = source.emailbox_;
+
+    return (*this);
+}
+
+/// \brief Render the \c MINFO in the wire format with taking into account
+/// compression.
+///
+/// As specified in RFC3597, TYPE MINFO is "well-known", the rmailbox and
+/// emailbox fields (domain names) will be compressed.
+///
+/// \exception std::bad_alloc Internal resource allocation fails.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer and name compression information.
+void
+MINFO::toWire(AbstractMessageRenderer& renderer) const {
+    renderer.writeName(rmailbox_);
+    renderer.writeName(emailbox_);
+}
+
+/// \brief Compare two instances of \c MINFO RDATA.
+///
+/// See documentation in \c Rdata.
+int
+MINFO::compare(const Rdata& other) const {
+    const MINFO& other_minfo = dynamic_cast<const MINFO&>(other);
+
+    const int cmp = compareNames(rmailbox_, other_minfo.rmailbox_);
+    if (cmp != 0) {
+        return (cmp);
+    }
+    return (compareNames(emailbox_, other_minfo.emailbox_));
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE

+ 82 - 0
src/lib/dns/rdata/generic/minfo_14.h

@@ -0,0 +1,82 @@
+// 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.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief \c rdata::generic::MINFO class represents the MINFO RDATA as
+/// defined in RFC1035.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// MINFO RDATA.
+class MINFO : public Rdata {
+public:
+    // BEGIN_COMMON_MEMBERS
+    // END_COMMON_MEMBERS
+
+    /// \brief Define the assignment operator.
+    ///
+    /// \exception std::bad_alloc Memory allocation fails in copying
+    /// internal member variables (this should be very rare).
+    MINFO& operator=(const MINFO& source);
+
+    /// \brief Return the value of the rmailbox field.
+    ///
+    /// \exception std::bad_alloc If resource allocation for the returned
+    /// \c Name fails.
+    ///
+    /// \note
+    /// Unlike the case of some other RDATA classes (such as
+    /// \c NS::getNSName()), this method constructs a new \c Name object
+    /// and returns it, instead of returning a reference to a \c Name object
+    /// internally maintained in the class (which is a private member).
+    /// This is based on the observation that this method will be rarely
+    /// used and even when it's used it will not be in a performance context
+    /// (for example, a recursive resolver won't need this field in its
+    /// resolution process).  By returning a new object we have flexibility
+    /// of changing the internal representation without the risk of changing
+    /// the interface or method property.
+    /// The same note applies to the \c getEmailbox() method.
+    Name getRmailbox() const { return (rmailbox_); }
+
+    /// \brief Return the value of the emailbox field.
+    ///
+    /// \exception std::bad_alloc If resource allocation for the returned
+    /// \c Name fails.
+    Name getEmailbox() const { return (emailbox_); }
+
+private:
+    Name rmailbox_;
+    Name emailbox_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:

+ 2 - 2
src/lib/dns/rdata/in_1/srv_33.h

@@ -12,13 +12,13 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+// BEGIN_HEADER_GUARD
+
 #include <stdint.h>
 
 #include <dns/name.h>
 #include <dns/rdata.h>
 
-// BEGIN_HEADER_GUARD
-
 // BEGIN_ISC_NAMESPACE
 
 // BEGIN_COMMON_DECLARATIONS

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

@@ -43,6 +43,7 @@ run_unittests_SOURCES += rdata_nsec3param_unittest.cc
 run_unittests_SOURCES += rdata_rrsig_unittest.cc
 run_unittests_SOURCES += rdata_rp_unittest.cc
 run_unittests_SOURCES += rdata_srv_unittest.cc
+run_unittests_SOURCES += rdata_minfo_unittest.cc
 run_unittests_SOURCES += rdata_tsig_unittest.cc
 run_unittests_SOURCES += rdata_naptr_unittest.cc
 run_unittests_SOURCES += rrset_unittest.cc rrsetlist_unittest.cc

+ 184 - 0
src/lib/dns/tests/rdata_minfo_unittest.cc

@@ -0,0 +1,184 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for generic
+// 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 <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+// minfo text
+const char* const minfo_txt = "rmailbox.example.com. emailbox.example.com.";
+const char* const minfo_txt2 = "root.example.com. emailbox.example.com.";
+const char* const too_long_label = "01234567890123456789012345678901234567"
+                                   "89012345678901234567890123";
+
+namespace {
+class Rdata_MINFO_Test : public RdataTest {
+public:
+    Rdata_MINFO_Test():
+        rdata_minfo(string(minfo_txt)), rdata_minfo2(string(minfo_txt2)) {}
+
+    const generic::MINFO rdata_minfo;
+    const generic::MINFO rdata_minfo2;
+};
+
+
+TEST_F(Rdata_MINFO_Test, createFromText) {
+    EXPECT_EQ(Name("rmailbox.example.com."), rdata_minfo.getRmailbox());
+    EXPECT_EQ(Name("emailbox.example.com."), rdata_minfo.getEmailbox());
+
+    EXPECT_EQ(Name("root.example.com."), rdata_minfo2.getRmailbox());
+    EXPECT_EQ(Name("emailbox.example.com."), rdata_minfo2.getEmailbox());
+}
+
+TEST_F(Rdata_MINFO_Test, badText) {
+    // incomplete text
+    EXPECT_THROW(generic::MINFO("root.example.com."),
+                 InvalidRdataText);
+    // number of fields (must be 2) is incorrect
+    EXPECT_THROW(generic::MINFO("root.example.com emailbox.example.com. "
+                                "example.com."),
+                 InvalidRdataText);
+    // bad rmailbox name
+    EXPECT_THROW(generic::MINFO("root.example.com. emailbox.example.com." +
+                                string(too_long_label)),
+                 TooLongLabel);
+    // bad emailbox name
+    EXPECT_THROW(generic::MINFO("root.example.com."  +
+                          string(too_long_label) + " emailbox.example.com."),
+                 TooLongLabel);
+}
+
+TEST_F(Rdata_MINFO_Test, createFromWire) {
+    // uncompressed names
+    EXPECT_EQ(0, rdata_minfo.compare(
+                  *rdataFactoryFromFile(RRType::MINFO(), RRClass::IN(),
+                                     "rdata_minfo_fromWire1.wire")));
+    // compressed names
+    EXPECT_EQ(0, rdata_minfo.compare(
+                  *rdataFactoryFromFile(RRType::MINFO(), RRClass::IN(),
+                                     "rdata_minfo_fromWire2.wire", 15)));
+    // RDLENGTH is too short
+    EXPECT_THROW(rdataFactoryFromFile(RRType::MINFO(), RRClass::IN(),
+                                     "rdata_minfo_fromWire3.wire"),
+                 InvalidRdataLength);
+    // RDLENGTH is too long
+    EXPECT_THROW(rdataFactoryFromFile(RRType::MINFO(), RRClass::IN(),
+                                      "rdata_minfo_fromWire4.wire"),
+                 InvalidRdataLength);
+    // bogus rmailbox name, the error should be detected in the name
+    // constructor
+    EXPECT_THROW(rdataFactoryFromFile(RRType::MINFO(), RRClass::IN(),
+                                      "rdata_minfo_fromWire5.wire"),
+                 DNSMessageFORMERR);
+    // bogus emailbox name, the error should be detected in the name
+    // constructor
+    EXPECT_THROW(rdataFactoryFromFile(RRType::MINFO(), RRClass::IN(),
+                                      "rdata_minfo_fromWire6.wire"),
+                 DNSMessageFORMERR);
+}
+
+TEST_F(Rdata_MINFO_Test, assignment) {
+    generic::MINFO copy((string(minfo_txt2)));
+    copy = rdata_minfo;
+    EXPECT_EQ(0, copy.compare(rdata_minfo));
+
+    // Check if the copied data is valid even after the original is deleted
+    generic::MINFO* copy2 = new generic::MINFO(rdata_minfo);
+    generic::MINFO copy3((string(minfo_txt2)));
+    copy3 = *copy2;
+    delete copy2;
+    EXPECT_EQ(0, copy3.compare(rdata_minfo));
+
+    // Self assignment
+    copy = copy;
+    EXPECT_EQ(0, copy.compare(rdata_minfo));
+}
+
+TEST_F(Rdata_MINFO_Test, toWireBuffer) {
+    rdata_minfo.toWire(obuffer);
+    vector<unsigned char> data;
+    UnitTestUtil::readWireData("rdata_minfo_toWireUncompressed1.wire", data);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        static_cast<const uint8_t *>(obuffer.getData()),
+                        obuffer.getLength(), &data[0], data.size());
+
+    obuffer.clear();
+    rdata_minfo2.toWire(obuffer);
+    vector<unsigned char> data2;
+    UnitTestUtil::readWireData("rdata_minfo_toWireUncompressed2.wire", data2);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        static_cast<const uint8_t *>(obuffer.getData()),
+                        obuffer.getLength(), &data2[0], data2.size());
+}
+
+TEST_F(Rdata_MINFO_Test, toWireRenderer) {
+    rdata_minfo.toWire(renderer);
+    vector<unsigned char> data;
+    UnitTestUtil::readWireData("rdata_minfo_toWire1.wire", data);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        static_cast<const uint8_t *>(obuffer.getData()),
+                        obuffer.getLength(), &data[0], data.size());
+    renderer.clear();
+    rdata_minfo2.toWire(renderer);
+    vector<unsigned char> data2;
+    UnitTestUtil::readWireData("rdata_minfo_toWire2.wire", data2);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        static_cast<const uint8_t *>(obuffer.getData()),
+                        obuffer.getLength(), &data2[0], data2.size());
+}
+
+TEST_F(Rdata_MINFO_Test, toText) {
+    EXPECT_EQ(minfo_txt, rdata_minfo.toText());
+    EXPECT_EQ(minfo_txt2, rdata_minfo2.toText());
+}
+
+TEST_F(Rdata_MINFO_Test, compare) {
+    // check reflexivity
+    EXPECT_EQ(0, rdata_minfo.compare(rdata_minfo));
+
+    // names must be compared in case-insensitive manner
+    EXPECT_EQ(0, rdata_minfo.compare(generic::MINFO("RMAILBOX.example.com. "
+                                                  "emailbox.EXAMPLE.com.")));
+
+    // another MINFO whose rmailbox name is larger than that of rdata_minfo.
+    const generic::MINFO large1_minfo("zzzzzzzz.example.com. "
+                                      "emailbox.example.com.");
+    EXPECT_GT(0, rdata_minfo.compare(large1_minfo));
+    EXPECT_LT(0, large1_minfo.compare(rdata_minfo));
+
+    // another MINFO whose emailbox name is larger than that of rdata_minfo.
+    const generic::MINFO large2_minfo("rmailbox.example.com. "
+                                      "zzzzzzzzzzz.example.com.");
+    EXPECT_GT(0, rdata_minfo.compare(large2_minfo));
+    EXPECT_LT(0, large2_minfo.compare(rdata_minfo));
+
+    // comparison attempt between incompatible RR types should be rejected
+    EXPECT_THROW(rdata_minfo.compare(*RdataTest::rdata_nomatch), bad_cast);
+}
+}

+ 12 - 0
src/lib/dns/tests/testdata/Makefile.am

@@ -26,6 +26,12 @@ BUILT_SOURCES += rdata_nsec3_fromWire10.wire rdata_nsec3_fromWire11.wire
 BUILT_SOURCES += rdata_nsec3_fromWire12.wire rdata_nsec3_fromWire13.wire
 BUILT_SOURCES += rdata_nsec3_fromWire14.wire rdata_nsec3_fromWire15.wire
 BUILT_SOURCES += rdata_rrsig_fromWire2.wire
+BUILT_SOURCES += rdata_minfo_fromWire1.wire rdata_minfo_fromWire2.wire
+BUILT_SOURCES += rdata_minfo_fromWire3.wire rdata_minfo_fromWire4.wire
+BUILT_SOURCES += rdata_minfo_fromWire5.wire rdata_minfo_fromWire6.wire
+BUILT_SOURCES += rdata_minfo_toWire1.wire rdata_minfo_toWire2.wire
+BUILT_SOURCES += rdata_minfo_toWireUncompressed1.wire
+BUILT_SOURCES += rdata_minfo_toWireUncompressed2.wire
 BUILT_SOURCES += rdata_rp_fromWire1.wire rdata_rp_fromWire2.wire
 BUILT_SOURCES += rdata_rp_fromWire3.wire rdata_rp_fromWire4.wire
 BUILT_SOURCES += rdata_rp_fromWire5.wire rdata_rp_fromWire6.wire
@@ -109,6 +115,12 @@ EXTRA_DIST += rdata_afsdb_fromWire5.spec
 EXTRA_DIST += rdata_afsdb_toWire1.spec rdata_afsdb_toWire2.spec
 EXTRA_DIST += rdata_soa_fromWire rdata_soa_toWireUncompressed.spec
 EXTRA_DIST += rdata_srv_fromWire
+EXTRA_DIST += rdata_minfo_fromWire1.spec rdata_minfo_fromWire2.spec
+EXTRA_DIST += rdata_minfo_fromWire3.spec rdata_minfo_fromWire4.spec
+EXTRA_DIST += rdata_minfo_fromWire5.spec rdata_minfo_fromWire6.spec
+EXTRA_DIST += rdata_minfo_toWire1.spec rdata_minfo_toWire2.spec
+EXTRA_DIST += rdata_minfo_toWireUncompressed1.spec
+EXTRA_DIST += rdata_minfo_toWireUncompressed2.spec
 EXTRA_DIST += rdata_txt_fromWire1 rdata_txt_fromWire2.spec
 EXTRA_DIST += rdata_txt_fromWire3.spec rdata_txt_fromWire4.spec
 EXTRA_DIST += rdata_txt_fromWire5.spec rdata_unknown_fromWire

+ 3 - 0
src/lib/dns/tests/testdata/rdata_minfo_fromWire1.spec

@@ -0,0 +1,3 @@
+[custom]
+sections: minfo
+[minfo]

+ 7 - 0
src/lib/dns/tests/testdata/rdata_minfo_fromWire2.spec

@@ -0,0 +1,7 @@
+[custom]
+sections: name:minfo
+[name]
+name: a.example.com.
+[minfo]
+rmailbox: rmailbox.ptr=02
+emailbox: emailbox.ptr=02

+ 6 - 0
src/lib/dns/tests/testdata/rdata_minfo_fromWire3.spec

@@ -0,0 +1,6 @@
+[custom]
+sections: minfo
+# rdlength too short
+[minfo]
+emailbox: emailbox.ptr=11
+rdlen: 3

+ 6 - 0
src/lib/dns/tests/testdata/rdata_minfo_fromWire4.spec

@@ -0,0 +1,6 @@
+[custom]
+sections: minfo
+# rdlength too long
+[minfo]
+emailbox: emailbox.ptr=11
+rdlen: 80

+ 5 - 0
src/lib/dns/tests/testdata/rdata_minfo_fromWire5.spec

@@ -0,0 +1,5 @@
+[custom]
+sections: minfo
+# bogus rmailbox name
+[minfo]
+rmailbox: "01234567890123456789012345678901234567890123456789012345678901234"

+ 5 - 0
src/lib/dns/tests/testdata/rdata_minfo_fromWire6.spec

@@ -0,0 +1,5 @@
+[custom]
+sections: minfo
+# bogus emailbox name
+[minfo]
+emailbox: "01234567890123456789012345678901234567890123456789012345678901234"

+ 5 - 0
src/lib/dns/tests/testdata/rdata_minfo_toWire1.spec

@@ -0,0 +1,5 @@
+[custom]
+sections: minfo
+[minfo]
+emailbox: emailbox.ptr=09
+rdlen: -1

+ 6 - 0
src/lib/dns/tests/testdata/rdata_minfo_toWire2.spec

@@ -0,0 +1,6 @@
+[custom]
+sections: minfo
+[minfo]
+rmailbox: root.example.com.
+emailbox: emailbox.ptr=05
+rdlen: -1

+ 7 - 0
src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed1.spec

@@ -0,0 +1,7 @@
+#
+# A simplest form of MINFO: all default parameters
+#
+[custom]
+sections: minfo
+[minfo]
+rdlen: -1

+ 8 - 0
src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed2.spec

@@ -0,0 +1,8 @@
+#
+# A simplest form of MINFO: custom rmailbox and default emailbox
+#
+[custom]
+sections: minfo
+[minfo]
+rmailbox: root.example.com.
+rdlen: -1

+ 12 - 0
src/lib/exceptions/exceptions.h

@@ -137,6 +137,18 @@ public:
 };
 
 ///
+/// \brief A generic exception that is thrown when a function is
+/// not implemented.
+///
+/// This may be due to unfinished implementation or in case the
+/// function isn't even planned to be provided for that situation.
+class NotImplemented : public Exception {
+public:
+    NotImplemented(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+///
 /// A shortcut macro to insert known values into exception arguments.
 ///
 /// It allows the \c stream argument to be part of a statement using an

+ 22 - 0
src/lib/util/python/gen_wiredata.py.in

@@ -822,6 +822,28 @@ class RP(RR):
         f.write('# MAILBOX=%s TEXT=%s\n' % (self.mailbox, self.text))
         f.write('%s %s\n' % (mailbox_wire, text_wire))
 
+class MINFO(RR):
+    '''Implements rendering MINFO RDATA in the test data format.
+
+    Configurable parameters are as follows (see the description of the
+    same name of attribute for the default value):
+    - rmailbox (string): The rmailbox field.
+    - emailbox (string): The emailbox field.
+    These strings must be interpreted as a valid domain name.
+    '''
+    rmailbox = 'rmailbox.example.com'
+    emailbox = 'emailbox.example.com'
+    def dump(self, f):
+        rmailbox_wire = encode_name(self.rmailbox)
+        emailbox_wire = encode_name(self.emailbox)
+        if self.rdlen is None:
+            self.rdlen = (len(rmailbox_wire) + len(emailbox_wire)) / 2
+        else:
+            self.rdlen = int(self.rdlen)
+        self.dump_header(f, self.rdlen)
+        f.write('# RMAILBOX=%s EMAILBOX=%s\n' % (self.rmailbox, self.emailbox))
+        f.write('%s %s\n' % (rmailbox_wire, emailbox_wire))
+
 class AFSDB(RR):
     '''Implements rendering AFSDB RDATA in the test data format.