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
 227.	[build]		jreed
 	Add missing libdns++ rdata files for the distribution (this
 	Add missing libdns++ rdata files for the distribution (this
 	fixes distcheck error). Change three generated libdns++
 	fixes distcheck error). Change three generated libdns++
@@ -10,17 +23,19 @@
 	implementation uses Botan as the backend library.
 	implementation uses Botan as the backend library.
 	This introduces a new dependency, on Botan.  Currently only Botan
 	This introduces a new dependency, on Botan.  Currently only Botan
 	1.8.x works; older or newer versions don't.
 	1.8.x works; older or newer versions don't.
-	(Trac#781, git 9df42279a47eb617f586144dce8cce680598558a)
+	(Trac #781, git 9df42279a47eb617f586144dce8cce680598558a)
 
 
 225.	[func]		naokikambe
 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)
 	(Trac #547, git 1cbd51919237a6e65983be46e4f5a63d1877b1d3)
 
 
 224.	[bug]		jinmei
 224.	[bug]		jinmei
@@ -32,11 +47,12 @@
 	(Trac #851, git 2463b96680bb3e9a76e50c38a4d7f1d38d810643)
 	(Trac #851, git 2463b96680bb3e9a76e50c38a4d7f1d38d810643)
 
 
 223.	[bug]		feng
 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)
 	(Trac #775, git 572ac2cf62e18f7eb69d670b890e2a3443bfd6e7)
 
 
-222.	[bug] 		jerry
+222.	[bug]		jerry
 	src/lib/zonemgr: Fix a bug that xfrin not checking for new copy of
 	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
 	zone on startup.  Imposes some random jitters to avoid many zones
 	need to do refresh at the same time.
 	need to do refresh at the same time.
@@ -51,7 +67,7 @@
 	(potentially) bad packets to a nameserver and prints the responses.
 	(potentially) bad packets to a nameserver and prints the responses.
 	(Trac #703, git 1b666838b6c0fe265522b30971e878d9f0d21fde)
 	(Trac #703, git 1b666838b6c0fe265522b30971e878d9f0d21fde)
 
 
-219.    [func]          ocean
+219.	[func]		ocean
 	src/lib: move some dns related code out of asiolink library to
 	src/lib: move some dns related code out of asiolink library to
 	asiodns library
 	asiodns library
 	(Trac #751, git 262ac6c6fc61224d54705ed4c700dadb606fcb1c)
 	(Trac #751, git 262ac6c6fc61224d54705ed4c700dadb606fcb1c)
@@ -61,9 +77,9 @@
 	(Trac #806, git 4e47d5f6b692c63c907af6681a75024450884a88)
 	(Trac #806, git 4e47d5f6b692c63c907af6681a75024450884a88)
 
 
 217.	[bug]		jerry
 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)
 	(Trac #363, git ce281e646be9f0f273229d94ccd75bf7e08d17cf)
 
 
 216.	[func]		vorner
 216.	[func]		vorner
@@ -1085,7 +1101,7 @@ bind10-devel-20100701 released on July 1, 2010
 55.	[bug]		shane
 55.	[bug]		shane
 	bin/xfrout: xfrout exception on Ctrl-C now no longer generates
 	bin/xfrout: xfrout exception on Ctrl-C now no longer generates
 	exception for 'Interrupted system call'
 	exception for 'Interrupted system call'
-	(Track #136, svn r2147)
+	(Trac #136, svn r2147)
 
 
 54.	[bug]		zhanglikun
 54.	[bug]		zhanglikun
 	bin/xfrout: Enable b10-xfrout can be launched in source
 	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
         for flag in ${BOTAN_LDFLAGS}; do
                 BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | sed -ne 's/^\(\-L\)/-R/p'`"
                 BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | sed -ne 's/^\(\-L\)/-R/p'`"
         done
         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
     fi
 
 
     AC_SUBST(BOTAN_LDFLAGS)
     AC_SUBST(BOTAN_LDFLAGS)
@@ -419,7 +428,7 @@ fi
 
 
 CPPFLAGS_SAVED=$CPPFLAGS
 CPPFLAGS_SAVED=$CPPFLAGS
 CPPFLAGS="$BOTAN_INCLUDES $CPPFLAGS"
 CPPFLAGS="$BOTAN_INCLUDES $CPPFLAGS"
-LDFLAGS_SAVED=$LDFLAGS
+LDFLAGS_SAVED="$LDFLAGS"
 LDFLAGS="$BOTAN_LDFLAGS $LDFLAGS"
 LDFLAGS="$BOTAN_LDFLAGS $LDFLAGS"
 
 
 AC_CHECK_HEADERS([botan/botan.h],,AC_MSG_ERROR([Missing required header files.]))
 AC_CHECK_HEADERS([botan/botan.h],,AC_MSG_ERROR([Missing required header files.]))
@@ -878,6 +887,7 @@ Flags:
   DEFS:          $DEFS
   DEFS:          $DEFS
   CPPFLAGS:      $CPPFLAGS
   CPPFLAGS:      $CPPFLAGS
   CXXFLAGS:      $CXXFLAGS
   CXXFLAGS:      $CXXFLAGS
+  LDFLAGS:       $LDFLAGS
   B10_CXXFLAGS:  $B10_CXXFLAGS
   B10_CXXFLAGS:  $B10_CXXFLAGS
 dnl includes too
 dnl includes too
   Python:        ${PYTHON_INCLUDES}
   Python:        ${PYTHON_INCLUDES}

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

@@ -30,7 +30,7 @@ export PYTHONPATH
 # required by loadable python modules.
 # required by loadable python modules.
 SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
 SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
 if test $SET_ENV_LIBRARY_PATH = yes; then
 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@
 	export @ENV_LIBRARY_PATH@
 fi
 fi
 
 

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

@@ -10,17 +10,20 @@ endif
 
 
 CLEANFILES = *.gcno *.gcda
 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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // 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
 char* dns_type = NULL;    // not set, so A, AAAA, MX
 const char* server = "127.0.0.1";
 const char* server = "127.0.0.1";
 const char* server_port = "53";
 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;
 struct timeval before_time, after_time;
 
 
 int
 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);
     Message msg(Message::RENDER);
 
 
@@ -64,8 +67,8 @@ host_lookup(const char* const name, const char* const type) {
     }
     }
 
 
     msg.addQuestion(Question(Name(name),
     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);
     OutputBuffer obuffer(512);
     MessageRenderer renderer(obuffer);
     MessageRenderer renderer(obuffer);
@@ -127,18 +130,29 @@ host_lookup(const char* const name, const char* const type) {
 
 
             rmsg.fromWire(ibuffer);
             rmsg.fromWire(ibuffer);
             if (!verbose) {
             if (!verbose) {
+                string description = "";
                 for (RRsetIterator it =
                 for (RRsetIterator it =
                          rmsg.beginSection(Message::SECTION_ANSWER);
                          rmsg.beginSection(Message::SECTION_ANSWER);
                      it != rmsg.endSection(Message::SECTION_ANSWER);
                      it != rmsg.endSection(Message::SECTION_ANSWER);
                      ++it) {
                      ++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();
                       RdataIteratorPtr rit = (*it)->getRdataIterator();
                       for (; !rit->isLast(); rit->next()) {
                       for (; !rit->isLast(); rit->next()) {
                           // instead of using my name, maybe use returned label?
                           // instead of using my name, maybe use returned label?
-                          cout << name << " has address " <<
+                          cout << name << " "  << description << " " <<
                               (*rit).getCurrent().toText() << endl;
                               (*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
                 // TODO: if NXDOMAIN, host(1) doesn't show HEADER
                 // Host hsdjkfhksjhdfkj not found: 3(NXDOMAIN)
                 // 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 <<
                 std::cout << "Received " << cc <<
                     " bytes in " << elapsed_time << " ms\n";
                     " bytes in " << elapsed_time << " ms\n";
                 // TODO: " bytes from 127.0.0.1#53 in 0 ms
                 // TODO: " bytes from 127.0.0.1#53 in 0 ms
 
 
             } //verbose
             } //verbose
+/*
+TODO: handle InvalidRRClass
+TODO: handle invalid type exception
+        } catch (InvalidType ivt) {
+            std::cerr << "invalid type:" << ivt.what();
+*/
         } catch (const exception& ex) {
         } catch (const exception& ex) {
             std::cerr << "parse failed for " <<
             std::cerr << "parse failed for " <<
                 string(name) << "/" << type << ": " << ex.what() << std::endl;
                 string(name) << "/" << type << ": " << ex.what() << std::endl;
@@ -184,26 +204,36 @@ int
 main(int argc, char* argv[]) {
 main(int argc, char* argv[]) {
     int c;
     int c;
 
 
-    while ((c = getopt(argc, argv, "p:rt:v")) != -1)
+    while ((c = getopt(argc, argv, "ac:dp:rt:v")) != -1)
         switch (c) {
         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':
         case 'r':
             recursive_bit = false;
             recursive_bit = false;
             break;
             break;
         case 't':
         case 't':
             dns_type = optarg;
             dns_type = optarg;
             break;
             break;
-        case 'p':
-            server_port = optarg;
-            break;
+        case 'd':
+            // drop through to v, because debug and verbose are equivalent
         case 'v':
         case 'v':
-            verbose = 1;
+            verbose = true;
             break;
             break;
     }
     }
     argc -= optind;
     argc -= optind;
     argv += optind;
     argv += optind;
 
 
     if (argc < 1) {
     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);
         exit(1);
     }
     }
 
 
@@ -212,12 +242,13 @@ main(int argc, char* argv[]) {
     }
     }
 
 
     if (dns_type == NULL) {
     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
         // 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 {
     } else {
-        host_lookup(argv[0], dns_type); 
+        // -t overrides -a, regardless of order
+        host_lookup(argv[0], dns_class, dns_type, false);
     }
     }
     return (0);
     return (0);
 }
 }

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

@@ -132,7 +132,7 @@ class SendNonblock(unittest.TestCase):
             task()
             task()
             # If we got here, then everything worked well and in time
             # If we got here, then everything worked well and in time
             # In that case, we terminate successfully
             # In that case, we terminate successfully
-            sys.exit(0)	# needs exit code
+            os._exit(0)	# needs exit code
         else:
         else:
             (pid, status) = os.waitpid(task_pid, 0)
             (pid, status) = os.waitpid(task_pid, 0)
             self.assertEqual(0, status,
             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 += tsig.h tsig.cc
 libdns___la_SOURCES += tsigerror.h tsigerror.cc
 libdns___la_SOURCES += tsigerror.h tsigerror.cc
 libdns___la_SOURCES += tsigkey.h tsigkey.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.h
 libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
 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);
     return (ret);
 }
 }
 
 
+namespace {
+/// Helper function to define unified implementation for the public versions
+/// of toWire().
 template <typename Output>
 template <typename Output>
 int
 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
     // Render EDNS OPT RR
     uint32_t extrcode_flags = extended_rcode << EXTRCODE_SHIFT;
     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;
         extrcode_flags |= EXTFLAG_DO;
     }
     }
 
 
     // Construct an RRset corresponding to the EDNS.
     // Construct an RRset corresponding to the EDNS.
     // We don't support any options for now, so the OPT RR can be empty.
     // 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)));
                                   RRType::OPT(), RRTTL(extrcode_flags)));
     edns_rrset->addRdata(ConstRdataPtr(new generic::OPT()));
     edns_rrset->addRdata(ConstRdataPtr(new generic::OPT()));
 
 
@@ -130,9 +136,12 @@ EDNS::toWire(Output& output, const uint8_t extended_rcode) const {
 
 
     return (1);
     return (1);
 }
 }
+}
 
 
 unsigned int
 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.
     // If adding the OPT RR would exceed the size limit, don't do it.
     // 11 = len(".") + type(2byte) + class(2byte) + TTL(4byte) + RDLEN(2byte)
     // 11 = len(".") + type(2byte) + class(2byte) + TTL(4byte) + RDLEN(2byte)
     // (RDATA is empty in this simple implementation)
     // (RDATA is empty in this simple implementation)
@@ -140,12 +149,16 @@ EDNS::toWire(MessageRenderer& renderer, const uint8_t extended_rcode) const {
         return (0);
         return (0);
     }
     }
 
 
-    return (toWire<MessageRenderer>(renderer, extended_rcode));
+    return (toWireCommon(renderer, version_, udp_size_, dnssec_aware_,
+                         extended_rcode));
 }
 }
 
 
 unsigned int
 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*
 EDNS*

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

@@ -32,7 +32,7 @@ namespace dns {
 
 
 class EDNS;
 class EDNS;
 class Name;
 class Name;
-class MessageRenderer;
+class AbstractMessageRenderer;
 class RRClass;
 class RRClass;
 class RRTTL;
 class RRTTL;
 class RRType;
 class RRType;
@@ -314,7 +314,7 @@ public:
     /// \param extended_rcode Upper 8 bits of extended RCODE to be rendered as
     /// \param extended_rcode Upper 8 bits of extended RCODE to be rendered as
     /// part of the EDNS OPT RR.
     /// part of the EDNS OPT RR.
     /// \return 1 if the OPT RR fits in the message size limit; otherwise 0.
     /// \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;
                         const uint8_t extended_rcode) const;
 
 
     /// \brief Render the \c EDNS in the wire format.
     /// \brief Render the \c EDNS in the wire format.
@@ -354,12 +354,6 @@ public:
     // something like this.
     // something like this.
     //void addOption();
     //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:
 public:
     /// \brief The highest EDNS version this implementation supports.
     /// \brief The highest EDNS version this implementation supports.
     static const uint8_t SUPPORTED_VERSION = 0;
     static const uint8_t SUPPORTED_VERSION = 0;

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

@@ -15,6 +15,7 @@
 #include <stdint.h>
 #include <stdint.h>
 
 
 #include <algorithm>
 #include <algorithm>
+#include <cassert>
 #include <string>
 #include <string>
 #include <sstream>
 #include <sstream>
 #include <vector>
 #include <vector>
@@ -40,6 +41,7 @@
 #include <dns/rrtype.h>
 #include <dns/rrtype.h>
 #include <dns/rrttl.h>
 #include <dns/rrttl.h>
 #include <dns/rrset.h>
 #include <dns/rrset.h>
+#include <dns/tsig.h>
 
 
 using namespace std;
 using namespace std;
 using namespace boost;
 using namespace boost;
@@ -81,7 +83,7 @@ const unsigned int HEADERFLAG_MASK = 0x87b0;
 const uint16_t MESSAGE_REPLYPRESERVE = (Message::HEADERFLAG_RD |
 const uint16_t MESSAGE_REPLYPRESERVE = (Message::HEADERFLAG_RD |
                                         Message::HEADERFLAG_CD);
                                         Message::HEADERFLAG_CD);
 
 
-const char *sectiontext[] = {
+const char* const sectiontext[] = {
     "QUESTION",
     "QUESTION",
     "ANSWER",
     "ANSWER",
     "AUTHORITY",
     "AUTHORITY",
@@ -114,8 +116,8 @@ public:
     vector<QuestionPtr> questions_;
     vector<QuestionPtr> questions_;
     vector<RRsetPtr> rrsets_[NUM_SECTIONS];
     vector<RRsetPtr> rrsets_[NUM_SECTIONS];
     ConstEDNSPtr edns_;
     ConstEDNSPtr edns_;
+    ConstTSIGRecordPtr tsig_rr_;
 
 
-    // tsig/sig0: TODO
     // RRsetsSorter* sorter_; : TODO
     // RRsetsSorter* sorter_; : TODO
 
 
     void init();
     void init();
@@ -123,6 +125,17 @@ public:
     void setRcode(const Rcode& rcode);
     void setRcode(const Rcode& rcode);
     int parseQuestion(InputBuffer& buffer);
     int parseQuestion(InputBuffer& buffer);
     int parseSection(const Message::Section section, 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) :
 MessageImpl::MessageImpl(Message::Mode mode) :
@@ -140,6 +153,7 @@ MessageImpl::init() {
     rcode_ = NULL;
     rcode_ = NULL;
     opcode_ = NULL;
     opcode_ = NULL;
     edns_ = EDNSPtr();
     edns_ = EDNSPtr();
+    tsig_rr_ = ConstTSIGRecordPtr();
 
 
     for (int i = 0; i < NUM_SECTIONS; ++i) {
     for (int i = 0; i < NUM_SECTIONS; ++i) {
         counts_[i] = 0;
         counts_[i] = 0;
@@ -164,6 +178,154 @@ MessageImpl::setRcode(const Rcode& rcode) {
     rcode_ = &rcode_placeholder_;
     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) :
 Message::Message(Mode mode) :
     impl_(new MessageImpl(mode))
     impl_(new MessageImpl(mode))
 {}
 {}
@@ -262,6 +424,16 @@ Message::setEDNS(ConstEDNSPtr edns) {
     impl_->edns_ = 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
 unsigned int
 Message::getRRCount(const Section section) const {
 Message::getRRCount(const Section section) const {
     if (section >= MessageImpl::NUM_SECTIONS) {
     if (section >= MessageImpl::NUM_SECTIONS) {
@@ -363,129 +535,14 @@ Message::addQuestion(const Question& question) {
     addQuestion(QuestionPtr(new 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
 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
 void
@@ -613,6 +670,9 @@ MessageImpl::parseSection(const Message::Section section,
     unsigned int added = 0;
     unsigned int added = 0;
 
 
     for (unsigned int count = 0; count < counts_[section]; ++count) {
     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);
         const Name name(buffer);
 
 
         // buffer must store at least RR TYPE, RR CLASS, TTL, and RDLEN.
         // 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);
         ConstRdataPtr rdata = createRdata(rrtype, rrclass, buffer, rdlen);
 
 
         if (rrtype == RRType::OPT()) {
         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 {
         } 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;
             ++added;
         }
         }
     }
     }
@@ -663,6 +703,65 @@ MessageImpl::parseSection(const Message::Section section,
     return (added);
     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 {
 namespace {
 template <typename T>
 template <typename T>
 struct SectionFormatter {
 struct SectionFormatter {
@@ -696,31 +795,31 @@ Message::toText() const {
     // for simplicity we don't consider extended rcode (unlike BIND9)
     // for simplicity we don't consider extended rcode (unlike BIND9)
     s += ", status: " + impl_->rcode_->toText();
     s += ", status: " + impl_->rcode_->toText();
     s += ", id: " + boost::lexical_cast<string>(impl_->qid_);
     s += ", id: " + boost::lexical_cast<string>(impl_->qid_);
-    s += "\n;; flags: ";
+    s += "\n;; flags:";
     if (getHeaderFlag(HEADERFLAG_QR)) {
     if (getHeaderFlag(HEADERFLAG_QR)) {
-        s += "qr ";
+        s += " qr";
     }
     }
     if (getHeaderFlag(HEADERFLAG_AA)) {
     if (getHeaderFlag(HEADERFLAG_AA)) {
-        s += "aa ";
+        s += " aa";
     }
     }
     if (getHeaderFlag(HEADERFLAG_TC)) {
     if (getHeaderFlag(HEADERFLAG_TC)) {
-        s += "tc ";
+        s += " tc";
     }
     }
     if (getHeaderFlag(HEADERFLAG_RD)) {
     if (getHeaderFlag(HEADERFLAG_RD)) {
-        s += "rd ";
+        s += " rd";
     }
     }
     if (getHeaderFlag(HEADERFLAG_RA)) {
     if (getHeaderFlag(HEADERFLAG_RA)) {
-        s += "ra ";
+        s += " ra";
     }
     }
     if (getHeaderFlag(HEADERFLAG_AD)) {
     if (getHeaderFlag(HEADERFLAG_AD)) {
-        s += "ad ";
+        s += " ad";
     }
     }
     if (getHeaderFlag(HEADERFLAG_CD)) {
     if (getHeaderFlag(HEADERFLAG_CD)) {
-        s += "cd ";
+        s += " cd";
     }
     }
 
 
     // for simplicity, don't consider the update case for now
     // 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]);
         lexical_cast<string>(impl_->counts_[SECTION_QUESTION]);
     s += ", ANSWER: " +
     s += ", ANSWER: " +
         lexical_cast<string>(impl_->counts_[SECTION_ANSWER]);
         lexical_cast<string>(impl_->counts_[SECTION_ANSWER]);
@@ -731,6 +830,9 @@ Message::toText() const {
     if (impl_->edns_ != NULL) {
     if (impl_->edns_ != NULL) {
         ++arcount;
         ++arcount;
     }
     }
+    if (impl_->tsig_rr_ != NULL) {
+        ++arcount;
+    }
     s += ", ADDITIONAL: " + lexical_cast<string>(arcount) + "\n";
     s += ", ADDITIONAL: " + lexical_cast<string>(arcount) + "\n";
 
 
     if (impl_->edns_ != NULL) {
     if (impl_->edns_ != NULL) {
@@ -767,6 +869,11 @@ Message::toText() const {
                  SectionFormatter<RRsetPtr>(SECTION_ADDITIONAL, s));
                  SectionFormatter<RRsetPtr>(SECTION_ADDITIONAL, s));
     }
     }
 
 
+    if (impl_->tsig_rr_ != NULL) {
+        s += "\n;; TSIG PSEUDOSECTION:\n";
+        s += impl_->tsig_rr_->toText();
+    }
+
     return (s);
     return (s);
 }
 }
 
 

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

@@ -33,6 +33,8 @@ class InputBuffer;
 }
 }
 
 
 namespace dns {
 namespace dns {
+class TSIGContext;
+class TSIGRecord;
 
 
 ///
 ///
 /// \brief A standard DNS module exception that is thrown if a wire format
 /// \brief A standard DNS module exception that is thrown if a wire format
@@ -80,7 +82,7 @@ public:
 
 
 typedef uint16_t qid_t;
 typedef uint16_t qid_t;
 
 
-class MessageRenderer;
+class AbstractMessageRenderer;
 class Message;
 class Message;
 class MessageImpl;
 class MessageImpl;
 class Opcode;
 class Opcode;
@@ -368,6 +370,25 @@ public:
     /// \c Message.
     /// \c Message.
     void setEDNS(ConstEDNSPtr edns);
     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.
     /// \brief Returns the number of RRs contained in the given section.
     ///
     ///
     /// In the \c PARSE mode, the returned value may not be identical to
     /// In the \c PARSE mode, the returned value may not be identical to
@@ -523,13 +544,31 @@ public:
     /// class \c InvalidMessageOperation will be thrown.
     /// class \c InvalidMessageOperation will be thrown.
     std::string toText() const;
     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.
     /// object.
     ///
     ///
     /// This \c Message must be in the \c RENDER mode and both \c Opcode and
     /// 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
     /// \c Rcode must have been set beforehand; otherwise, an exception of
     /// class \c InvalidMessageOperation will be thrown.
     /// 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.
     /// \brief Parse the header section of the \c Message.
     void parseHeader(isc::util::InputBuffer& buffer);
     void parseHeader(isc::util::InputBuffer& buffer);
@@ -563,6 +602,16 @@ private:
 /// that originated the asynchronous call falls out of scope.
 /// that originated the asynchronous call falls out of scope.
 typedef boost::shared_ptr<Message> MessagePtr;
 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);
 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 += rdata_python.cc
 EXTRA_DIST += rrtype_python.cc
 EXTRA_DIST += rrtype_python.cc
 EXTRA_DIST += tsigkey_python.cc
 EXTRA_DIST += tsigkey_python.cc
+EXTRA_DIST += tsig_python.cc
 
 
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # suffix for dynamic objects.  -module is necessary to work this around.
 # 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::dns;
 using namespace isc::util;
 using namespace isc::util;
 
 
+namespace {
 //
 //
 // Declaration of the custom exceptions
 // Declaration of the custom exceptions
 // Initialization and addition of these go in the initModulePart
 // Initialization and addition of these go in the initModulePart
 // function at the end of this file
 // 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
 // Definition of the classes
@@ -36,10 +37,6 @@ static PyObject* po_InvalidMessageUDPSize;
 // and a type description
 // and a type description
 
 
 //
 //
-// Section
-//
-
-//
 // Message
 // Message
 //
 //
 
 
@@ -55,36 +52,36 @@ public:
 //
 //
 
 
 // General creation and destruction
 // 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?)
 // 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_beginQuestion(s_Message* self, PyObject* args);
 //static PyObject* Message_endQuestion(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_beginSection(s_Message* self, PyObject* args);
 //static PyObject* Message_endSection(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
 // This list contains the actual set of functions we have in
 // python. Each entry has
 // python. Each entry has
@@ -92,7 +89,7 @@ static PyObject* Message_fromWire(s_Message* self, PyObject* args);
 // 2. Our static function here
 // 2. Our static function here
 // 3. Argument type
 // 3. Argument type
 // 4. Documentation
 // 4. Documentation
-static PyMethodDef Message_methods[] = {
+PyMethodDef Message_methods[] = {
     { "get_header_flag", reinterpret_cast<PyCFunction>(Message_getHeaderFlag),
     { "get_header_flag", reinterpret_cast<PyCFunction>(Message_getHeaderFlag),
       METH_VARARGS,
       METH_VARARGS,
       "Return whether the specified header flag bit is set in the "
       "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
 // This defines the complete type for reflection in python and
 // parsing of PyObject* to s_Message
 // parsing of PyObject* to s_Message
 // Most of the functions are not actually implemented and NULL here.
 // Most of the functions are not actually implemented and NULL here.
-static PyTypeObject message_type = {
+PyTypeObject message_type = {
     PyVarObject_HEAD_INIT(NULL, 0)
     PyVarObject_HEAD_INIT(NULL, 0)
     "pydnspp.Message",
     "pydnspp.Message",
     sizeof(s_Message),                  // tp_basicsize
     sizeof(s_Message),                  // tp_basicsize
@@ -225,7 +222,7 @@ static PyTypeObject message_type = {
     0                                   // tp_version_tag
     0                                   // tp_version_tag
 };
 };
 
 
-static int
+int
 Message_init(s_Message* self, PyObject* args) {
 Message_init(s_Message* self, PyObject* args) {
     int i;
     int i;
 
 
@@ -248,14 +245,14 @@ Message_init(s_Message* self, PyObject* args) {
     return (-1);
     return (-1);
 }
 }
 
 
-static void
+void
 Message_destroy(s_Message* self) {
 Message_destroy(s_Message* self) {
     delete self->message;
     delete self->message;
     self->message = NULL;
     self->message = NULL;
     Py_TYPE(self)->tp_free(self);
     Py_TYPE(self)->tp_free(self);
 }
 }
 
 
-static PyObject*
+PyObject*
 Message_getHeaderFlag(s_Message* self, PyObject* args) {
 Message_getHeaderFlag(s_Message* self, PyObject* args) {
     unsigned int messageflag;
     unsigned int messageflag;
     if (!PyArg_ParseTuple(args, "I", &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) {
 Message_setHeaderFlag(s_Message* self, PyObject* args) {
     long messageflag;
     long messageflag;
     PyObject *on = Py_True;
     PyObject *on = Py_True;
@@ -304,12 +301,12 @@ Message_setHeaderFlag(s_Message* self, PyObject* args) {
     }
     }
 }
 }
 
 
-static PyObject*
+PyObject*
 Message_getQid(s_Message* self) {
 Message_getQid(s_Message* self) {
     return (Py_BuildValue("I", self->message->getQid()));
     return (Py_BuildValue("I", self->message->getQid()));
 }
 }
 
 
-static PyObject*
+PyObject*
 Message_setQid(s_Message* self, PyObject* args) {
 Message_setQid(s_Message* self, PyObject* args) {
     long id;
     long id;
     if (!PyArg_ParseTuple(args, "l", &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) {
 Message_getRcode(s_Message* self) {
     s_Rcode* rcode;
     s_Rcode* rcode;
 
 
@@ -356,7 +353,7 @@ Message_getRcode(s_Message* self) {
     return (rcode);
     return (rcode);
 }
 }
 
 
-static PyObject*
+PyObject*
 Message_setRcode(s_Message* self, PyObject* args) {
 Message_setRcode(s_Message* self, PyObject* args) {
     s_Rcode* rcode;
     s_Rcode* rcode;
     if (!PyArg_ParseTuple(args, "O!", &rcode_type, &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) {
 Message_getOpcode(s_Message* self) {
     s_Opcode* opcode;
     s_Opcode* opcode;
 
 
@@ -394,7 +391,7 @@ Message_getOpcode(s_Message* self) {
     return (opcode);
     return (opcode);
 }
 }
 
 
-static PyObject*
+PyObject*
 Message_setOpcode(s_Message* self, PyObject* args) {
 Message_setOpcode(s_Message* self, PyObject* args) {
     s_Opcode* opcode;
     s_Opcode* opcode;
     if (!PyArg_ParseTuple(args, "O!", &opcode_type, &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) {
 Message_getEDNS(s_Message* self) {
     s_EDNS* edns;
     s_EDNS* edns;
     EDNS* edns_body;
     EDNS* edns_body;
@@ -429,7 +426,7 @@ Message_getEDNS(s_Message* self) {
     return (edns);
     return (edns);
 }
 }
 
 
-static PyObject*
+PyObject*
 Message_setEDNS(s_Message* self, PyObject* args) {
 Message_setEDNS(s_Message* self, PyObject* args) {
     s_EDNS* edns;
     s_EDNS* edns;
     if (!PyArg_ParseTuple(args, "O!", &edns_type, &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) {
 Message_getRRCount(s_Message* self, PyObject* args) {
     unsigned int section;
     unsigned int section;
     if (!PyArg_ParseTuple(args, "I", &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?)
 // TODO use direct iterators for these? (or simply lists for now?)
-static PyObject*
+PyObject*
 Message_getQuestion(s_Message* self) {
 Message_getQuestion(s_Message* self) {
     QuestionIterator qi, qi_end;
     QuestionIterator qi, qi_end;
     try {
     try {
@@ -502,7 +499,7 @@ Message_getQuestion(s_Message* self) {
     return (list);
     return (list);
 }
 }
 
 
-static PyObject*
+PyObject*
 Message_getSection(s_Message* self, PyObject* args) {
 Message_getSection(s_Message* self, PyObject* args) {
     unsigned int section;
     unsigned int section;
     if (!PyArg_ParseTuple(args, "I", &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_beginSection(s_Message* self, PyObject* args);
 //static PyObject* Message_endSection(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_addQuestion(s_Message* self, PyObject* args);
-static PyObject*
+PyObject*
 Message_addQuestion(s_Message* self, PyObject* args) {
 Message_addQuestion(s_Message* self, PyObject* args) {
     s_Question *question;
     s_Question *question;
 
 
@@ -572,7 +569,7 @@ Message_addQuestion(s_Message* self, PyObject* args) {
     Py_RETURN_NONE;
     Py_RETURN_NONE;
 }
 }
 
 
-static PyObject*
+PyObject*
 Message_addRRset(s_Message* self, PyObject* args) {
 Message_addRRset(s_Message* self, PyObject* args) {
     PyObject *sign = Py_False;
     PyObject *sign = Py_False;
     int section;
     int section;
@@ -599,7 +596,7 @@ Message_addRRset(s_Message* self, PyObject* args) {
     }
     }
 }
 }
 
 
-static PyObject*
+PyObject*
 Message_clear(s_Message* self, PyObject* args) {
 Message_clear(s_Message* self, PyObject* args) {
     int i;
     int i;
     if (PyArg_ParseTuple(args, "i", &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) {
 Message_makeResponse(s_Message* self) {
     self->message->makeResponse();
     self->message->makeResponse();
     Py_RETURN_NONE;
     Py_RETURN_NONE;
 }
 }
 
 
-static PyObject*
+PyObject*
 Message_toText(s_Message* self) {
 Message_toText(s_Message* self) {
     // Py_BuildValue makes python objects from native data
     // Py_BuildValue makes python objects from native data
     try {
     try {
@@ -641,7 +638,7 @@ Message_toText(s_Message* self) {
     }
     }
 }
 }
 
 
-static PyObject*
+PyObject*
 Message_str(PyObject* self) {
 Message_str(PyObject* self) {
     // Simply call the to_text method we already defined
     // Simply call the to_text method we already defined
     return (PyObject_CallMethod(self,
     return (PyObject_CallMethod(self,
@@ -649,13 +646,20 @@ Message_str(PyObject* self) {
                                 const_cast<char*>("")));
                                 const_cast<char*>("")));
 }
 }
 
 
-static PyObject*
+PyObject*
 Message_toWire(s_Message* self, PyObject* args) {
 Message_toWire(s_Message* self, PyObject* args) {
     s_MessageRenderer* mr;
     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 {
         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
             // If we return NULL it is seen as an error, so use this for
             // None returns
             // None returns
             Py_RETURN_NONE;
             Py_RETURN_NONE;
@@ -671,7 +675,7 @@ Message_toWire(s_Message* self, PyObject* args) {
     return (NULL);
     return (NULL);
 }
 }
 
 
-static PyObject*
+PyObject*
 Message_fromWire(s_Message* self, PyObject* args) {
 Message_fromWire(s_Message* self, PyObject* args) {
     const char* b;
     const char* b;
     Py_ssize_t len;
     Py_ssize_t len;
@@ -765,3 +769,4 @@ initModulePart_Message(PyObject* mod) {
 
 
     return (true);
     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,
 #include <dns/python/question_python.cc>       // needs RRClass, RRType, RRTTL,
                                                // Name
                                                // Name
 #include <dns/python/tsigkey_python.cc>        // needs 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/opcode_python.cc>
 #include <dns/python/rcode_python.cc>
 #include <dns/python/rcode_python.cc>
 #include <dns/python/edns_python.cc>           // needs Messagerenderer, Rcode
 #include <dns/python/edns_python.cc>           // needs Messagerenderer, Rcode
@@ -153,6 +154,10 @@ PyInit_pydnspp(void) {
         return (NULL);
         return (NULL);
     }
     }
 
 
+    if (!initModulePart_TSIGContext(mod)) {
+        return (NULL);
+    }
+
     return (mod);
     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 += rrset_python_test.py
 PYTESTS += rrttl_python_test.py
 PYTESTS += rrttl_python_test.py
 PYTESTS += rrtype_python_test.py
 PYTESTS += rrtype_python_test.py
+PYTESTS += tsig_python_test.py
 PYTESTS += tsigkey_python_test.py
 PYTESTS += tsigkey_python_test.py
 
 
 EXTRA_DIST = $(PYTESTS)
 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)
     message_render.add_rrset(Message.SECTION_ANSWER, rrset)
     return message_render
     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):
 class MessageTest(unittest.TestCase):
 
 
@@ -81,6 +87,8 @@ class MessageTest(unittest.TestCase):
 
 
         self.bogus_section = Message.SECTION_ADDITIONAL + 1
         self.bogus_section = Message.SECTION_ADDITIONAL + 1
         self.bogus_below_section = Message.SECTION_QUESTION - 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):
     def test_init(self):
         self.assertRaises(TypeError, Message, -1)
         self.assertRaises(TypeError, Message, -1)
@@ -277,12 +285,39 @@ class MessageTest(unittest.TestCase):
         self.assertRaises(InvalidMessageOperation, self.r.to_wire,
         self.assertRaises(InvalidMessageOperation, self.r.to_wire,
                           MessageRenderer())
                           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):
     def test_to_text(self):
         message_render = create_message()
         message_render = create_message()
         
         
         msg_str =\
         msg_str =\
 """;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4149
 """;; ->>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:
 ;; QUESTION SECTION:
 ;test.example.com. IN A
 ;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
 unsigned int
-Question::toWire(MessageRenderer& renderer) const {
+Question::toWire(AbstractMessageRenderer& renderer) const {
     renderer.writeName(name_);
     renderer.writeName(name_);
     rrtype_.toWire(renderer);
     rrtype_.toWire(renderer);
     rrclass_.toWire(renderer);
     rrclass_.toWire(renderer);

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

@@ -32,7 +32,7 @@ class OutputBuffer;
 
 
 namespace dns {
 namespace dns {
 
 
-class MessageRenderer;
+class AbstractMessageRenderer;
 class Question;
 class Question;
 
 
 /// \brief A pointer-like type pointing to an \c Question object.
 /// \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
     /// \param renderer DNS message rendering context that encapsulates the
     /// output buffer and name compression information.
     /// output buffer and name compression information.
     /// \return 1
     /// \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.
     /// \brief Render the Question in the wire format without name compression.
     ///
     ///
     /// This method behaves like the render version except it doesn't compress
     /// This method behaves like the render version except it doesn't compress
     /// the owner name.
     /// 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.
     /// \param buffer An output buffer to store the wire data.
     /// \return 1
     /// \return 1

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

@@ -24,7 +24,7 @@
 #include <dns/messagerenderer.h>
 #include <dns/messagerenderer.h>
 #include <dns/rdata.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 #include <dns/rdataclass.h>
-
+#include <dns/tsigerror.h>
 
 
 using namespace std;
 using namespace std;
 using namespace boost;
 using namespace boost;
@@ -313,15 +313,7 @@ TSIG::toText() const {
         result += encodeBase64(impl_->mac_) + " ";
         result += encodeBase64(impl_->mac_) + " ";
     }
     }
     result += lexical_cast<string>(impl_->original_id_) + " ";
     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());
     result += lexical_cast<string>(impl_->other_data_.size());
     if (impl_->other_data_.size() > 0) {
     if (impl_->other_data_.size() > 0) {
         result += " " + encodeBase64(impl_->other_data_);
         result += " " + encodeBase64(impl_->other_data_);

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

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

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

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

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

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

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

@@ -28,7 +28,7 @@ class OutputBuffer;
 namespace dns {
 namespace dns {
 
 
 // forward declarations
 // forward declarations
-class MessageRenderer;
+class AbstractMessageRenderer;
 
 
 ///
 ///
 /// \brief A standard DNS module exception that is thrown if an RRTTL object
 /// \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
     /// \param renderer DNS message rendering context that encapsulates the
     /// output buffer in which the RRTTL is to be stored.
     /// 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.
     /// \brief Render the \c RRTTL in the wire format.
     ///
     ///
     /// This method renders the TTL value in network byte order into the
     /// 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 += tsig_unittest.cc
 run_unittests_SOURCES += tsigerror_unittest.cc
 run_unittests_SOURCES += tsigerror_unittest.cc
 run_unittests_SOURCES += tsigkey_unittest.cc
 run_unittests_SOURCES += tsigkey_unittest.cc
+run_unittests_SOURCES += tsigrecord_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 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
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
+#include <fstream>
+
+#include <boost/scoped_ptr.hpp>
+
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
 #include <util/buffer.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/edns.h>
 #include <dns/exceptions.h>
 #include <dns/exceptions.h>
 #include <dns/message.h>
 #include <dns/message.h>
@@ -26,6 +35,8 @@
 #include <dns/rrclass.h>
 #include <dns/rrclass.h>
 #include <dns/rrttl.h>
 #include <dns/rrttl.h>
 #include <dns/rrtype.h>
 #include <dns/rrtype.h>
+#include <dns/tsig.h>
+#include <dns/tsigkey.h>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
@@ -53,6 +64,18 @@ using namespace isc::dns::rdata;
 const uint16_t Message::DEFAULT_MAX_UDPSIZE;
 const uint16_t Message::DEFAULT_MAX_UDPSIZE;
 const Name test_name("test.example.com");
 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 {
 namespace {
 class MessageTest : public ::testing::Test {
 class MessageTest : public ::testing::Test {
 protected:
 protected:
@@ -60,7 +83,9 @@ protected:
                     message_parse(Message::PARSE),
                     message_parse(Message::PARSE),
                     message_render(Message::RENDER),
                     message_render(Message::RENDER),
                     bogus_section(static_cast<Message::Section>(
                     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(),
         rrset_a = RRsetPtr(new RRset(test_name, RRClass::IN(),
                                      RRType::A(), RRTTL(3600)));
                                      RRType::A(), RRTTL(3600)));
@@ -88,6 +113,9 @@ protected:
     RRsetPtr rrset_a;           // A RRset with two RDATAs
     RRsetPtr rrset_a;           // A RRset with two RDATAs
     RRsetPtr rrset_aaaa;        // AAAA RRset with one RDATA with RRSIG
     RRsetPtr rrset_aaaa;        // AAAA RRset with one RDATA with RRSIG
     RRsetPtr rrset_rrsig;       // RRSIG for the AAAA RRset
     RRsetPtr rrset_rrsig;       // RRSIG for the AAAA RRset
+    TSIGContext tsig_ctx;
+    vector<unsigned char> expected_data;
+
     static void factoryFromFile(Message& message, const char* datafile);
     static void factoryFromFile(Message& message, const char* datafile);
 };
 };
 
 
@@ -166,6 +194,70 @@ TEST_F(MessageTest, setEDNS) {
     EXPECT_EQ(edns, message_render.getEDNS());
     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) {
 TEST_F(MessageTest, getRRCount) {
     // by default all counters should be 0
     // by default all counters should be 0
     EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION));
     EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION));
@@ -519,6 +611,65 @@ TEST_F(MessageTest, toWireInParseMode) {
     EXPECT_THROW(message_parse.toWire(renderer), InvalidMessageOperation);
     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) {
 TEST_F(MessageTest, toWireWithoutOpcode) {
     message_render.setRcode(Rcode::NOERROR());
     message_render.setRcode(Rcode::NOERROR());
     EXPECT_THROW(message_render.toWire(renderer), InvalidMessageOperation);
     EXPECT_THROW(message_render.toWire(renderer), InvalidMessageOperation);
@@ -529,6 +680,44 @@ TEST_F(MessageTest, toWireWithoutRcode) {
     EXPECT_THROW(message_render.toWire(renderer), InvalidMessageOperation);
     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) {
 TEST_F(MessageTest, toTextWithoutOpcode) {
     message_render.setRcode(Rcode::NOERROR());
     message_render.setRcode(Rcode::NOERROR());
     EXPECT_THROW(message_render.toText(), InvalidMessageOperation);
     EXPECT_THROW(message_render.toText(), InvalidMessageOperation);

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

@@ -14,13 +14,16 @@
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
+#include <util/unittests/testdata.h>
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/unittest_util.h>
 
 
 int
 int
 main(int argc, char* argv[]) {
 main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
     ::testing::InitGoogleTest(&argc, argv);
     isc::UnitTestUtil::addDataPath(TEST_DATA_SRCDIR);
     isc::UnitTestUtil::addDataPath(TEST_DATA_SRCDIR);
+    isc::util::unittests::addTestDataPath(TEST_DATA_SRCDIR);
     isc::UnitTestUtil::addDataPath(TEST_DATA_BUILDDIR);
     isc::UnitTestUtil::addDataPath(TEST_DATA_BUILDDIR);
+    isc::util::unittests::addTestDataPath(TEST_DATA_BUILDDIR);
 
 
     return (RUN_ALL_TESTS());
     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_toWire1.wire edns_toWire2.wire edns_toWire3.wire
 BUILT_SOURCES += edns_toWire4.wire
 BUILT_SOURCES += edns_toWire4.wire
 BUILT_SOURCES += message_fromWire10.wire message_fromWire11.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 += name_toWire5.wire name_toWire6.wire
 BUILT_SOURCES += rdatafields1.wire rdatafields2.wire rdatafields3.wire
 BUILT_SOURCES += rdatafields1.wire rdatafields2.wire rdatafields3.wire
 BUILT_SOURCES += rdatafields4.wire rdatafields5.wire rdatafields6.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_toWire1.wire rdata_tsig_toWire2.wire
 BUILT_SOURCES += rdata_tsig_toWire3.wire rdata_tsig_toWire4.wire
 BUILT_SOURCES += rdata_tsig_toWire3.wire rdata_tsig_toWire4.wire
 BUILT_SOURCES += rdata_tsig_toWire5.wire
 BUILT_SOURCES += rdata_tsig_toWire5.wire
+BUILT_SOURCES += tsigrecord_toWire1.wire tsigrecord_toWire2.wire
 
 
 # NOTE: keep this in sync with real file listing
 # NOTE: keep this in sync with real file listing
 # so is included in tarball
 # so is included in tarball
@@ -45,8 +52,13 @@ EXTRA_DIST += message_fromWire3 message_fromWire4
 EXTRA_DIST += message_fromWire5 message_fromWire6
 EXTRA_DIST += message_fromWire5 message_fromWire6
 EXTRA_DIST += message_fromWire7 message_fromWire8
 EXTRA_DIST += message_fromWire7 message_fromWire8
 EXTRA_DIST += message_fromWire9 message_fromWire10.spec
 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_fromWire1 name_fromWire2 name_fromWire3_1 name_fromWire3_2
 EXTRA_DIST += name_fromWire4 name_fromWire6 name_fromWire7 name_fromWire8
 EXTRA_DIST += name_fromWire4 name_fromWire6 name_fromWire7 name_fromWire8
 EXTRA_DIST += name_fromWire9 name_fromWire10 name_fromWire11 name_fromWire12
 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_fromWire8.spec rdata_nsec_fromWire9.spec
 EXTRA_DIST += rdata_nsec_fromWire10.spec
 EXTRA_DIST += rdata_nsec_fromWire10.spec
 EXTRA_DIST += rdata_nsec3param_fromWire1
 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_fromWire4.spec rdata_nsec3_fromWire5.spec
 EXTRA_DIST += rdata_nsec3_fromWire6.spec rdata_nsec3_fromWire7.spec
 EXTRA_DIST += rdata_nsec3_fromWire6.spec rdata_nsec3_fromWire7.spec
 EXTRA_DIST += rdata_nsec3_fromWire8.spec rdata_nsec3_fromWire9.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_toWire1.spec rdata_tsig_toWire2.spec
 EXTRA_DIST += rdata_tsig_toWire3.spec rdata_tsig_toWire4.spec
 EXTRA_DIST += rdata_tsig_toWire3.spec rdata_tsig_toWire4.spec
 EXTRA_DIST += rdata_tsig_toWire5.spec
 EXTRA_DIST += rdata_tsig_toWire5.spec
-EXTRA_DIST += rdata_nsec3_fromWire2.spec
+EXTRA_DIST += tsigrecord_toWire1.spec tsigrecord_toWire2.spec
 
 
 .spec.wire:
 .spec.wire:
 	./gen-wiredata.py -o $@ $<
 	./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
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 
-import configparser, re, time, sys
+import configparser, re, time, socket, sys
 from datetime import datetime
 from datetime import datetime
 from optparse import OptionParser
 from optparse import OptionParser
 
 
@@ -215,6 +215,74 @@ class EDNS:
         f.write('# RDLEN=%d\n' % self.rdlen)
         f.write('# RDLEN=%d\n' % self.rdlen)
         f.write('%04x\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:
 class SOA:
     # this currently doesn't support name compression within the RDATA.
     # this currently doesn't support name compression within the RDATA.
     rdlen = -1                  # auto-calculate
     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('# Tag=%d Signer=%s and Signature\n' % (self.tag, self.signer))
         f.write('%04x %s %s\n' % (self.tag, name_wire, sig_wire))
         f.write('%04x %s %s\n' % (self.tag, name_wire, sig_wire))
 
 
-class TSIG:
+class TSIG(RR):
     rdlen = None                # auto-calculate
     rdlen = None                # auto-calculate
     algorithm = 'hmac-sha256'
     algorithm = 'hmac-sha256'
     time_signed = 1286978795    # arbitrarily chosen default
     time_signed = 1286978795    # arbitrarily chosen default
@@ -444,12 +512,18 @@ class TSIG:
     other_len = None         # 6 if error is BADTIME; otherwise 0
     other_len = None         # 6 if error is BADTIME; otherwise 0
     other_data = None        # use time_signed + fudge + 1 for BADTIME
     other_data = None        # use time_signed + fudge + 1 for BADTIME
     dict_macsize = { 'hmac-md5' : 16, 'hmac-sha1' : 20, 'hmac-sha256' : 32 }
     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):
     def dump(self, f):
         if str(self.algorithm) == 'hmac-md5':
         if str(self.algorithm) == 'hmac-md5':
             name_wire = encode_name('hmac-md5.sig-alg.reg.int')
             name_wire = encode_name('hmac-md5.sig-alg.reg.int')
         else:
         else:
             name_wire = encode_name(self.algorithm)
             name_wire = encode_name(self.algorithm)
-        rdlen = self.rdlen
         mac_size = self.mac_size
         mac_size = self.mac_size
         if mac_size is None:
         if mac_size is None:
             if self.algorithm in self.dict_macsize.keys():
             if self.algorithm in self.dict_macsize.keys():
@@ -468,11 +542,10 @@ class TSIG:
                 if self.error == 18 else ''
                 if self.error == 18 else ''
         else:
         else:
             other_data = encode_string(self.other_data, other_len)
             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' %
         f.write('# Algorithm=%s Time-Signed=%d Fudge=%d\n' %
                 (self.algorithm, self.time_signed, self.fudge))
                 (self.algorithm, self.time_signed, self.fudge))
         f.write('%s %012x %04x\n' % (name_wire, 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, {}),
     config_param = {'name' : (Name, {}),
                     'header' : (DNSHeader, header_xtables),
                     'header' : (DNSHeader, header_xtables),
                     'question' : (DNSQuestion, question_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, {}),
                     'rp' : (RP, {}), 'rrsig' : (RRSIG, {}),
                     'nsec' : (NSEC, {}), 'nsec3' : (NSEC3, {}),
                     'nsec' : (NSEC, {}), 'nsec3' : (NSEC3, {}),
                     'tsig' : (TSIG, {}) }
                     '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/buffer.h>
 #include <util/encode/base64.h>
 #include <util/encode/base64.h>
 #include <util/unittests/newhook.h>
 #include <util/unittests/newhook.h>
+#include <util/time_utilities.h>
 
 
 #include <dns/message.h>
 #include <dns/message.h>
 #include <dns/messagerenderer.h>
 #include <dns/messagerenderer.h>
@@ -36,6 +37,7 @@
 #include <dns/rrtype.h>
 #include <dns/rrtype.h>
 #include <dns/tsig.h>
 #include <dns/tsig.h>
 #include <dns/tsigkey.h>
 #include <dns/tsigkey.h>
+#include <dns/tsigrecord.h>
 
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/unittest_util.h>
 
 
@@ -49,14 +51,12 @@ using isc::UnitTestUtil;
 
 
 // See dnssectime.cc
 // See dnssectime.cc
 namespace isc {
 namespace isc {
-namespace dns {
-namespace tsig {
+namespace util {
 namespace detail {
 namespace detail {
 extern int64_t (*gettimeFunction)();
 extern int64_t (*gettimeFunction)();
 }
 }
 }
 }
 }
 }
-}
 
 
 namespace {
 namespace {
 // See dnssectime_unittest.cc
 // See dnssectime_unittest.cc
@@ -75,7 +75,7 @@ protected:
     {
     {
         // Make sure we use the system time by default so that we won't be
         // Make sure we use the system time by default so that we won't be
         // confused due to other tests that tweak the time.
         // confused due to other tests that tweak the time.
-        tsig::detail::gettimeFunction = NULL;
+        isc::util::detail::gettimeFunction = NULL;
 
 
         decodeBase64("SFuWd/q99SzF8Yzd1QbB9g==", secret);
         decodeBase64("SFuWd/q99SzF8Yzd1QbB9g==", secret);
         tsig_ctx.reset(new TSIGContext(TSIGKey(test_name,
         tsig_ctx.reset(new TSIGContext(TSIGKey(test_name,
@@ -87,7 +87,7 @@ protected:
                                                       secret.size())));
                                                       secret.size())));
     }
     }
     ~TSIGTest() {
     ~TSIGTest() {
-        tsig::detail::gettimeFunction = NULL;
+        isc::util::detail::gettimeFunction = NULL;
     }
     }
 
 
     // Many of the tests below create some DNS message and sign it under
     // 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
     0x21, 0xce, 0x6c, 0x6f, 0xff, 0x1e, 0x9e, 0xf3
 };
 };
 TEST_F(TSIGTest, sign) {
 TEST_F(TSIGTest, sign) {
-    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
 
 
     {
     {
         SCOPED_TRACE("Sign test for query");
         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
 // 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.
 // be ensured at the level of TSIGKey, but we confirm that at this level, too.
 TEST_F(TSIGTest, signUsingUpperCasedKeyName) {
 TEST_F(TSIGTest, signUsingUpperCasedKeyName) {
-    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
 
 
     TSIGContext cap_ctx(TSIGKey(Name("WWW.EXAMPLE.COM"),
     TSIGContext cap_ctx(TSIGKey(Name("WWW.EXAMPLE.COM"),
                                 TSIGKey::HMACMD5_NAME(),
                                 TSIGKey::HMACMD5_NAME(),
@@ -239,7 +239,7 @@ TEST_F(TSIGTest, signUsingUpperCasedKeyName) {
 
 
 // Same as the previous test, but for the algorithm name.
 // Same as the previous test, but for the algorithm name.
 TEST_F(TSIGTest, signUsingUpperCasedAlgorithmName) {
 TEST_F(TSIGTest, signUsingUpperCasedAlgorithmName) {
-    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
 
 
     TSIGContext cap_ctx(TSIGKey(test_name,
     TSIGContext cap_ctx(TSIGKey(test_name,
                                 Name("HMAC-md5.SIG-alg.REG.int"),
                                 Name("HMAC-md5.SIG-alg.REG.int"),
@@ -325,7 +325,7 @@ TEST_F(TSIGTest, signExceptionSafety) {
 //   HMAC Size: 20
 //   HMAC Size: 20
 //   HMAC: 415340c7daf824ed684ee586f7b5a67a2febc0d3
 //   HMAC: 415340c7daf824ed684ee586f7b5a67a2febc0d3
 TEST_F(TSIGTest, signUsingHMACSHA1) {
 TEST_F(TSIGTest, signUsingHMACSHA1) {
-    tsig::detail::gettimeFunction = testGetTime<0x4dae7d5f>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4dae7d5f>;
 
 
     secret.clear();
     secret.clear();
     decodeBase64("MA+QDhXbyqUak+qnMFyTyEirzng=", secret);
     decodeBase64("MA+QDhXbyqUak+qnMFyTyEirzng=", secret);
@@ -350,7 +350,7 @@ TEST_F(TSIGTest, signUsingHMACSHA1) {
 // Answer: www.example.com. 86400 IN A 192.0.2.1
 // Answer: www.example.com. 86400 IN A 192.0.2.1
 // MAC: 8fcda66a7cd1a3b9948eb1869d384a9f
 // MAC: 8fcda66a7cd1a3b9948eb1869d384a9f
 TEST_F(TSIGTest, signResponse) {
 TEST_F(TSIGTest, signResponse) {
-    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
 
 
     ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
     ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
                                                    tsig_ctx.get());
                                                    tsig_ctx.get());
@@ -385,7 +385,7 @@ TEST_F(TSIGTest, signResponse) {
 //    Answer: example.com. 86400 IN NS ns.example.com.
 //    Answer: example.com. 86400 IN NS ns.example.com.
 //    MAC: 102458f7f62ddd7d638d746034130968
 //    MAC: 102458f7f62ddd7d638d746034130968
 TEST_F(TSIGTest, signContinuation) {
 TEST_F(TSIGTest, signContinuation) {
-    tsig::detail::gettimeFunction = testGetTime<0x4da8e951>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8e951>;
 
 
     const uint16_t axfr_qid = 0x3410;
     const uint16_t axfr_qid = 0x3410;
     const Name zone_name("example.com");
     const Name zone_name("example.com");
@@ -435,7 +435,7 @@ TEST_F(TSIGTest, signContinuation) {
 //   Error: 0x12 (BADTIME), Other Len: 6
 //   Error: 0x12 (BADTIME), Other Len: 6
 //   Other data: 00004da8be86
 //   Other data: 00004da8be86
 TEST_F(TSIGTest, badtimeResponse) {
 TEST_F(TSIGTest, badtimeResponse) {
-    tsig::detail::gettimeFunction = testGetTime<0x4da8b9d6>;
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6>;
 
 
     const uint16_t test_qid = 0x7fc4;
     const uint16_t test_qid = 0x7fc4;
     ConstTSIGRecordPtr tsig = createMessageAndSign(test_qid, test_name,
     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
     // "advance the clock" and try validating, which should fail due to BADTIME
     // (verifyTentative actually doesn't check the time, though)
     // (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());
     tsig_verify_ctx->verifyTentative(tsig, TSIGError::BAD_TIME());
     EXPECT_EQ(TSIGError::BAD_TIME(), tsig_verify_ctx->getError());
     EXPECT_EQ(TSIGError::BAD_TIME(), tsig_verify_ctx->getError());
 
 
@@ -468,7 +468,7 @@ TEST_F(TSIGTest, badtimeResponse) {
 }
 }
 
 
 TEST_F(TSIGTest, badsigResponse) {
 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
     // Sign a simple message, and force the verification to fail with
     // BADSIG.
     // BADSIG.
@@ -489,7 +489,7 @@ TEST_F(TSIGTest, badsigResponse) {
 
 
 TEST_F(TSIGTest, badkeyResponse) {
 TEST_F(TSIGTest, badkeyResponse) {
     // A similar test as badsigResponse but for BADKEY
     // 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_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name,
                                                           tsig_ctx.get()),
                                                           tsig_ctx.get()),
                                      TSIGError::BAD_KEY());
                                      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 <exceptions/exceptions.h>
 
 
 #include <util/buffer.h>
 #include <util/buffer.h>
+#include <util/time_utilities.h>
 
 
 #include <dns/rdataclass.h>
 #include <dns/rdataclass.h>
 #include <dns/rrclass.h>
 #include <dns/rrclass.h>
@@ -41,38 +42,10 @@ using namespace isc::dns::rdata;
 
 
 namespace isc {
 namespace isc {
 namespace dns {
 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 {
 namespace {
 typedef boost::shared_ptr<HMAC> HMACPtr;
 typedef boost::shared_ptr<HMAC> HMACPtr;
 }
 }
 
 
-const RRClass&
-TSIGRecord::getClass() {
-    return (RRClass::ANY());
-}
-
 struct TSIGContext::TSIGContextImpl {
 struct TSIGContext::TSIGContextImpl {
     TSIGContextImpl(const TSIGKey& key) :
     TSIGContextImpl(const TSIGKey& key) :
         state_(INIT), key_(key), error_(Rcode::NOERROR()),
         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,
     // represents a value in the expected range.  (In reality, however,
     // gettimeofdayWrapper() will return a positive integer that will fit
     // gettimeofdayWrapper() will return a positive integer that will fit
     // in 48 bits)
     // in 48 bits)
-    const uint64_t now = (gettimeofdayWrapper() & 0x0000ffffffffffffULL);
+    const uint64_t now = (detail::gettimeWrapper() & 0x0000ffffffffffffULL);
 
 
     // For responses adjust the error code.
     // For responses adjust the error code.
     if (impl_->state_ == CHECKED) {
     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.
     // specified in Section 4.3 of RFC2845.
     if (error == TSIGError::BAD_SIG() || error == TSIGError::BAD_KEY()) {
     if (error == TSIGError::BAD_SIG() || error == TSIGError::BAD_KEY()) {
         ConstTSIGRecordPtr tsig(new TSIGRecord(
         ConstTSIGRecordPtr tsig(new TSIGRecord(
+                                    impl_->key_.getKeyName(),
                                     any::TSIG(impl_->key_.getAlgorithmName(),
                                     any::TSIG(impl_->key_.getAlgorithmName(),
                                               now, DEFAULT_FUDGE, 0, NULL,
                                               now, DEFAULT_FUDGE, 0, NULL,
                                               qid, error.getCode(), 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.
     // Get the final digest, update internal state, then finish.
     vector<uint8_t> digest = hmac->sign();
     vector<uint8_t> digest = hmac->sign();
     ConstTSIGRecordPtr tsig(new TSIGRecord(
     ConstTSIGRecordPtr tsig(new TSIGRecord(
+                                impl_->key_.getKeyName(),
                                 any::TSIG(impl_->key_.getAlgorithmName(),
                                 any::TSIG(impl_->key_.getAlgorithmName(),
                                           time_signed, DEFAULT_FUDGE,
                                           time_signed, DEFAULT_FUDGE,
                                           digest.size(), &digest[0],
                                           digest.size(), &digest[0],

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

@@ -15,84 +15,14 @@
 #ifndef __TSIG_H
 #ifndef __TSIG_H
 #define __TSIG_H 1
 #define __TSIG_H 1
 
 
-#include <boost/shared_ptr.hpp>
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
 
 
-#include <dns/rdataclass.h>
 #include <dns/tsigerror.h>
 #include <dns/tsigerror.h>
 #include <dns/tsigkey.h>
 #include <dns/tsigkey.h>
+#include <dns/tsigrecord.h>
 
 
 namespace isc {
 namespace isc {
 namespace dns {
 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.
 /// TSIG session context.
 ///
 ///
 /// The \c TSIGContext class maintains a context of a signed session of
 /// 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
 // DecodeNormalizer is an input iterator intended to be used as a filter
 // between the encoded baseX stream and binary_from_baseXX.
 // between the encoded baseX stream and binary_from_baseXX.
 // A DecodeNormalizer object is configured with three string iterators
 // 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
 // the beginning position of baseX padding (when there's padding), and
 // end of the string, respectively.  It internally iterators over the original
 // end of the string, respectively.  It internally iterators over the original
 // stream, and return each character of the encoded string via its dereference
 // 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.
 // library, it's not even declared in a header file.
 namespace detail {
 namespace detail {
 int64_t (*gettimeFunction)() = NULL;
 int64_t (*gettimeFunction)() = NULL;
-}
 
 
-namespace {
 int64_t
 int64_t
-gettimeofdayWrapper() {
-    using namespace detail;
+gettimeWrapper() {
     if (gettimeFunction != NULL) {
     if (gettimeFunction != NULL) {
         return (gettimeFunction());
         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.
     // 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
     // Note that the following variables must be signed in order to handle
     // time until year 2038 correctly.
     // time until year 2038 correctly.
-    const int64_t start = gettimeofdayWrapper() - 0x7fffffff;
+    const int64_t start = detail::gettimeWrapper() - 0x7fffffff;
     int64_t base = 0;
     int64_t base = 0;
     int64_t t;
     int64_t t;
     while ((t = (base + value)) < start) {
     while ((t = (base + value)) < start) {

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

@@ -15,6 +15,8 @@
 #ifndef __TIME_UTILITIES_H
 #ifndef __TIME_UTILITIES_H
 #define __TIME_UTILITIES_H 1
 #define __TIME_UTILITIES_H 1
 
 
+#include <string>
+
 #include <sys/types.h>
 #include <sys/types.h>
 #include <stdint.h>
 #include <stdint.h>
 
 
@@ -39,6 +41,32 @@ public:
         isc::Exception(file, line, what) {}
         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.
 /// \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
 lib_LTLIBRARIES = libutil_unittests.la
 libutil_unittests_la_SOURCES = fork.h fork.cc resolver.h
 libutil_unittests_la_SOURCES = fork.h fork.cc resolver.h
 libutil_unittests_la_SOURCES += newhook.h newhook.cc
 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
 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: