Parcourir la source

Merge branch 'master' of ssh://bind10.isc.org/var/bind10/git/bind10

Naoki Kambe il y a 14 ans
Parent
commit
fa321eca22
63 fichiers modifiés avec 2530 ajouts et 465 suppressions
  1. 33 17
      ChangeLog
  2. 12 2
      configure.ac
  3. 1 1
      src/bin/bind10/run_bind10.sh.in
  4. 17 14
      src/bin/host/Makefile.am
  5. 3 13
      src/bin/host/README
  6. 122 0
      src/bin/host/b10-host.1
  7. 201 0
      src/bin/host/b10-host.xml
  8. 52 21
      src/bin/host/host.cc
  9. 1 1
      src/bin/msgq/tests/msgq_test.py
  10. 1 0
      src/lib/dns/Makefile.am
  11. 21 8
      src/lib/dns/edns.cc
  12. 2 8
      src/lib/dns/edns.h
  13. 263 156
      src/lib/dns/message.cc
  14. 52 3
      src/lib/dns/message.h
  15. 1 0
      src/lib/dns/python/Makefile.am
  16. 64 59
      src/lib/dns/python/message_python.cc
  17. 5 0
      src/lib/dns/python/pydnspp.cc
  18. 1 0
      src/lib/dns/python/tests/Makefile.am
  19. 36 1
      src/lib/dns/python/tests/message_python_test.py
  20. 29 0
      src/lib/dns/python/tests/tsig_python_test.py
  21. 157 0
      src/lib/dns/python/tsig_python.cc
  22. 1 1
      src/lib/dns/question.cc
  23. 3 3
      src/lib/dns/question.h
  24. 2 10
      src/lib/dns/rdata/any_255/tsig_250.cc
  25. 2 2
      src/lib/dns/rrclass-placeholder.h
  26. 1 1
      src/lib/dns/rrclass.cc
  27. 3 3
      src/lib/dns/rrset.cc
  28. 3 3
      src/lib/dns/rrset.h
  29. 1 1
      src/lib/dns/rrttl.cc
  30. 2 2
      src/lib/dns/rrttl.h
  31. 1 0
      src/lib/dns/tests/Makefile.am
  32. 190 1
      src/lib/dns/tests/message_unittest.cc
  33. 3 0
      src/lib/dns/tests/run_unittests.cc
  34. 17 4
      src/lib/dns/tests/testdata/Makefile.am
  35. 83 9
      src/lib/dns/tests/testdata/gen-wiredata.py.in
  36. 21 0
      src/lib/dns/tests/testdata/message_fromWire12.spec
  37. 20 0
      src/lib/dns/tests/testdata/message_fromWire13.spec
  38. 21 0
      src/lib/dns/tests/testdata/message_fromWire14.spec
  39. 22 0
      src/lib/dns/tests/testdata/message_fromWire15.spec
  40. 21 0
      src/lib/dns/tests/testdata/message_fromWire16.spec
  41. 24 0
      src/lib/dns/tests/testdata/message_toText1.spec
  42. 14 0
      src/lib/dns/tests/testdata/message_toText1.txt
  43. 14 0
      src/lib/dns/tests/testdata/message_toText2.spec
  44. 8 0
      src/lib/dns/tests/testdata/message_toText2.txt
  45. 31 0
      src/lib/dns/tests/testdata/message_toText3.spec
  46. 17 0
      src/lib/dns/tests/testdata/message_toText3.txt
  47. 21 0
      src/lib/dns/tests/testdata/message_toWire2.spec
  48. 22 0
      src/lib/dns/tests/testdata/message_toWire3.spec
  49. 16 0
      src/lib/dns/tests/testdata/tsigrecord_toWire1.spec
  50. 19 0
      src/lib/dns/tests/testdata/tsigrecord_toWire2.spec
  51. 15 15
      src/lib/dns/tests/tsig_unittest.cc
  52. 160 0
      src/lib/dns/tests/tsigrecord_unittest.cc
  53. 4 29
      src/lib/dns/tsig.cc
  54. 1 71
      src/lib/dns/tsig.h
  55. 145 0
      src/lib/dns/tsigrecord.cc
  56. 285 0
      src/lib/dns/tsigrecord.h
  57. 1 1
      src/lib/util/encode/base_n.cc
  58. 2 5
      src/lib/util/time_utilities.cc
  59. 28 0
      src/lib/util/time_utilities.h
  60. 2 0
      src/lib/util/unittests/Makefile.am
  61. 61 0
      src/lib/util/unittests/testdata.cc
  62. 46 0
      src/lib/util/unittests/testdata.h
  63. 103 0
      src/lib/util/unittests/textdata.h

+ 33 - 17
ChangeLog

