Browse Source

[trac1114] implement afsdb rdata

chenzhengzhang 13 years ago
parent
commit
b4a1bc9ba2

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

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

+ 168 - 0
src/lib/dns/rdata/generic/afsdb_18.cc

@@ -0,0 +1,168 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string>
+#include <sstream>
+
+#include <util/buffer.h>
+#include <util/strutil.h>
+
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::util::str;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief Constructor from string.
+///
+/// \c afsdb_str must be formatted as follows:
+/// \code <subtype> <server name>
+/// \endcode
+/// where server name field must represent a valid domain name.
+///
+/// An example of valid string is:
+/// \code "1 server.example.com." \endcode
+///
+/// <b>Exceptions</b>
+///
+/// \exception InvalidRdataText The number of RDATA fields (must be 2) is
+/// incorrect.
+/// \exception std::bad_alloc Memory allocation fails.
+/// \exception Other The constructor of the \c Name class will throw if the
+/// names in the string is invalid.
+AFSDB::AFSDB(const std::string& afsdb_str) :
+    subtype_(0), server_(Name::ROOT_NAME())
+{
+    istringstream iss(afsdb_str);
+
+    try {
+        const uint32_t subtype = tokenToNum<int32_t, 16>(getToken(iss));
+        const Name servername(getToken(iss));
+        string server;
+
+        if (!iss.eof()) {
+            isc_throw(InvalidRdataText, "Unexpected input for AFSDB"
+                    "RDATA: " << afsdb_str);
+        }
+
+        subtype_ = subtype;
+        server_ = servername;
+
+    } catch (const StringTokenError& ste) {
+        isc_throw(InvalidRdataText, "Invalid AFSDB text: " <<
+                  ste.what() << ": " << afsdb_str);
+    }
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// This constructor doesn't check the validity of the second parameter (rdata
+/// length) for parsing.
+/// If necessary, the caller will check consistency.
+///
+/// \exception std::bad_alloc Memory allocation fails.
+/// \exception Other The constructor of the \c Name class will throw if the
+/// names in the wire is invalid.
+AFSDB::AFSDB(InputBuffer& buffer, size_t) :
+    subtype_(buffer.readUint16()), server_(buffer)
+{}
+
+/// \brief Copy constructor.
+///
+/// \exception std::bad_alloc Memory allocation fails in copying internal
+/// member variables (this should be very rare).
+AFSDB::AFSDB(const AFSDB& other) :
+    Rdata(), subtype_(other.subtype_), server_(other.server_)
+{}
+
+AFSDB&
+AFSDB::operator=(const AFSDB& source) {
+    subtype_ = source.subtype_;
+    server_ = source.server_;
+
+    return (*this);
+}
+
+/// \brief Convert the \c AFSDB to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c AFSDB(const std::string&))).
+///
+/// \exception std::bad_alloc Internal resource allocation fails.
+///
+/// \return A \c string object that represents the \c AFSDB object.
+string
+AFSDB::toText() const {
+    return (lexical_cast<string>(subtype_) + " " + server_.toText());
+}
+
+/// \brief Render the \c AFSDB in the wire format without name compression.
+///
+/// \exception std::bad_alloc Internal resource allocation fails.
+///
+/// \param buffer An output buffer to store the wire data.
+void
+AFSDB::toWire(OutputBuffer& buffer) const {
+    buffer.writeUint16(subtype_);
+    server_.toWire(buffer);
+}
+
+/// \brief Render the \c AFSDB in the wire format with taking into account
+/// compression.
+///
+/// As specified in RFC3597, TYPE AFSDB is not "well-known", the server
+/// field (domain name) will not be compressed.
+///
+/// \exception std::bad_alloc Internal resource allocation fails.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer and name compression information.
+void
+AFSDB::toWire(AbstractMessageRenderer& renderer) const {
+    renderer.writeUint16(subtype_);
+    renderer.writeName(server_, false);
+}
+
+/// \brief Compare two instances of \c AFSDB RDATA.
+///
+/// See documentation in \c Rdata.
+int
+AFSDB::compare(const Rdata& other) const {
+    const AFSDB& other_afsdb = dynamic_cast<const AFSDB&>(other);
+    if (subtype_ < other_afsdb.subtype_) {
+        return (-1);
+    } else if (subtype_ > other_afsdb.subtype_) {
+        return (1);
+    }
+
+    return (compareNames(server_, other_afsdb.server_));
+}
+
+const Name&
+AFSDB::getServer() const {
+    return (server_);
+}
+
+uint16_t
+AFSDB::getSubtype() const {
+    return (subtype_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE

+ 74 - 0
src/lib/dns/rdata/generic/afsdb_18.h

@@ -0,0 +1,74 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief \c rdata::AFSDB class represents the AFSDB RDATA as defined %in
+/// RFC1183.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// AFSDB RDATA.
+class AFSDB : public Rdata {
+public:
+    // BEGIN_COMMON_MEMBERS
+    // END_COMMON_MEMBERS
+
+    /// \brief Assignment operator.
+    ///
+    /// This method never throws an exception.
+    AFSDB& operator=(const AFSDB& source);
+    ///
+    /// Specialized methods
+    ///
+
+    /// \brief Return the value of the server field.
+    ///
+    /// \return A reference to a \c Name class object corresponding to the
+    /// internal server name.
+    ///
+    /// This method never throws an exception.
+    const Name& getServer() const;
+
+    /// \brief Return the value of the subtype field.
+    ///
+    /// This method never throws an exception.
+    uint16_t getSubtype() const;
+
+private:
+    uint16_t subtype_;
+    Name server_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:

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

@@ -32,6 +32,7 @@ run_unittests_SOURCES += rdata_ns_unittest.cc rdata_soa_unittest.cc
 run_unittests_SOURCES += rdata_txt_unittest.cc rdata_mx_unittest.cc
 run_unittests_SOURCES += rdata_ptr_unittest.cc rdata_cname_unittest.cc
 run_unittests_SOURCES += rdata_dname_unittest.cc
+run_unittests_SOURCES += rdata_afsdb_unittest.cc
 run_unittests_SOURCES += rdata_opt_unittest.cc
 run_unittests_SOURCES += rdata_dnskey_unittest.cc
 run_unittests_SOURCES += rdata_ds_unittest.cc

+ 210 - 0
src/lib/dns/tests/rdata_afsdb_unittest.cc

@@ -0,0 +1,210 @@
+// 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 <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+const char* const afsdb_text = "1 afsdb.example.com.";
+const char* const afsdb_text2 = "0 root.example.com.";
+const char* const too_long_label("012345678901234567890123456789"
+        "0123456789012345678901234567890123");
+
+namespace {
+class Rdata_AFSDB_Test : public RdataTest {
+protected:
+    Rdata_AFSDB_Test() :
+        rdata_afsdb(string(afsdb_text)), rdata_afsdb2(string(afsdb_text2))
+    {}
+
+    const generic::AFSDB rdata_afsdb;
+    const generic::AFSDB rdata_afsdb2;
+    vector<uint8_t> expected_wire;
+};
+
+
+TEST_F(Rdata_AFSDB_Test, createFromText) {
+    EXPECT_EQ(1, rdata_afsdb.getSubtype());
+    EXPECT_EQ(Name("afsdb.example.com."), rdata_afsdb.getServer());
+
+    EXPECT_EQ(0, rdata_afsdb2.getSubtype());
+    EXPECT_EQ(Name("root.example.com."), rdata_afsdb2.getServer());
+}
+
+TEST_F(Rdata_AFSDB_Test, badText) {
+    // subtype is too large
+    EXPECT_THROW(const generic::AFSDB rdata_afsdb("99999999 afsdb.example.com."),
+                 InvalidRdataText);
+    // incomplete text
+    EXPECT_THROW(const generic::AFSDB rdata_afsdb("10"), InvalidRdataText);
+    EXPECT_THROW(const generic::AFSDB rdata_afsdb("SPOON"), InvalidRdataText);
+    EXPECT_THROW(const generic::AFSDB rdata_afsdb("1root.example.com."), InvalidRdataText);
+    // number of fields (must be 2) is incorrect
+    EXPECT_THROW(const generic::AFSDB rdata_afsdb("10 afsdb. example.com."),
+                 InvalidRdataText);
+    // bad name
+    EXPECT_THROW(const generic::AFSDB rdata_afsdb("1 afsdb.example.com." +
+                string(too_long_label)), TooLongLabel);
+}
+
+TEST_F(Rdata_AFSDB_Test, assignment) {
+    generic::AFSDB copy((string(afsdb_text2)));
+    copy = rdata_afsdb;
+    EXPECT_EQ(0, copy.compare(rdata_afsdb));
+
+    // Check if the copied data is valid even after the original is deleted
+    generic::AFSDB* copy2 = new generic::AFSDB(rdata_afsdb);
+    generic::AFSDB copy3((string(afsdb_text2)));
+    copy3 = *copy2;
+    delete copy2;
+    EXPECT_EQ(0, copy3.compare(rdata_afsdb));
+
+    // Self assignment
+    copy = copy;
+    EXPECT_EQ(0, copy.compare(rdata_afsdb));
+}
+
+TEST_F(Rdata_AFSDB_Test, createFromWire) {
+    // uncompressed names
+    EXPECT_EQ(0, rdata_afsdb.compare(
+                  *rdataFactoryFromFile(RRType::AFSDB(), RRClass::IN(),
+                                     "rdata_afsdb_fromWire1.wire")));
+    // compressed name
+    EXPECT_EQ(0, rdata_afsdb.compare(
+                  *rdataFactoryFromFile(RRType::AFSDB(), RRClass::IN(),
+                                     "rdata_afsdb_fromWire2.wire", 13)));
+    // RDLENGTH is too short
+    EXPECT_THROW(rdataFactoryFromFile(RRType::AFSDB(), RRClass::IN(),
+                                     "rdata_afsdb_fromWire3.wire"),
+                 InvalidRdataLength);
+    // RDLENGTH is too long
+    EXPECT_THROW(rdataFactoryFromFile(RRType::AFSDB(), RRClass::IN(),
+                                      "rdata_afsdb_fromWire4.wire"),
+                 InvalidRdataLength);
+    // bogus server name, the error should be detected in the name
+    // constructor
+    EXPECT_THROW(rdataFactoryFromFile(RRType::AFSDB(), RRClass::IN(),
+                                      "rdata_afsdb_fromWire5.wire"),
+                 DNSMessageFORMERR);
+}
+
+TEST_F(Rdata_AFSDB_Test, toWireBuffer) {
+    // construct actual data
+    rdata_afsdb.toWire(obuffer);
+
+    // construct expected data
+    UnitTestUtil::readWireData("rdata_afsdb_toWire1.wire", expected_wire);
+
+    // then compare them
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        obuffer.getData(), obuffer.getLength(),
+                        &expected_wire[0], expected_wire.size());
+
+    // clear buffer for the next test
+    obuffer.clear();
+
+    // construct actual data
+    Name("example.com.").toWire(obuffer);
+    rdata_afsdb2.toWire(obuffer);
+
+    // construct expected data
+    UnitTestUtil::readWireData("rdata_afsdb_toWire2.wire", expected_wire);
+
+    // then compare them
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        obuffer.getData(), obuffer.getLength(),
+                        &expected_wire[0], expected_wire.size());
+}
+
+TEST_F(Rdata_AFSDB_Test, toWireRenderer) {
+    // similar to toWireBuffer, but names in RDATA could be compressed due to
+    // preceding names.  Actually they must not be compressed according to
+    // RFC3597, and this test checks that.
+
+    // construct actual data
+    rdata_afsdb.toWire(renderer);
+
+    // construct expected data
+    UnitTestUtil::readWireData("rdata_afsdb_toWire1.wire", expected_wire);
+
+    // then compare them
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        renderer.getData(), renderer.getLength(),
+                        &expected_wire[0], expected_wire.size());
+
+    // clear renderer for the next test
+    renderer.clear();
+
+    // construct actual data
+    Name("example.com.").toWire(obuffer);
+    rdata_afsdb2.toWire(renderer);
+
+    // construct expected data
+    UnitTestUtil::readWireData("rdata_afsdb_toWire2.wire", expected_wire);
+
+    // then compare them
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        renderer.getData(), renderer.getLength(),
+                        &expected_wire[0], expected_wire.size());
+}
+
+TEST_F(Rdata_AFSDB_Test, toText) {
+    EXPECT_EQ(afsdb_text, rdata_afsdb.toText());
+    EXPECT_EQ(afsdb_text2, rdata_afsdb2.toText());
+}
+
+TEST_F(Rdata_AFSDB_Test, compare) {
+    // check reflexivity
+    EXPECT_EQ(0, rdata_afsdb.compare(rdata_afsdb));
+
+    // name must be compared in case-insensitive manner
+    EXPECT_EQ(0, rdata_afsdb.compare(generic::AFSDB("1 "
+                                "AFSDB.example.com.")));
+
+    const generic::AFSDB small1("10 afsdb.example.com");
+    const generic::AFSDB large1("65535 afsdb.example.com");
+    const generic::AFSDB large2("256 afsdb.example.com");
+
+    // confirm these are compared as unsigned values
+    EXPECT_GT(0, rdata_afsdb.compare(large1));
+    EXPECT_LT(0, large1.compare(rdata_afsdb));
+
+    // confirm these are compared in network byte order
+    EXPECT_GT(0, small1.compare(large2));
+    EXPECT_LT(0, large2.compare(small1));
+
+    // another AFSDB whose server name is larger than that of rdata_afsdb.
+    const generic::AFSDB large3("256 zzzzz.example.com");
+    EXPECT_GT(0, large2.compare(large3));
+    EXPECT_LT(0, large3.compare(large2));
+
+    // comparison attempt between incompatible RR types should be rejected
+    EXPECT_THROW(rdata_afsdb.compare(*rdata_nomatch), bad_cast);
+}
+}

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

@@ -30,6 +30,10 @@ BUILT_SOURCES += rdata_rp_fromWire1.wire rdata_rp_fromWire2.wire
 BUILT_SOURCES += rdata_rp_fromWire3.wire rdata_rp_fromWire4.wire
 BUILT_SOURCES += rdata_rp_fromWire5.wire rdata_rp_fromWire6.wire
 BUILT_SOURCES += rdata_rp_toWire1.wire rdata_rp_toWire2.wire
+BUILT_SOURCES += rdata_afsdb_fromWire1.wire rdata_afsdb_fromWire2.wire
+BUILT_SOURCES += rdata_afsdb_fromWire3.wire rdata_afsdb_fromWire4.wire
+BUILT_SOURCES += rdata_afsdb_fromWire5.wire
+BUILT_SOURCES += rdata_afsdb_toWire1.wire rdata_afsdb_toWire2.wire
 BUILT_SOURCES += rdata_soa_toWireUncompressed.wire
 BUILT_SOURCES += rdata_txt_fromWire2.wire rdata_txt_fromWire3.wire
 BUILT_SOURCES += rdata_txt_fromWire4.wire rdata_txt_fromWire5.wire
@@ -99,6 +103,10 @@ EXTRA_DIST += rdata_rp_fromWire1.spec rdata_rp_fromWire2.spec
 EXTRA_DIST += rdata_rp_fromWire3.spec rdata_rp_fromWire4.spec
 EXTRA_DIST += rdata_rp_fromWire5.spec rdata_rp_fromWire6.spec
 EXTRA_DIST += rdata_rp_toWire1.spec rdata_rp_toWire2.spec
+EXTRA_DIST += rdata_afsdb_fromWire1.spec rdata_afsdb_fromWire2.spec
+EXTRA_DIST += rdata_afsdb_fromWire3.spec rdata_afsdb_fromWire4.spec
+EXTRA_DIST += rdata_afsdb_fromWire5.spec
+EXTRA_DIST += rdata_afsdb_toWire1.spec rdata_afsdb_toWire2.spec
 EXTRA_DIST += rdata_soa_fromWire rdata_soa_toWireUncompressed.spec
 EXTRA_DIST += rdata_srv_fromWire
 EXTRA_DIST += rdata_txt_fromWire1 rdata_txt_fromWire2.spec

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

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

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

@@ -0,0 +1,6 @@
+[custom]
+sections: name:afsdb
+[name]
+name: example.com
+[afsdb]
+server: afsdb.ptr=0

+ 4 - 0
src/lib/dns/tests/testdata/rdata_afsdb_fromWire3.spec

@@ -0,0 +1,4 @@
+[custom]
+sections: afsdb
+[afsdb]
+rdlen: 3

+ 4 - 0
src/lib/dns/tests/testdata/rdata_afsdb_fromWire4.spec

@@ -0,0 +1,4 @@
+[custom]
+sections: afsdb
+[afsdb]
+rdlen: 80

+ 4 - 0
src/lib/dns/tests/testdata/rdata_afsdb_fromWire5.spec

@@ -0,0 +1,4 @@
+[custom]
+sections: afsdb
+[afsdb]
+server: "01234567890123456789012345678901234567890123456789012345678901234"

+ 4 - 0
src/lib/dns/tests/testdata/rdata_afsdb_toWire1.spec

@@ -0,0 +1,4 @@
+[custom]
+sections: afsdb
+[afsdb]
+rdlen: -1

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

@@ -0,0 +1,8 @@
+[custom]
+sections: name:afsdb
+[name]
+name: example.com.
+[afsdb]
+subtype: 0
+server: root.example.com
+rdlen: -1

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

@@ -822,6 +822,27 @@ class RP(RR):
         f.write('# MAILBOX=%s TEXT=%s\n' % (self.mailbox, self.text))
         f.write('%s %s\n' % (mailbox_wire, text_wire))
 
+class AFSDB(RR):
+    '''Implements rendering AFSDB RDATA in the test data format.
+
+    Configurable parameters are as follows (see the description of the
+    same name of attribute for the default value):
+    - subtype (16 bit int): The subtype field.
+    - server (string): The server field.
+    The string must be interpreted as a valid domain name.
+    '''
+    subtype = 1
+    server = 'afsdb.example.com'
+    def dump(self, f):
+        server_wire = encode_name(self.server)
+        if self.rdlen is None:
+            self.rdlen = 2 + len(server_wire) / 2
+        else:
+            self.rdlen = int(self.rdlen)
+        self.dump_header(f, self.rdlen)
+        f.write('# SUBTYPE=%d SERVER=%s\n' % (self.subtype, self.server))
+        f.write('%04x %s\n' % (self.subtype, server_wire))
+
 class NSECBASE(RR):
     '''Implements rendering NSEC/NSEC3 type bitmaps commonly used for
     these RRs.  The NSEC and NSEC3 classes will be inherited from this