@@ -1,3 +1,16 @@
+229.	[doc]		jreed
+	Add manual page for b10-host.
+	(git a437d4e26b81bb07181ff35a625c540703eee845)
+
+228.	[func]*		jreed
+	The host tool is renamed to b10-host. While the utility is
+	a work in progress, it is expected to now be shipped with
+	tarballs. Its initial goal was to be a host(1) clone,
+	rewritten in C++ from scratch and using BIND 10's libdns++.
+	It now supports the -a (any), -c class, -d (verbose) switches
+	and has improved output.
+	(Trac #872, git d846851699d5c76937533adf9ff9d948dfd593ca)
+
 227.	[build]		jreed
 	Add missing libdns++ rdata files for the distribution (this
 	fixes distcheck error). Change three generated libdns++
@@ -10,17 +23,19 @@
 	implementation uses Botan as the backend library.
 	This introduces a new dependency, on Botan.  Currently only Botan
 	1.8.x works; older or newer versions don't.
-	(Trac#781, git 9df42279a47eb617f586144dce8cce680598558a)
+	(Trac #781, git 9df42279a47eb617f586144dce8cce680598558a)
 
 225.	[func]		naokikambe
-	Added the HTTP/XML interface(b10-stats-httpd) to the statistics feature
-	in BIND 10. b10-stats-httpd is a standalone HTTP server and it requests
-	statistics data to the stats daemon(b10-stats) and sends it to HTTP
-	clients in XML format. Items of the data collected via b10-stats-httpd
-	are almost equivalent to ones which are collected via bindctl. Since it
-	also can send XSL(Extensible Stylessheet Language) document and XSD(XML
-	Schema definition) document, XML document is human-friendly to view
-	through web browsers and its data types are strictly defined.
+	Added the HTTP/XML interface(b10-stats-httpd) to the
+	statistics feature in BIND 10. b10-stats-httpd is a standalone
+	HTTP server and it requests statistics data to the stats
+	daemon(b10-stats) and sends it to HTTP clients in XML
+	format. Items of the data collected via b10-stats-httpd
+	are almost equivalent to ones which are collected via
+	bindctl. Since it also can send XSL(Extensible Stylessheet
+	Language) document and XSD(XML Schema definition) document,
+	XML document is human-friendly to view through web browsers
+	and its data types are strictly defined.
 	(Trac #547, git 1cbd51919237a6e65983be46e4f5a63d1877b1d3)
 
 224.	[bug]		jinmei
@@ -32,11 +47,12 @@
 	(Trac #851, git 2463b96680bb3e9a76e50c38a4d7f1d38d810643)
 
 223.	[bug]		feng
-	If ip address or port isn't usable for name server, name server process
-	won't exist and give end user chance to reconfigure them.
+	If ip address or port isn't usable for name server, name
+	server process won't exist and give end user chance to
+	reconfigure them.
 	(Trac #775, git 572ac2cf62e18f7eb69d670b890e2a3443bfd6e7)
 
-222.	[bug] 		jerry
+222.	[bug]		jerry
 	src/lib/zonemgr: Fix a bug that xfrin not checking for new copy of
 	zone on startup.  Imposes some random jitters to avoid many zones
 	need to do refresh at the same time.
@@ -51,7 +67,7 @@
 	(potentially) bad packets to a nameserver and prints the responses.
 	(Trac #703, git 1b666838b6c0fe265522b30971e878d9f0d21fde)
 
-219.    [func]          ocean
+219.	[func]		ocean
 	src/lib: move some dns related code out of asiolink library to
 	asiodns library
 	(Trac #751, git 262ac6c6fc61224d54705ed4c700dadb606fcb1c)
@@ -61,9 +77,9 @@
 	(Trac #806, git 4e47d5f6b692c63c907af6681a75024450884a88)
 
 217.	[bug]		jerry
-	src/lib/dns/python: Use a signed version of larger size of integer and
-	perform more strict range checks with PyArg_ParseTuple() in case of
-	overflows.
+	src/lib/dns/python: Use a signed version of larger size of
+	integer and perform more strict range checks with
+	PyArg_ParseTuple() in case of overflows.
 	(Trac #363, git ce281e646be9f0f273229d94ccd75bf7e08d17cf)
 
 216.	[func]		vorner
@@ -1085,7 +1101,7 @@ bind10-devel-20100701 released on July 1, 2010
 55.	[bug]		shane
 	bin/xfrout: xfrout exception on Ctrl-C now no longer generates
 	exception for 'Interrupted system call'
-	(Track #136, svn r2147)
+	(Trac #136, svn r2147)
 
 54.	[bug]		zhanglikun
 	bin/xfrout: Enable b10-xfrout can be launched in source

+ 12 - 2
configure.ac

@@ -410,7 +410,16 @@ if test -x "${BOTAN_CONFIG}" ; then
         for flag in ${BOTAN_LDFLAGS}; do
                 BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | sed -ne 's/^\(\-L\)/-R/p'`"
         done
-        AC_SUBST(BOTAN_RPATH)
+	AC_SUBST(BOTAN_RPATH)
+
+	# According to the libtool manual, it should be sufficient if we
+	# specify the "-R libdir" in our wrapper library of botan (no other
+	# programs will need libbotan directly); "libdir" should be added to
+	# the program's binary image.  But we've seen in our build environments
+	# that (some versions of?) libtool doesn't propagate -R as documented,
+	# and it caused a linker error at run time.  To work around this, we
+	# also add the rpath to the global LDFLAGS.
+        LDFLAGS="$BOTAN_RPATH $LDFLAGS"
     fi
 
     AC_SUBST(BOTAN_LDFLAGS)
@@ -419,7 +428,7 @@ fi
 
 CPPFLAGS_SAVED=$CPPFLAGS
 CPPFLAGS="$BOTAN_INCLUDES $CPPFLAGS"
-LDFLAGS_SAVED=$LDFLAGS
+LDFLAGS_SAVED="$LDFLAGS"
 LDFLAGS="$BOTAN_LDFLAGS $LDFLAGS"
 
 AC_CHECK_HEADERS([botan/botan.h],,AC_MSG_ERROR([Missing required header files.]))
@@ -878,6 +887,7 @@ Flags:
   DEFS:          $DEFS
   CPPFLAGS:      $CPPFLAGS
   CXXFLAGS:      $CXXFLAGS
+  LDFLAGS:       $LDFLAGS
   B10_CXXFLAGS:  $B10_CXXFLAGS
 dnl includes too
   Python:        ${PYTHON_INCLUDES}

+ 1 - 1
src/bin/bind10/run_bind10.sh.in

@@ -30,7 +30,7 @@ export PYTHONPATH
 # required by loadable python modules.
 SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
 if test $SET_ENV_LIBRARY_PATH = yes; then
-	@ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:$@ENV_LIBRARY_PATH@
+	@ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:$@ENV_LIBRARY_PATH@
 	export @ENV_LIBRARY_PATH@
 fi
 

+ 17 - 14
src/bin/host/Makefile.am

@@ -10,17 +10,20 @@ endif
 
 CLEANFILES = *.gcno *.gcda
 
-bin_PROGRAMS = host
-host_SOURCES = host.cc
-host_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
-host_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
-
-#man_MANS = host.1
-#EXTRA_DIST = $(man_MANS) host.xml
-#
-#if ENABLE_MAN
-#
-#host.1: host.xml
-#	xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/host.xml
-#
-#endif
+bin_PROGRAMS = b10-host
+b10_host_SOURCES = host.cc
+b10_host_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
+b10_host_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+
+man_MANS = b10-host.1
+EXTRA_DIST = $(man_MANS) b10-host.xml
+
+.PHONY: man
+if ENABLE_MAN
+
+man: b10-host.1
+
+b10-host.1: b10-host.xml
+	xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-host.xml
+
+endif

+ 3 - 13
src/bin/host/README

@@ -1,14 +1,4 @@
-Rewriting host(1) in C++ from scratch using BIND 10's libdns.
+Rewriting host(1) in C++ from scratch using BIND 10's libdns++.
 
-Initial functionality:
-
-  host _hostname_ [server]
-
-By default, it looks up the A, AAAA, and MX record sets.
-
-Note it doesn't use /etc/resolv.conf at this time.
-The default name server used is 127.0.0.1.
-
-  -r	 	disable recursive processing
-  -t _type_	specific query type 
-  -v		enable verbose output mode, including elapsed time
+The bugs and incompatibilities are listed in the manual page
+and in the source code.

+ 122 - 0
src/bin/host/b10-host.1

@@ -0,0 +1,122 @@
+'\" t
+.\"     Title: b10-host
+.\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
+.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
+.\"      Date: May 4, 2011
+.\"    Manual: BIND10
+.\"    Source: BIND10
+.\"  Language: English
+.\"
+.TH "B10\-HOST" "1" "May 4, 2011" "BIND10" "BIND10"
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+b10-host \- DNS lookup utility
+.SH "SYNOPSIS"
+.HP \w'\fBb10\-host\fR\ 'u
+\fBb10\-host\fR [\fB\-a\fR] [\fB\-c\ \fR\fB\fIclass\fR\fR] [\fB\-d\fR] [\fB\-p\ \fR\fB\fIport\fR\fR] [\fB\-r\fR] [\fB\-t\ \fR\fB\fItype\fR\fR] [\fB\-v\fR] [\fIname\fR] [\fB\fIserver\fR\fR]
+.SH "DESCRIPTION"
+.PP
+The
+\fBb10\-host\fR
+utility does DNS lookups\&. Its initial goal is to be a
+\fBhost\fR(1)
+clone, but also add a few features useful for BIND 10 development testing\&.
+.PP
+By default, it looks up the A, AAAA, and MX record sets for the
+\fIname\fR\&. Optionally, you may select a name server to query against by adding the
+\fIserver\fR
+argument\&.
+.SH "OPTIONS"
+.PP
+The arguments are as follows:
+.PP
+\fB\-a\fR
+.RS 4
+Enable verbose mode and do a query for type ANY\&. (If the
+\fB\-t\fR
+option is also set, then the ANY query is not done, but it still uses verbose mode\&.)
+.RE
+.PP
+\fB\-c \fR\fB\fIclass\fR\fR
+.RS 4
+Define the class for the query\&. The default is IN (Internet)\&.
+.RE
+.PP
+\fB\-d\fR
+.RS 4
+Enable verbose output mode, including elapsed time in milliseconds\&. Verbose mode shows the header, question, answer, authority, and additional sections (if provided)\&. (Same as
+\fB\-v\fR\&.)
+.RE
+.PP
+\fB\-p \fR\fB\fIport\fR\fR
+.RS 4
+Select an alternative port for the query\&. This may be a number or a service name\&. The default is 53 (domain)\&. This is not a standard feature of
+\fBhost\fR(1)\&.
+.RE
+.PP
+\fB\-r\fR
+.RS 4
+Disable recursive processing by not setting the Recursion Desired flag in the query\&.
+.RE
+.PP
+\fB\-t \fR\fB\fItype\fR\fR
+.RS 4
+Select a specific resource record type for the query\&. By default, it looks up the A, AAAA, and MX record sets\&.
+(This overrides the
+\fB\-a\fR
+option\&.)
+.RE
+.PP
+\fB\-v\fR
+.RS 4
+Same as
+\fB\-d\fR
+option\&.
+.RE
+.SH "COMPATIBILITY / BUGS"
+.PP
+
+\fBb10\-host\fR
+does not do reverse lookups by default yet (by detecting if name is a IPv4 or IPv6 address)\&.
+.PP
+Unknown
+\fB\-c\fR
+class or
+\fB\-t\fR
+type causes
+\fBb10\-host\fR
+to Abort\&.
+.PP
+Not all types are supported yet for formatting\&. Not all switches are supported yet\&.
+.PP
+It doesn\'t use
+/etc/resolv\&.conf
+at this time\&. The default name server used is 127\&.0\&.0\&.1\&.
+.PP
+
+\fBb10\-host\fR
+does not do reverse lookups by default yet (by detecting if name is a IPv4 or IPv6 address)\&.
+.PP
+
+\fB\-p\fR
+is not a standard feature\&.
+.SH "HISTORY"
+.PP
+The C++ version of
+\fBb10\-host\fR
+was started in October 2009 by Jeremy C\&. Reed of ISC\&. Its usage and output were based on the standard
+\fBhost\fR
+command\&.
+.SH "COPYRIGHT"
+.br
+Copyright \(co 2011 Internet Systems Consortium, Inc. ("ISC")
+.br

+ 201 - 0
src/bin/host/b10-host.xml

@@ -0,0 +1,201 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+               "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	       [<!ENTITY mdash "&#8212;">]>
+<!--
+ - 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.
+-->
+
+<!-- $Id$ -->
+<refentry>
+
+  <refentryinfo>
+    <date>May 4, 2011</date>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>b10-host</refentrytitle>
+    <manvolnum>1</manvolnum>
+    <refmiscinfo>BIND10</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>b10-host</refname>
+    <refpurpose>DNS lookup utility</refpurpose>
+  </refnamediv>
+
+  <docinfo>
+    <copyright>
+      <year>2011</year>
+      <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+    </copyright>
+  </docinfo>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>b10-host</command>
+      <arg><option>-a</option></arg>
+      <arg><option>-c <replaceable>class</replaceable></option></arg>
+      <arg><option>-d</option></arg>
+      <arg><option>-p <replaceable>port</replaceable></option></arg>
+      <arg><option>-r</option></arg>
+      <arg><option>-t <replaceable>type</replaceable></option></arg>
+      <arg><option>-v</option></arg>
+      <arg><replaceable>name</replaceable></arg>
+      <arg><option><replaceable>server</replaceable></option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>DESCRIPTION</title>
+    <para>
+      The <command>b10-host</command> utility does DNS lookups.
+      Its initial goal is to be a
+      <citerefentry><refentrytitle>host</refentrytitle>
+        <manvolnum>1</manvolnum></citerefentry>
+      clone, but also add a few features useful for BIND 10 development
+      testing.
+    </para>
+
+    <para>
+      By default, it looks up the A, AAAA, and MX record sets for the
+      <replaceable>name</replaceable>.
+      Optionally, you may select a name server to query against by adding
+      the <replaceable>server</replaceable> argument.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>OPTIONS</title>
+
+    <para>The arguments are as follows:</para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term><option>-a</option></term>
+        <listitem><para>
+          Enable verbose mode and do a query for type ANY.
+          (If the <option>-t</option> option is also set, then the
+          ANY query is not done, but it still uses verbose mode.)
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-c <replaceable>class</replaceable></option></term>
+        <listitem><para>
+          Define the class for the query.
+          The default is IN (Internet).
+<!-- TODO: bug if class is unknown causes seg fault and possible core dump -->
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-d</option></term>
+        <listitem><para>
+	  Enable verbose output mode, including elapsed time in
+	  milliseconds.
+          Verbose mode shows the header, question, answer, authority,
+          and additional sections (if provided).
+          (Same as <option>-v</option>.)
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-p <replaceable>port</replaceable></option></term>
+        <listitem><para>
+          Select an alternative port for the query.
+          This may be a number or a service name.
+          The default is 53 (domain).
+          This is not a standard feature of
+          <citerefentry><refentrytitle>host</refentrytitle>
+            <manvolnum>1</manvolnum></citerefentry>.
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-r</option></term>
+        <listitem><para>
+          Disable recursive processing by not setting the
+          Recursion Desired flag in the query.
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-t <replaceable>type</replaceable></option></term>
+        <listitem><para>
+          Select a specific resource record type for the query.
+          By default, it looks up the A, AAAA, and MX record sets.
+<!-- TODO: bug if class is unknown causes seg fault and possible core dump -->
+          (This overrides the <option>-a</option> option.)
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-v</option></term>
+        <listitem><para>
+	  Same as <option>-d</option> option.
+        </para></listitem>
+      </varlistentry>
+
+    </variablelist>
+
+  </refsect1>
+
+  <refsect1>
+    <title>COMPATIBILITY / BUGS</title>
+    <para>
+      <command>b10-host</command> does not do reverse lookups by
+      default yet (by detecting if name is a IPv4 or IPv6 address).
+    </para>
+
+    <para>
+      Unknown <option>-c</option> class or <option>-t</option> type
+      causes <command>b10-host</command> to Abort.
+    </para>
+
+    <para>
+      Not all types are supported yet for formatting.
+      Not all switches are supported yet.
+    </para>
+
+    <para>
+      It doesn't use <filename>/etc/resolv.conf</filename> at this time.
+      The default name server used is 127.0.0.1.
+    </para>
+
+    <para>
+      <command>b10-host</command> does not do reverse lookups by
+      default yet (by detecting if name is a IPv4 or IPv6 address).
+    </para>
+
+    <para>
+      <option>-p</option> is not a standard feature.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>HISTORY</title>
+    <para>
+      The C++ version of <command>b10-host</command> was started in
+      October 2009 by Jeremy C. Reed of ISC.
+      Its usage and output were based on the standard <command>host</command>
+      command.
+    </para>
+  </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->

+ 52 - 21
src/bin/host/host.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-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
@@ -44,13 +44,16 @@ namespace {
 char* dns_type = NULL;    // not set, so A, AAAA, MX
 const char* server = "127.0.0.1";
 const char* server_port = "53";
-int   verbose = 0;
-int   first_time = 1;
-bool  recursive_bit = true;
+const char* dns_class  = "IN";
+bool verbose = false;
+bool dns_any = false;
+int first_time = 1;
+bool recursive_bit = true;
 struct timeval before_time, after_time;
 
 int
-host_lookup(const char* const name, const char* const type) {
+host_lookup(const char* const name, const char* const dns_class,
+            const char* const type, bool any) {
 
     Message msg(Message::RENDER);
 
@@ -64,8 +67,8 @@ host_lookup(const char* const name, const char* const type) {
     }
 
     msg.addQuestion(Question(Name(name),
-                             RRClass::IN(),    // IN class only for now
-                             RRType(type)));  // if NULL then:
+                             RRClass(dns_class),
+                             any ? RRType::ANY() : RRType(type)));  // if NULL then:
 
     OutputBuffer obuffer(512);
     MessageRenderer renderer(obuffer);
@@ -127,18 +130,29 @@ host_lookup(const char* const name, const char* const type) {
 
             rmsg.fromWire(ibuffer);
             if (!verbose) {
+                string description = "";
                 for (RRsetIterator it =
                          rmsg.beginSection(Message::SECTION_ANSWER);
                      it != rmsg.endSection(Message::SECTION_ANSWER);
                      ++it) {
-                      if ((*it)->getType() != RRType::A()) {
-                          continue;
+
+                      if ((*it)->getType() == RRType::A()) {
+                          description = "has address";
+                      }
+                      else if ((*it)->getType() == RRType::AAAA()) {
+                          description = "has IPv6 address";
+                      }
+                      else if ((*it)->getType() == RRType::MX()) {
+                          description = "mail is handled by";
+                      }
+                      else if ((*it)->getType() == RRType::TXT()) {
+                          description = "descriptive text";
                       }
 
                       RdataIteratorPtr rit = (*it)->getRdataIterator();
                       for (; !rit->isLast(); rit->next()) {
                           // instead of using my name, maybe use returned label?
-                          cout << name << " has address " <<
+                          cout << name << " "  << description << " " <<
                               (*rit).getCurrent().toText() << endl;
                       }
                   }
@@ -159,13 +173,19 @@ host_lookup(const char* const name, const char* const type) {
 
                 // TODO: if NXDOMAIN, host(1) doesn't show HEADER
                 // Host hsdjkfhksjhdfkj not found: 3(NXDOMAIN)
-                // TODO: figure out the new libdns way to test if NXDOMAIN
+                // TODO: test if NXDOMAIN
 
                 std::cout << "Received " << cc <<
                     " bytes in " << elapsed_time << " ms\n";
                 // TODO: " bytes from 127.0.0.1#53 in 0 ms
 
             } //verbose
+/*
+TODO: handle InvalidRRClass
+TODO: handle invalid type exception
+        } catch (InvalidType ivt) {
+            std::cerr << "invalid type:" << ivt.what();
+*/
         } catch (const exception& ex) {
             std::cerr << "parse failed for " <<
                 string(name) << "/" << type << ": " << ex.what() << std::endl;
@@ -184,26 +204,36 @@ int
 main(int argc, char* argv[]) {
     int c;
 
-    while ((c = getopt(argc, argv, "p:rt:v")) != -1)
+    while ((c = getopt(argc, argv, "ac:dp:rt:v")) != -1)
         switch (c) {
+        case 'a':
+            dns_any = true;
+            verbose = true;
+            break;
+        case 'c':
+            dns_class = optarg;
+            break;
+	// p for port is a non-standard switch
+        case 'p':
+            server_port = optarg;
+            break;
         case 'r':
             recursive_bit = false;
             break;
         case 't':
             dns_type = optarg;
             break;
-        case 'p':
-            server_port = optarg;
-            break;
+        case 'd':
+            // drop through to v, because debug and verbose are equivalent
         case 'v':
-            verbose = 1;
+            verbose = true;
             break;
     }
     argc -= optind;
     argv += optind;
 
     if (argc < 1) {
-        cout << "Usage: host [-vr] [-t type] hostname [server]\n";
+        cout << "Usage: host [-adprv] [-c class] [-t type] hostname [server]\n";
         exit(1);
     }
 
@@ -212,12 +242,13 @@ main(int argc, char* argv[]) {
     }
 
     if (dns_type == NULL) {
-        host_lookup(argv[0], "A");
+        host_lookup(argv[0], dns_class, "A", dns_any);
         // TODO: don't do next if A doesn't exist
-        host_lookup(argv[0], "AAAA");
-        host_lookup(argv[0], "MX");
+        host_lookup(argv[0], dns_class, "AAAA", dns_any);
+        host_lookup(argv[0], dns_class, "MX", dns_any);
     } else {
-        host_lookup(argv[0], dns_type); 
+        // -t overrides -a, regardless of order
+        host_lookup(argv[0], dns_class, dns_type, false);
     }
     return (0);
 }

+ 1 - 1
src/bin/msgq/tests/msgq_test.py

@@ -132,7 +132,7 @@ class SendNonblock(unittest.TestCase):
             task()
             # If we got here, then everything worked well and in time
             # In that case, we terminate successfully
-            sys.exit(0)	# needs exit code
+            os._exit(0)	# needs exit code
         else:
             (pid, status) = os.waitpid(task_pid, 0)
             self.assertEqual(0, status,

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

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

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

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

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

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

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

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

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

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

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

@@ -25,6 +25,7 @@ EXTRA_DIST += rrttl_python.cc
 EXTRA_DIST += rdata_python.cc
 EXTRA_DIST += rrtype_python.cc
 EXTRA_DIST += tsigkey_python.cc
+EXTRA_DIST += tsig_python.cc
 
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # suffix for dynamic objects.  -module is necessary to work this around.

+ 64 - 59
src/lib/dns/python/message_python.cc

@@ -17,15 +17,16 @@
 using namespace isc::dns;
 using namespace isc::util;
 
+namespace {
 //
 // Declaration of the custom exceptions
 // Initialization and addition of these go in the initModulePart
 // function at the end of this file
 //
-static PyObject* po_MessageTooShort;
-static PyObject* po_InvalidMessageSection;
-static PyObject* po_InvalidMessageOperation;
-static PyObject* po_InvalidMessageUDPSize;
+PyObject* po_MessageTooShort;
+PyObject* po_InvalidMessageSection;
+PyObject* po_InvalidMessageOperation;
+PyObject* po_InvalidMessageUDPSize;
 
 //
 // Definition of the classes
@@ -36,10 +37,6 @@ static PyObject* po_InvalidMessageUDPSize;
 // and a type description
 
 //
-// Section
-//
-
-//
 // Message
 //
 
@@ -55,36 +52,36 @@ public:
 //
 
 // General creation and destruction
-static int Message_init(s_Message* self, PyObject* args);
-static void Message_destroy(s_Message* self);
-
-static PyObject* Message_getHeaderFlag(s_Message* self, PyObject* args);
-static PyObject* Message_setHeaderFlag(s_Message* self, PyObject* args);
-static PyObject* Message_getQid(s_Message* self);
-static PyObject* Message_setQid(s_Message* self, PyObject* args);
-static PyObject* Message_getRcode(s_Message* self);
-static PyObject* Message_setRcode(s_Message* self, PyObject* args);
-static PyObject* Message_getOpcode(s_Message* self);
-static PyObject* Message_setOpcode(s_Message* self, PyObject* args);
-static PyObject* Message_getEDNS(s_Message* self);
-static PyObject* Message_setEDNS(s_Message* self, PyObject* args);
-static PyObject* Message_getRRCount(s_Message* self, PyObject* args);
+int Message_init(s_Message* self, PyObject* args);
+void Message_destroy(s_Message* self);
+
+PyObject* Message_getHeaderFlag(s_Message* self, PyObject* args);
+PyObject* Message_setHeaderFlag(s_Message* self, PyObject* args);
+PyObject* Message_getQid(s_Message* self);
+PyObject* Message_setQid(s_Message* self, PyObject* args);
+PyObject* Message_getRcode(s_Message* self);
+PyObject* Message_setRcode(s_Message* self, PyObject* args);
+PyObject* Message_getOpcode(s_Message* self);
+PyObject* Message_setOpcode(s_Message* self, PyObject* args);
+PyObject* Message_getEDNS(s_Message* self);
+PyObject* Message_setEDNS(s_Message* self, PyObject* args);
+PyObject* Message_getRRCount(s_Message* self, PyObject* args);
 // use direct iterators for these? (or simply lists for now?)
-static PyObject* Message_getQuestion(s_Message* self);
-static PyObject* Message_getSection(s_Message* self, PyObject* args);
+PyObject* Message_getQuestion(s_Message* self);
+PyObject* Message_getSection(s_Message* self, PyObject* args);
 //static PyObject* Message_beginQuestion(s_Message* self, PyObject* args);
 //static PyObject* Message_endQuestion(s_Message* self, PyObject* args);
 //static PyObject* Message_beginSection(s_Message* self, PyObject* args);
 //static PyObject* Message_endSection(s_Message* self, PyObject* args);
 
-static PyObject* Message_addQuestion(s_Message* self, PyObject* args);
-static PyObject* Message_addRRset(s_Message* self, PyObject* args);
-static PyObject* Message_clear(s_Message* self, PyObject* args);
-static PyObject* Message_makeResponse(s_Message* self);
-static PyObject* Message_toText(s_Message* self);
-static PyObject* Message_str(PyObject* self);
-static PyObject* Message_toWire(s_Message* self, PyObject* args);
-static PyObject* Message_fromWire(s_Message* self, PyObject* args);
+PyObject* Message_addQuestion(s_Message* self, PyObject* args);
+PyObject* Message_addRRset(s_Message* self, PyObject* args);
+PyObject* Message_clear(s_Message* self, PyObject* args);
+PyObject* Message_makeResponse(s_Message* self);
+PyObject* Message_toText(s_Message* self);
+PyObject* Message_str(PyObject* self);
+PyObject* Message_toWire(s_Message* self, PyObject* args);
+PyObject* Message_fromWire(s_Message* self, PyObject* args);
 
 // This list contains the actual set of functions we have in
 // python. Each entry has
@@ -92,7 +89,7 @@ static PyObject* Message_fromWire(s_Message* self, PyObject* args);
 // 2. Our static function here
 // 3. Argument type
 // 4. Documentation
-static PyMethodDef Message_methods[] = {
+PyMethodDef Message_methods[] = {
     { "get_header_flag", reinterpret_cast<PyCFunction>(Message_getHeaderFlag),
       METH_VARARGS,
       "Return whether the specified header flag bit is set in the "
@@ -175,7 +172,7 @@ static PyMethodDef Message_methods[] = {
 // This defines the complete type for reflection in python and
 // parsing of PyObject* to s_Message
 // Most of the functions are not actually implemented and NULL here.
-static PyTypeObject message_type = {
+PyTypeObject message_type = {
     PyVarObject_HEAD_INIT(NULL, 0)
     "pydnspp.Message",
     sizeof(s_Message),                  // tp_basicsize
@@ -225,7 +222,7 @@ static PyTypeObject message_type = {
     0                                   // tp_version_tag
 };
 
-static int
+int
 Message_init(s_Message* self, PyObject* args) {
     int i;
 
@@ -248,14 +245,14 @@ Message_init(s_Message* self, PyObject* args) {
     return (-1);
 }
 
-static void
+void
 Message_destroy(s_Message* self) {
     delete self->message;
     self->message = NULL;
     Py_TYPE(self)->tp_free(self);
 }
 
-static PyObject*
+PyObject*
 Message_getHeaderFlag(s_Message* self, PyObject* args) {
     unsigned int messageflag;
     if (!PyArg_ParseTuple(args, "I", &messageflag)) {
@@ -273,7 +270,7 @@ Message_getHeaderFlag(s_Message* self, PyObject* args) {
     }
 }
 
-static PyObject*
+PyObject*
 Message_setHeaderFlag(s_Message* self, PyObject* args) {
     long messageflag;
     PyObject *on = Py_True;
@@ -304,12 +301,12 @@ Message_setHeaderFlag(s_Message* self, PyObject* args) {
     }
 }
 
-static PyObject*
+PyObject*
 Message_getQid(s_Message* self) {
     return (Py_BuildValue("I", self->message->getQid()));
 }
 
-static PyObject*
+PyObject*
 Message_setQid(s_Message* self, PyObject* args) {
     long id;
     if (!PyArg_ParseTuple(args, "l", &id)) {
@@ -333,7 +330,7 @@ Message_setQid(s_Message* self, PyObject* args) {
     }
 }
 
-static PyObject*
+PyObject*
 Message_getRcode(s_Message* self) {
     s_Rcode* rcode;
 
@@ -356,7 +353,7 @@ Message_getRcode(s_Message* self) {
     return (rcode);
 }
 
-static PyObject*
+PyObject*
 Message_setRcode(s_Message* self, PyObject* args) {
     s_Rcode* rcode;
     if (!PyArg_ParseTuple(args, "O!", &rcode_type, &rcode)) {
@@ -371,7 +368,7 @@ Message_setRcode(s_Message* self, PyObject* args) {
     }
 }
 
-static PyObject*
+PyObject*
 Message_getOpcode(s_Message* self) {
     s_Opcode* opcode;
 
@@ -394,7 +391,7 @@ Message_getOpcode(s_Message* self) {
     return (opcode);
 }
 
-static PyObject*
+PyObject*
 Message_setOpcode(s_Message* self, PyObject* args) {
     s_Opcode* opcode;
     if (!PyArg_ParseTuple(args, "O!", &opcode_type, &opcode)) {
@@ -409,7 +406,7 @@ Message_setOpcode(s_Message* self, PyObject* args) {
     }
 }
 
-static PyObject*
+PyObject*
 Message_getEDNS(s_Message* self) {
     s_EDNS* edns;
     EDNS* edns_body;
@@ -429,7 +426,7 @@ Message_getEDNS(s_Message* self) {
     return (edns);
 }
 
-static PyObject*
+PyObject*
 Message_setEDNS(s_Message* self, PyObject* args) {
     s_EDNS* edns;
     if (!PyArg_ParseTuple(args, "O!", &edns_type, &edns)) {
@@ -444,7 +441,7 @@ Message_setEDNS(s_Message* self, PyObject* args) {
     }
 }
 
-static PyObject*
+PyObject*
 Message_getRRCount(s_Message* self, PyObject* args) {
     unsigned int section;
     if (!PyArg_ParseTuple(args, "I", &section)) {
@@ -463,7 +460,7 @@ Message_getRRCount(s_Message* self, PyObject* args) {
 }
 
 // TODO use direct iterators for these? (or simply lists for now?)
-static PyObject*
+PyObject*
 Message_getQuestion(s_Message* self) {
     QuestionIterator qi, qi_end;
     try {
@@ -502,7 +499,7 @@ Message_getQuestion(s_Message* self) {
     return (list);
 }
 
-static PyObject*
+PyObject*
 Message_getSection(s_Message* self, PyObject* args) {
     unsigned int section;
     if (!PyArg_ParseTuple(args, "I", &section)) {
@@ -559,7 +556,7 @@ Message_getSection(s_Message* self, PyObject* args) {
 //static PyObject* Message_beginSection(s_Message* self, PyObject* args);
 //static PyObject* Message_endSection(s_Message* self, PyObject* args);
 //static PyObject* Message_addQuestion(s_Message* self, PyObject* args);
-static PyObject*
+PyObject*
 Message_addQuestion(s_Message* self, PyObject* args) {
     s_Question *question;
 
@@ -572,7 +569,7 @@ Message_addQuestion(s_Message* self, PyObject* args) {
     Py_RETURN_NONE;
 }
 
-static PyObject*
+PyObject*
 Message_addRRset(s_Message* self, PyObject* args) {
     PyObject *sign = Py_False;
     int section;
@@ -599,7 +596,7 @@ Message_addRRset(s_Message* self, PyObject* args) {
     }
 }
 
-static PyObject*
+PyObject*
 Message_clear(s_Message* self, PyObject* args) {
     int i;
     if (PyArg_ParseTuple(args, "i", &i)) {
@@ -620,13 +617,13 @@ Message_clear(s_Message* self, PyObject* args) {
     }
 }
 
-static PyObject*
+PyObject*
 Message_makeResponse(s_Message* self) {
     self->message->makeResponse();
     Py_RETURN_NONE;
 }
 
-static PyObject*
+PyObject*
 Message_toText(s_Message* self) {
     // Py_BuildValue makes python objects from native data
     try {
@@ -641,7 +638,7 @@ Message_toText(s_Message* self) {
     }
 }
 
-static PyObject*
+PyObject*
 Message_str(PyObject* self) {
     // Simply call the to_text method we already defined
     return (PyObject_CallMethod(self,
@@ -649,13 +646,20 @@ Message_str(PyObject* self) {
                                 const_cast<char*>("")));
 }
 
-static PyObject*
+PyObject*
 Message_toWire(s_Message* self, PyObject* args) {
     s_MessageRenderer* mr;
+    s_TSIGContext* tsig_ctx = NULL;
     
-    if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, &mr)) {
+    if (PyArg_ParseTuple(args, "O!|O!", &messagerenderer_type, &mr,
+                         &tsig_context_type, &tsig_ctx)) {
         try {
-            self->message->toWire(*mr->messagerenderer);
+            if (tsig_ctx == NULL) {
+                self->message->toWire(*mr->messagerenderer);
+            } else {
+                self->message->toWire(*mr->messagerenderer,
+                                      *tsig_ctx->tsig_ctx);
+            }
             // If we return NULL it is seen as an error, so use this for
             // None returns
             Py_RETURN_NONE;
@@ -671,7 +675,7 @@ Message_toWire(s_Message* self, PyObject* args) {
     return (NULL);
 }
 
-static PyObject*
+PyObject*
 Message_fromWire(s_Message* self, PyObject* args) {
     const char* b;
     Py_ssize_t len;
@@ -765,3 +769,4 @@ initModulePart_Message(PyObject* mod) {
 
     return (true);
 }
+} // end of unnamed namespace

+ 5 - 0
src/lib/dns/python/pydnspp.cc

@@ -56,6 +56,7 @@ static PyObject* po_DNSMessageBADVERS;
 #include <dns/python/question_python.cc>       // needs RRClass, RRType, RRTTL,
                                                // Name
 #include <dns/python/tsigkey_python.cc>        // needs Name
+#include <dns/python/tsig_python.cc>           // needs tsigkey
 #include <dns/python/opcode_python.cc>
 #include <dns/python/rcode_python.cc>
 #include <dns/python/edns_python.cc>           // needs Messagerenderer, Rcode
@@ -153,6 +154,10 @@ PyInit_pydnspp(void) {
         return (NULL);
     }
 
+    if (!initModulePart_TSIGContext(mod)) {
+        return (NULL);
+    }
+
     return (mod);
 }
 

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

@@ -11,6 +11,7 @@ PYTESTS += rrclass_python_test.py
 PYTESTS += rrset_python_test.py
 PYTESTS += rrttl_python_test.py
 PYTESTS += rrtype_python_test.py
+PYTESTS += tsig_python_test.py
 PYTESTS += tsigkey_python_test.py
 
 EXTRA_DIST = $(PYTESTS)

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

@@ -62,6 +62,12 @@ def create_message():
     message_render.add_rrset(Message.SECTION_ANSWER, rrset)
     return message_render
 
+def strip_mutable_tsig_data(data):
+    # Unfortunately we cannot easily compare TSIG RR because we can't tweak
+    # current time.  As a work around this helper function strips off the time
+    # dependent part of TSIG RDATA, i.e., the MAC (assuming HMAC-MD5) and
+    # Time Signed.
+    return data[0:-32] + data[-26:-22] + data[-6:]
 
 class MessageTest(unittest.TestCase):
 
@@ -81,6 +87,8 @@ class MessageTest(unittest.TestCase):
 
         self.bogus_section = Message.SECTION_ADDITIONAL + 1
         self.bogus_below_section = Message.SECTION_QUESTION - 1
+        self.tsig_key = TSIGKey("www.example.com:SFuWd/q99SzF8Yzd1QbB9g==")
+        self.tsig_ctx = TSIGContext(self.tsig_key)
 
     def test_init(self):
         self.assertRaises(TypeError, Message, -1)
@@ -277,12 +285,39 @@ class MessageTest(unittest.TestCase):
         self.assertRaises(InvalidMessageOperation, self.r.to_wire,
                           MessageRenderer())
 
+    def __common_tsigquery_setup(self):
+        self.r.set_opcode(Opcode.QUERY())
+        self.r.set_rcode(Rcode.NOERROR())
+        self.r.set_header_flag(Message.HEADERFLAG_RD)
+        self.r.add_question(Question(Name("www.example.com"),
+                                     RRClass("IN"), RRType("A")))
+
+    def __common_tsig_checks(self, expected_file):
+        renderer = MessageRenderer()
+        self.r.to_wire(renderer, self.tsig_ctx)
+        actual_wire = strip_mutable_tsig_data(renderer.get_data())
+        expected_wire = strip_mutable_tsig_data(read_wire_data(expected_file))
+        self.assertEqual(expected_wire, actual_wire)
+
+    def test_to_wire_with_tsig(self):
+        self.r.set_qid(0x2d65)
+        self.__common_tsigquery_setup()
+        self.__common_tsig_checks("message_toWire2.wire")
+
+    def test_to_wire_with_edns_tsig(self):
+        self.r.set_qid(0x6cd)
+        self.__common_tsigquery_setup()
+        edns = EDNS()
+        edns.set_udp_size(4096)
+        self.r.set_edns(edns)
+        self.__common_tsig_checks("message_toWire3.wire")
+
     def test_to_text(self):
         message_render = create_message()
         
         msg_str =\
 """;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4149
-;; flags: qr aa rd ; QUESTION: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
+;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
 
 ;; QUESTION SECTION:
 ;test.example.com. IN A

+ 29 - 0
src/lib/dns/python/tests/tsig_python_test.py

@@ -0,0 +1,29 @@
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM 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.
+
+import unittest
+from pydnspp import *
+
+class TSIGContextTest(unittest.TestCase):
+    tsig_key = TSIGKey('www.example.com:SFuWd/q99SzF8Yzd1QbB9g==')
+
+    def setUp(self):
+        # In the minimal implementation, we simply check constructing a
+        # TSIGContext doesn't cause any disruption.  We can add more tests
+        # later.
+        self.tsig_ctx = TSIGContext(self.tsig_key)
+
+if __name__ == '__main__':
+    unittest.main()

+ 157 - 0
src/lib/dns/python/tsig_python.cc

@@ -0,0 +1,157 @@
+// 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/tsig.h>
+
+using namespace isc::dns;
+
+//
+// Definition of the classes
+//
+
+// For each class, we need a struct, a helper functions (init, destroy,
+// and static wrappers around the methods we export), a list of methods,
+// and a type description
+
+namespace {
+// The s_* Class simply covers one instantiation of the object
+
+class s_TSIGContext : public PyObject {
+public:
+    TSIGContext* tsig_ctx;
+};
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+// General creation and destruction
+int TSIGContext_init(s_TSIGContext* self, PyObject* args);
+void TSIGContext_destroy(s_TSIGContext* self);
+
+// These are the functions we export
+// For a minimal support, we don't need them.
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef TSIGContext_methods[] = {
+    { NULL, NULL, 0, NULL }
+};
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_EDNS
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject tsig_context_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "libdns_python.TSIGContext",
+    sizeof(s_TSIGContext),              // tp_basicsize
+    0,                                  // tp_itemsize
+    (destructor)TSIGContext_destroy,    // tp_dealloc
+    NULL,                               // tp_print
+    NULL,                               // tp_getattr
+    NULL,                               // tp_setattr
+    NULL,                               // tp_reserved
+    NULL,                               // tp_repr
+    NULL,                               // tp_as_number
+    NULL,                               // tp_as_sequence
+    NULL,                               // tp_as_mapping
+    NULL,                               // tp_hash 
+    NULL,                               // tp_call
+    NULL,                               // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    "The TSIGContext class maintains a context of a signed session of "
+    "DNS transactions by TSIG.",
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    TSIGContext_methods,                // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,                               // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    (initproc)TSIGContext_init,         // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
+
+int
+TSIGContext_init(s_TSIGContext* self, PyObject* args) {
+    const s_TSIGKey* tsigkey_obj;
+
+    try {
+        if (PyArg_ParseTuple(args, "O!", &tsigkey_type, &tsigkey_obj)) {
+            self->tsig_ctx = new TSIGContext(*tsigkey_obj->tsigkey);
+            return (0);
+        }
+    } catch (...) {
+        PyErr_SetString(po_IscException, "Unexpected exception");
+        return (-1);
+    }
+
+    PyErr_Clear();
+    PyErr_SetString(PyExc_TypeError,
+                    "Invalid arguments to TSIGContext constructor");
+
+    return (-1);
+}
+
+void
+TSIGContext_destroy(s_TSIGContext* const self) {
+    delete self->tsig_ctx;
+    self->tsig_ctx = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_TSIGContext(PyObject* mod) {
+    // We initialize the static description object with PyType_Ready(),
+    // then add it to the module. This is not just a check! (leaving
+    // this out results in segmentation faults)
+    if (PyType_Ready(&tsig_context_type) < 0) {
+        return (false);
+    }
+    Py_INCREF(&tsig_context_type);
+    void* p = &tsig_context_type;
+    PyModule_AddObject(mod, "TSIGContext", static_cast<PyObject*>(p));
+
+    addClassVariable(tsig_context_type, "DEFAULT_FUDGE",
+                     Py_BuildValue("H", TSIGContext::DEFAULT_FUDGE));
+
+    return (true);
+}
+} // end of anonymous namespace

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 8 - 0
src/lib/dns/tests/testdata/message_toText2.txt

@@ -0,0 +1,8 @@
+;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 45981
+;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
+
+;; OPT PSEUDOSECTION:
+; EDNS: version: 0, flags: do; udp: 4096
+
+;; QUESTION SECTION:
+;example.com. IN A

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

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

+ 17 - 0
src/lib/dns/tests/testdata/message_toText3.txt

@@ -0,0 +1,17 @@
+;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 10140
+;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2
+
+;; QUESTION SECTION:
+;www.example.com. IN A
+
+;; ANSWER SECTION:
+www.example.com. 86400 IN A 192.0.2.80
+
+;; AUTHORITY SECTION:
+example.com. 86400 IN NS ns.example.com.
+
+;; ADDITIONAL SECTION:
+ns.example.com. 86400 IN A 192.0.2.1
+
+;; TSIG PSEUDOSECTION:
+www.example.com. 0 ANY TSIG hmac-md5.sig-alg.reg.int. 1304384318 300 16 UlfIA5by+pWyDHeummUvsg== 10140 NOERROR 0

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

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

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,160 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <vector>
+#include <sstream>
+
+#include <gtest/gtest.h>
+
+#include <util/buffer.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/tsig.h>
+#include <dns/tsigkey.h>
+#include <dns/tsigrecord.h>
+
+#include <dns/tests/unittest_util.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+
+namespace {
+class TSIGRecordTest : public ::testing::Test {
+protected:
+    TSIGRecordTest() :
+        test_name("www.example.com"), test_mac(16, 0xda),
+        test_rdata(any::TSIG(TSIGKey::HMACMD5_NAME(), 0x4da8877a,
+                             TSIGContext::DEFAULT_FUDGE,
+                             test_mac.size(), &test_mac[0],
+                             0x2d65, 0, 0, NULL)),
+        test_record(test_name, test_rdata),
+        buffer(0), renderer(buffer)
+    {}
+    const Name test_name;
+    vector<unsigned char> test_mac;
+    const any::TSIG test_rdata;
+    const TSIGRecord test_record;
+    OutputBuffer buffer;
+    MessageRenderer renderer;
+    vector<unsigned char> data;
+};
+
+TEST_F(TSIGRecordTest, getName) {
+    EXPECT_EQ(test_name, test_record.getName());
+}
+
+TEST_F(TSIGRecordTest, getLength) {
+    // 85 = 17 + 26 + 16 + 26
+    // len(www.example.com) = 17
+    // len(hmac-md5.sig-alg.reg.int) = 26
+    // len(MAC) = 16
+    // the rest are fixed length fields (26 in total)
+    EXPECT_EQ(85, test_record.getLength());
+}
+
+TEST_F(TSIGRecordTest, fromParams) {
+    // Construct the same TSIG RR as test_record from parameters.
+    // See the getLength test for the magic number of 85 (although it
+    // actually doesn't matter)
+    const TSIGRecord record(test_name, TSIGRecord::getClass(),
+                            TSIGRecord::getTTL(), test_rdata, 85);
+    // Perform straight sanity checks
+    EXPECT_EQ(test_name, record.getName());
+    EXPECT_EQ(85, record.getLength());
+    EXPECT_EQ(0, test_rdata.compare(record.getRdata()));
+
+    // The constructor doesn't check the length...
+    EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+                               TSIGRecord::getTTL(), test_rdata, 82));
+    // ...even for impossibly small values...
+    EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+                               TSIGRecord::getTTL(), test_rdata, 1));
+    // ...or too large values.
+    EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+                               TSIGRecord::getTTL(), test_rdata, 65536));
+
+    // RDATA must indeed be TSIG
+    EXPECT_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+                            TSIGRecord::getTTL(), in::A("192.0.2.1"), 85),
+                 DNSMessageFORMERR);
+
+    // Unexpected class
+    EXPECT_THROW(TSIGRecord(test_name, RRClass::IN(), TSIGRecord::getTTL(),
+                            test_rdata, 85),
+                 DNSMessageFORMERR);
+
+    // Unexpected TTL (simply ignored)
+    EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+                               RRTTL(3600), test_rdata, 85));
+}
+
+TEST_F(TSIGRecordTest, recordToWire) {
+    UnitTestUtil::readWireData("tsigrecord_toWire1.wire", data);
+    EXPECT_EQ(1, test_record.toWire(renderer));
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        renderer.getData(), renderer.getLength(),
+                        &data[0], data.size());
+
+    // Same test for a dumb buffer
+    buffer.clear();
+    EXPECT_EQ(1, test_record.toWire(buffer));
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        buffer.getData(), buffer.getLength(),
+                        &data[0], data.size());
+}
+
+TEST_F(TSIGRecordTest, recordToOLongToWire) {
+    // By setting the limit to "record length - 1", it will fail, and the
+    // renderer will be marked as "truncated".
+    renderer.setLengthLimit(test_record.getLength() - 1);
+    EXPECT_FALSE(renderer.isTruncated()); // not marked before render attempt
+    EXPECT_EQ(0, test_record.toWire(renderer));
+    EXPECT_TRUE(renderer.isTruncated());
+}
+
+TEST_F(TSIGRecordTest, recordToWireAfterNames) {
+    // A similar test but the TSIG RR follows some domain names that could
+    // cause name compression inside TSIG.  Our implementation shouldn't
+    // compress either owner (key) name or the algorithm name.  This test
+    // confirms that.
+
+    UnitTestUtil::readWireData("tsigrecord_toWire2.wire", data);
+    renderer.writeName(TSIGKey::HMACMD5_NAME());
+    renderer.writeName(Name("foo.example.com"));
+    EXPECT_EQ(1, test_record.toWire(renderer));
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        renderer.getData(), renderer.getLength(),
+                        &data[0], data.size());
+}
+
+TEST_F(TSIGRecordTest, toText) {
+    EXPECT_EQ("www.example.com. 0 ANY TSIG hmac-md5.sig-alg.reg.int. "
+              "1302890362 300 16 2tra2tra2tra2tra2tra2g== 11621 NOERROR 0\n",
+              test_record.toText());
+}
+
+// test operator<<.  We simply confirm it appends the result of toText().
+TEST_F(TSIGRecordTest, LeftShiftOperator) {
+    ostringstream oss;
+    oss << test_record;
+    EXPECT_EQ(test_record.toText(), oss.str());
+}
+} // end namespace

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

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

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

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

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

@@ -0,0 +1,145 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <ostream>
+#include <string>
+
+#include <util/buffer.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/tsigrecord.h>
+
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+namespace {
+// Internally used constants:
+
+// Size in octets for the RR type, class TTL, RDLEN fields.
+const size_t RR_COMMON_LEN = 10;
+
+// Size in octets for the fixed part of TSIG RDATAs.
+// - Time Signed (6)
+// - Fudge (2)
+// - MAC Size (2)
+// - Original ID (2)
+// - Error (2)
+// - Other Len (2)
+const size_t RDATA_COMMON_LEN = 16;
+}
+
+namespace isc {
+namespace dns {
+TSIGRecord::TSIGRecord(const Name& key_name,
+                       const rdata::any::TSIG& tsig_rdata) :
+    key_name_(key_name), rdata_(tsig_rdata),
+    length_(RR_COMMON_LEN + RDATA_COMMON_LEN + key_name_.getLength() +
+            rdata_.getAlgorithm().getLength() +
+            rdata_.getMACSize() + rdata_.getOtherLen())
+{}
+
+namespace {
+// This is a straightforward wrapper of dynamic_cast<const any::TSIG&>.
+// We use this so that we can throw the DNSMessageFORMERR exception when
+// unexpected type of RDATA is detected in the member initialization list
+// of the constructor below.
+const any::TSIG&
+castToTSIGRdata(const rdata::Rdata& rdata) {
+    try {
+        return (dynamic_cast<const any::TSIG&>(rdata));
+    } catch (std::bad_cast&) {
+        isc_throw(DNSMessageFORMERR,
+                  "TSIG record is being constructed from "
+                  "incompatible RDATA:" << rdata.toText());
+    }
+}
+}
+
+TSIGRecord::TSIGRecord(const Name& name, const RRClass& rrclass,
+                       const RRTTL&, // we ignore TTL
+                       const rdata::Rdata& rdata,
+                       size_t length) :
+    key_name_(name), rdata_(castToTSIGRdata(rdata)), length_(length)
+{
+    if (rrclass != getClass()) {
+        isc_throw(DNSMessageFORMERR, "Unexpected TSIG RR class: " << rrclass);
+    }
+}
+
+const RRClass&
+TSIGRecord::getClass() {
+    return (RRClass::ANY());
+}
+
+const RRTTL&
+TSIGRecord::getTTL() {
+    static RRTTL ttl(TSIG_TTL);
+    return (ttl);
+}
+
+namespace {
+template <typename OUTPUT>
+void
+toWireCommon(OUTPUT& output, const rdata::any::TSIG& rdata) {
+    // RR type, class, TTL are fixed constants.
+    RRType::TSIG().toWire(output);
+    TSIGRecord::getClass().toWire(output);
+    output.writeUint32(TSIGRecord::TSIG_TTL);
+
+    // RDLEN
+    output.writeUint16(RDATA_COMMON_LEN + rdata.getAlgorithm().getLength() +
+                       rdata.getMACSize() + rdata.getOtherLen());
+
+    // TSIG RDATA
+    rdata.toWire(output);
+}
+}
+
+int
+TSIGRecord::toWire(AbstractMessageRenderer& renderer) const {
+    // If adding the TSIG would exceed the size limit, don't do it.
+    if (renderer.getLength() + length_ > renderer.getLengthLimit()) {
+        renderer.setTruncated();
+        return (0);
+    }
+
+    // key name = owner.  note that we disable compression.
+    renderer.writeName(key_name_, false);
+    toWireCommon(renderer, rdata_);
+    return (1);
+}
+
+int
+TSIGRecord::toWire(OutputBuffer& buffer) const {
+    key_name_.toWire(buffer);
+    toWireCommon(buffer, rdata_);
+    return (1);
+}
+
+std::string
+TSIGRecord::toText() const {
+    return (key_name_.toText() + " " + RRTTL(TSIG_TTL).toText() + " " +
+            getClass().toText() + " " + RRType::TSIG().toText() + " " +
+            rdata_.toText() + "\n");
+}
+
+std::ostream&
+operator<<(std::ostream& os, const TSIGRecord& record) {
+    return (os << record.toText());
+}
+} // namespace dns
+} // namespace isc

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

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

+ 1 - 1
src/lib/util/encode/base_n.cc

@@ -138,7 +138,7 @@ private:
 // DecodeNormalizer is an input iterator intended to be used as a filter
 // between the encoded baseX stream and binary_from_baseXX.
 // A DecodeNormalizer object is configured with three string iterators
-// (base, base_beinpad, and base_beginpad), specifying the head of the string,
+// (base, base_beginpad, and base_end), specifying the head of the string,
 // the beginning position of baseX padding (when there's padding), and
 // end of the string, respectively.  It internally iterators over the original
 // stream, and return each character of the encoded string via its dereference

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

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

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

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

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

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

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

@@ -0,0 +1,61 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string>
+#include <stdexcept>
+#include <fstream>
+#include <vector>
+
+#include "testdata.h"
+
+using namespace std;
+
+namespace {
+vector<string>&
+getDataPaths() {
+    static vector<string> data_path;
+    return (data_path);
+}
+}
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+void
+addTestDataPath(const string& path) {
+    getDataPaths().push_back(path);
+}
+
+void
+openTestData(const char* const datafile, ifstream& ifs) {
+    vector<string>::const_iterator it = getDataPaths().begin();
+    for (; it != getDataPaths().end(); ++it) {
+        string data_path = *it;
+        if (data_path.empty() || *data_path.rbegin() != '/') {
+            data_path.push_back('/');
+        }
+        ifs.open((data_path + datafile).c_str(), ios_base::in);
+        if (!ifs.fail()) {
+            return;
+        }
+    }
+
+    throw runtime_error("failed to open data file in data paths: " +
+                        string(datafile));
+}
+
+}
+}
+}

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

@@ -0,0 +1,46 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __UTIL_UNITTESTS_TESTDATA_H
+#define __UTIL_UNITTESTS_TESTDATA_H 1
+
+/**
+ * @file testdata.h
+ * @short Manipulating test data files.
+ *
+ * This utility defines functions that help test case handle test data
+ * stored in a file.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+/// Add a path (directory) that \c openTestData() will search for test data
+/// files.
+void addTestDataPath(const std::string& path);
+
+/// Open a file specified by 'datafile' using the data paths registered via
+/// addTestDataPath().  On success, ifs will be ready for reading the data
+/// stored in 'datafile'.  If the data file cannot be open with any of the
+/// registered paths, a runtime_error exception will be thrown.
+void openTestData(const char* const datafile, std::ifstream& ifs);
+}
+}
+}
+
+#endif // __UTIL_UNITTESTS_TESTDATA_H
+
+// Local Variables:
+// mode: c++
+// End:

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

@@ -0,0 +1,103 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <istream>
+#include <string>
+#include <sstream>
+
+#include <gtest/gtest.h>
+
+#ifndef __UTIL_UNITTESTS_TEXTDATA_H
+#define __UTIL_UNITTESTS_TEXTDATA_H 1
+
+/**
+ * @file textdata.h
+ * @short Utilities for tests with text data.
+ *
+ * This utility provides convenient helper functions for unit tests using
+ * textual data.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// Line-by-line text comparison.
+///
+/// This templated function takes two standard input streams, extracts
+/// strings from them, and compares the two sets of strings line by line.
+template <typename EXPECTED_STREAM, typename ACTUAL_STREAM>
+void
+matchTextData(EXPECTED_STREAM& expected, ACTUAL_STREAM& actual) {
+    std::string actual_line;
+    std::string expected_line;
+    while (std::getline(actual, actual_line), !actual.eof()) {
+        std::getline(expected, expected_line);
+        if (expected.eof()) {
+            FAIL() << "Redundant line in actual output: " << actual_line;
+            break;
+        }
+        if (actual.bad() || actual.fail() ||
+            expected.bad() || expected.fail()) {
+            throw std::runtime_error("Unexpected error in data streams");
+        }
+        EXPECT_EQ(expected_line, actual_line);
+    }
+    while (std::getline(expected, expected_line), !expected.eof()) {
+        ADD_FAILURE() << "Missing line in actual output: " << expected_line;
+    }
+}
+
+/// Similar to the fully templated version, but takes string for the second
+/// (actual) data.
+///
+/// Due to the nature of textual data, it will often be the case that test
+/// data is given as a string object.  This shortcut version helps such cases
+/// so that the test code doesn't have to create a string stream with the
+/// string data just for testing.
+template <typename EXPECTED_STREAM>
+void
+matchTextData(EXPECTED_STREAM& expected, const std::string& actual_text) {
+    std::istringstream iss(actual_text);
+    matchTextData(expected, iss);
+}
+
+/// Same for the previous version, but the first argument is string.
+template <typename ACTUAL_STREAM>
+void
+matchTextData(const std::string& expected_text, ACTUAL_STREAM& actual) {
+    std::istringstream iss(expected_text);
+    matchTextData(iss, actual);
+}
+
+/// Same for the previous two, but takes strings for both expected and
+/// actual data.
+void
+matchTextData(const std::string& expected_text,
+              const std::string& actual_text)
+{
+    std::istringstream expected_is(expected_text);
+    std::istringstream actual_is(actual_text);
+    matchTextData(expected_is, actual_is);
+}
+
+}
+}
+}
+
+#endif // __UTIL_UNITTESTS_TEXTDATA_H
+
+// Local Variables:
+// mode: c++
+// End: