Index: names/common.py
===================================================================
--- names/common.py	(revision 33585)
+++ names/common.py	(working copy)
@@ -18,6 +18,65 @@
 
 EMPTY_RESULT = (), (), ()
 
+class DnssecConfig():
+    """
+    Sets recDes and the DNSSEC parameters. See RFC's 4033, 4034 and 4035.
+
+    recDes (RD)   - Recursion Desired. Not a DNSSEC parameter and does not require ednsEnabled,
+                    but still nice to be able to control whether or not you're asking for recursion.
+                    (bool)
+
+    ednsEnabled   - EDNS Enabled adds an OPT record to the query. The OPT record contains a
+                    version field that indicates the version of EDNS that you can handle.
+                    Presently, only EDNS Version 0 is defined.
+                    (bool)
+                    
+    maxUdpPktSz   - The max size UDP packet (bytes) that your end-to-end network can handle
+                    (on a modern network a reliable size is 1492, although up to 65535 is possible).
+                    Requires ednsEnabled.
+                    (int)
+                    
+    dnssecOK (DO) - Dnssec Ok. A bit that indicates you want DNSSEC RR's and validation 
+                    if the resolver validates. If DO is set, AD will be set in the response 
+                    if the answer validates. Requires ednsEnabled.
+                    (bool)
+
+    chkDis (CD)   - Checking Disabled. If DO and CD are set, a validating resolver won't do
+                    validation but will return the DNSSEC RR's so that YOU can.
+                    (bool)
+                    
+    version       - sets the edns version level. Currently, only 0 is defined and supported.
+                    (int)
+                    
+    authData (AD) - Authentic Data. If set in a response from a validating resolver that you can
+                    trust, indicates that the validating resolver validated the answer. Requires 
+                    ednsEnabled and DO set. (See note below.)
+                    (bool)
+                    
+    Note - RFC-4035 does not define AD for a query. However, most validating resolvers seem 
+    to honor it set in a query and return AD set in a response to a query that validates. But 
+    you should not to rely on this behavior - set DO instead.
+    """
+    def __init__(self, 
+                 recDes=True,
+                 ednsEnabled=False, 
+                 maxUdpPktSz=512, 
+                 dnssecOk=False,
+                 chkDis=False,
+                 version=0,
+                 authData=False):
+
+        self.recDes = recDes
+        self.ednsEnabled = ednsEnabled
+        self.maxUdpPktSz = maxUdpPktSz
+        self.dnssecOk = dnssecOk
+        self.chkDis = chkDis
+        self.version = version
+        self.authData = authData
+
+        assert not self.ednsEnabled or 512 <= self.maxUdpPktSz <= 65535
+        assert version == 0
+
 class ResolverBase:
     """
     L{ResolverBase} is a base class for L{IResolver} implementations which
@@ -36,12 +95,14 @@
 
     typeToMethod = None
 
-    def __init__(self):
+    def __init__(self, dnssecConfig=None):
         self.typeToMethod = {}
         for (k, v) in typeToMethod.items():
             self.typeToMethod[k] = getattr(self, v)
+        self.dnssecConfig = dnssecConfig
+        if self.dnssecConfig == None:
+            self.dnssecConfig = DnssecConfig()
 
-
     def exceptionForCode(self, responseCode):
         """
         Convert a response code (one of the possible values of
@@ -200,6 +261,12 @@
         @see: twisted.names.client.lookupAllRecords
         """
         return self._lookup(name, dns.IN, dns.ALL_RECORDS, timeout)
+    
+    def lookupDNSKey(self, name, timeout=None):
+        """
+        @see: twisted.names.client.lookupDNSKey
+        """
+        return self._lookup(name, dns.IN, dns.DNSKEY, timeout)
 
     def getHostByName(self, name, timeout = None, effort = 10):
         """
@@ -268,7 +335,7 @@
     dns.MX:    'lookupMailExchange',
     dns.TXT:   'lookupText',
     dns.SPF:   'lookupSenderPolicy',
-
+    dns.DNSKEY:'lookupDNSKey',
     dns.RP:    'lookupResponsibility',
     dns.AFSDB: 'lookupAFSDatabase',
     dns.SRV:   'lookupService',
Index: names/dns.py
===================================================================
--- names/dns.py	(revision 33585)
+++ names/dns.py	(working copy)
@@ -15,9 +15,10 @@
 __all__ = [
     'IEncodable', 'IRecord',
 
-    'A', 'A6', 'AAAA', 'AFSDB', 'CNAME', 'DNAME', 'HINFO',
+    'A', 'A6', 'AAAA', 'AFSDB', 'CNAME', 'DNAME', 'DNSKEY', 'DS', 'HINFO',
     'MAILA', 'MAILB', 'MB', 'MD', 'MF', 'MG', 'MINFO', 'MR', 'MX',
-    'NAPTR', 'NS', 'NULL', 'PTR', 'RP', 'SOA', 'SPF', 'SRV', 'TXT', 'WKS',
+    'NAPTR', 'NS', 'NSEC', 'NSEC3', 'NSEC3PARAM', 'NULL', 'OPT', 'PTR', 
+    'RP', 'RRSIG', 'SOA', 'SPF', 'SRV', 'TXT', 'WKS',
 
     'ANY', 'CH', 'CS', 'HS', 'IN',
 
@@ -26,14 +27,15 @@
     'EFORMAT', 'ENAME', 'ENOTIMP', 'EREFUSED', 'ESERVER',
 
     'Record_A', 'Record_A6', 'Record_AAAA', 'Record_AFSDB', 'Record_CNAME',
-    'Record_DNAME', 'Record_HINFO', 'Record_MB', 'Record_MD', 'Record_MF',
+    'Record_DNAME', 'Record_DNSKEY', 'Record_DS', 'Record_HINFO', 'Record_MB', 
+    'Record_MD', 'Record_MF',
     'Record_MG', 'Record_MINFO', 'Record_MR', 'Record_MX', 'Record_NAPTR',
-    'Record_NS', 'Record_NULL', 'Record_PTR', 'Record_RP', 'Record_SOA',
-    'Record_SPF', 'Record_SRV', 'Record_TXT', 'Record_WKS', 'UnknownRecord',
+    'Record_NS', 'Record_NSEC', 'Record_NSEC3', 'Record_NULL', 'Record_OPT', 'Record_PTR', 'Record_RP', 'Record_RRSIG', 
+    'Record_SOA', 'Record_SPF', 'Record_SRV', 'Record_TXT', 'Record_WKS', 'UnknownRecord',
 
     'QUERY_CLASSES', 'QUERY_TYPES', 'REV_CLASSES', 'REV_TYPES', 'EXT_QUERIES',
 
-    'Charstr', 'Message', 'Name', 'Query', 'RRHeader', 'SimpleRecord',
+    'Charstr', 'Message', 'Name', 'OPTHeader', 'Query', 'RRHeader', 'SimpleRecord',
     'DNSDatagramProtocol', 'DNSMixin', 'DNSProtocol',
 
     'OK', 'OP_INVERSE', 'OP_NOTIFY', 'OP_QUERY', 'OP_STATUS', 'OP_UPDATE',
@@ -46,6 +48,8 @@
 # System imports
 import warnings
 
+import re
+
 import struct, random, types, socket
 
 import cStringIO as StringIO
@@ -54,13 +58,16 @@
 
 from zope.interface import implements, Interface, Attribute
 
+from base64 import b64decode, b64encode
 
+
 # Twisted imports
 from twisted.internet import protocol, defer
 from twisted.internet.error import CannotListenError
 from twisted.python import log, failure
 from twisted.python import util as tputil
 from twisted.python import randbytes
+from twisted.names.ser_num_arith import SNA, DateSNA
 
 
 def randomSource():
@@ -79,6 +86,14 @@
 NAPTR = 35
 A6 = 38
 DNAME = 39
+OPT = 41
+DS = 43
+RRSIG = 46
+NSEC = 47
+DNSKEY = 48
+NSEC3 = 50
+NSEC3PARAM = 51
+
 SPF = 99
 
 QUERY_TYPES = {
@@ -108,7 +123,14 @@
     NAPTR: 'NAPTR',
     A6: 'A6',
     DNAME: 'DNAME',
-    SPF: 'SPF'
+    OPT: 'OPT',
+    DS: 'DS',
+    RRSIG: 'RRSIG',
+    NSEC: 'NSEC',
+    DNSKEY: 'DNSKEY',
+    NSEC3: 'NSEC3', 
+    NSEC3PARAM: 'NSEC3PARAM',
+    SPF: 'SPF',
 }
 
 IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256)
@@ -277,8 +299,6 @@
     def __str__(self):
         return self.string
 
-
-
 class Name:
     implements(IEncodable)
 
@@ -370,7 +390,187 @@
 
     def __str__(self):
         return self.name
+    
+class Sigstr(object):
+    '''
+    for signatures and keys. display as b64 encoded
+    '''
+    implements(IEncodable)
+    
+    def __init__(self, string=''):
+        if not isinstance(string, str):
+            raise ValueError("%r is not a string" % (string, ))
+        self.string = string #b64encoded string
+        
+    def encode(self, strio, compDict=None):
+        '''
+        Write the byte representation (the un-b64-encoded string)
+        to the file.
+        
+        @type strio: file
+        @param srio: The byte representation of this signature or key
+        will be written to this file
+        
+        @type compDict: dict
+        @param compDict: not used.
+        '''
+        strio.write(b64decode(self.string))
+        
+    def decode(self, strio, length=None):
+        '''
+        Decode a signature or a key.
 
+        @type strio: file
+        @param strio: Exactly length bytes will be read from this file 
+        to decode the full signature or key
+        
+        @type length: int
+        @param lenth: length must always be given. A signature or key
+        is always the last thing in an RR and so you can always determine
+        its length. 
+        '''
+        self.string = ''
+        if length == None:
+            return
+        
+        assert isinstance(length, int)
+        buff = readPrecisely(strio, length)
+        self.string = b64encode(buff)
+
+    def __eq__(self, other):
+        if isinstance(other, Sigstr):
+            return self.string == other.string
+        return False
+
+    def __hash__(self):
+        return hash(self.string)
+
+    def __str__(self):
+        return self.string
+    
+
+class TypeBitmaps(object):
+    '''
+    bitmap encoding scheme used by NSEC and NSEC3 RR's
+    to indicate the RRset types that exist at the 
+    NSEC/NSEC3 RR's original owner name or hashed name.  
+    See RFC 4034 and RFC 5155.
+    '''
+    fmt = 'BB'
+    typeRegex = re.compile('TYPE(\d+)')
+    
+    def __init__(self, string=''):
+        self.string = string
+        
+    def encode(self, strio, compDict=None):
+        """
+        Encode the string field, which consists of a set 
+        of type names, into an NSEC/NSEC3 type bitmap.
+        
+        @type strio: file
+        @param strio: the byte representation of the type bitmap
+        will be written to this file.
+        
+        @type compDict: dict
+        @param compDict: not used.
+        """
+        if not self.string:
+            return;
+        
+        #get a sorted list of RR Type Values
+        mnus = self.string.split(' ')
+        mnuVals = []
+        for mnu in mnus:
+            mnuVal = REV_TYPES.get(mnu, None)
+            if not mnuVal:
+                m = self.typeRegex.match(mnu)
+                if m.groups():
+                    mnuVal = int(m.group(1))
+                    assert mnuVal < 65536
+                else:
+                    log.err("can't parse %s in %s" % (mnu, self.string, ))
+                    continue;
+            mnuVals.append(mnuVal)
+        mnuVals.sort()
+        
+        #convert that to a dict of windows and lists
+        windDict = {}
+        for v in mnuVals:
+            window = (v >> 8) & 0xFF
+            if window not in windDict:
+                windDict[window] = []
+            windDict[window].append(v & 0xFF)
+        
+        #have to sort the keys - they're not in order!
+        windows = windDict.keys()
+        windows.sort()
+
+        #create the bitmaps
+        bmap = bytearray()
+        for w in windows:
+            bmapseg = bytearray(32)
+            maxoff = 0
+            for v in windDict[w]:
+                vm1 = v - 1
+                off = vm1 >> 3
+                bit = vm1 & 0x7
+                msk = 1 << bit
+                bmapseg[off] |= msk
+                maxoff = max(off, maxoff)
+            bmapseg = bmapseg[0:maxoff+1]
+            bmap += chr(w) + chr(maxoff+1) + bmapseg
+        
+        strio.write(str(bmap))
+    
+    def decode(self, strio, length=None):
+        """
+        Decode an NSEC/NSEC3 type bitmap into a string
+        representation of type names.
+        """
+        self.type_bitmaps = ""
+        if length == None:
+            return
+
+        type_bitmaps = bytearray()
+        l = struct.calcsize(self.fmt)
+        parsed_length = 0
+        while parsed_length < length:
+            buff = readPrecisely(strio, l)
+            wb_num, bm_len = struct.unpack(self.fmt, buff)
+            assert parsed_length + 2 + bm_len <= length
+            bm = readPrecisely(strio, bm_len)
+            byteNum = -1
+            for b in bm:
+                byteNum += 1
+                ob = ord(b)
+                if ob == 0:
+                    continue
+                
+                for v in range(8):
+                    msk = 1<<v
+                    if ob & msk:
+                        val = wb_num*256 + byteNum*8 + v + 1
+                        mnu = QUERY_TYPES.get(val, None)
+                        if not mnu:
+                            mnu = 'TYPE' + str(val)
+                        type_bitmaps += (mnu + ' ')                        
+            
+            parsed_length += 2 + bm_len
+            
+        self.type_bitmaps = str(type_bitmaps[0:-1])
+
+    def __eq__(self, other):
+        if isinstance(other, TypeBitmaps):
+            return self.string == other.string
+        return False
+
+    def __hash__(self):
+        return hash(self.string)
+
+    def __str__(self):
+        return self.string
+
+
 class Query:
     """
     Represent a single DNS query.
@@ -434,8 +634,104 @@
         return 'Query(%r, %r, %r)' % (str(self.name), self.type, self.cls)
 
 
-class RRHeader(tputil.FancyEqMixin):
+    
+class OPTHeader(tputil.FancyEqMixin):
     """
+    A OPT record header.
+
+    @cvar fmt: C{str} specifying the byte format of an OPT Header.
+
+    @ivar name: Root (0, 8-bits)
+    @ivar type: 41 (OPT Record)
+    @ivar payload: An object that implements the IEncodable interface
+    @ivar auth: Whether this header is authoritative or not.
+    """
+
+    implements(IEncodable)
+
+    compareAttributes = ('name', 'type', 'payload', 'auth')
+
+    fmt = "!H"
+
+    name = None
+    type = None
+    payload = None
+
+    #OPTHeader _really_ has no ttl or rdlength, but the
+    #existence of the attributes is required.
+    ttl = None
+    rdlength = None
+
+    cachedResponse = None
+
+    def __init__(self, payload=None, auth=False):
+        """
+        @type name: C{str}
+        @param name: Root (0)
+
+        @type type: C{int}
+        @param type: Query type 41.
+
+        @type payload: An object implementing C{IEncodable}
+        @param payload: The OPT payload
+        """
+        assert (payload is None) or (payload.TYPE == OPT)
+
+        self.name = 0
+        self.type = OPT
+        self.payload = payload
+        self.auth = auth
+
+    def encode(self, strio, compDict=None):
+        strio.write(struct.pack('!B', 0))
+        strio.write(struct.pack(self.fmt, self.type))
+        if self.payload:
+            prefix = strio.tell()
+            self.payload.encode(strio, compDict)
+            aft = strio.tell()
+            strio.seek(prefix - 2, 0)
+            strio.write(struct.pack('!H', aft - prefix))
+            strio.seek(aft, 0)
+
+    def decode(self, strio, length = None):
+        self.name.decode(strio)
+        l = struct.calcsize(self.fmt)
+        buff = readPrecisely(strio, l)
+        r = struct.unpack(self.fmt, buff)
+        self.type = r[0]
+
+    def isAuthoritative(self):
+        return self.auth
+
+    def __str__(self):
+        return '<OPT auth=%s>' % (self.auth and 'True' or 'False')
+
+    @staticmethod
+    def factory(strio, auth=False):
+        '''
+        reads enough of the stream to figure out if what is there is
+        an OPTHeader or an RRHeader
+        '''
+        beginPos = strio.tell()
+        name = Name()
+        name.decode(strio)
+        type = struct.unpack(OPTHeader.fmt, readPrecisely(strio, 2))[0]
+        
+        if len(name.name) == 0 and type == OPT:
+            return OPTHeader()
+        else:
+            #back up to the beginning and try again
+            strio.seek(beginPos, 0)
+            rrh = RRHeader(auth=auth)
+            rrh.decode(strio)
+            rrh.auth = auth
+            return rrh
+        
+    __repr__ = __str__
+    
+    
+class RRHeader(OPTHeader):
+    """
     A resource record header.
 
     @cvar fmt: C{str} specifying the byte format of an RR.
@@ -1469,7 +1765,463 @@
         return hash(tuple(self.data))
 
 
+class Record_OPT(tputil.FancyEqMixin, tputil.FancyStrMixin):
+    """
+    EDNS0 Option record.
+    
+    @type payload_size: C{int}
+    @ivar payload_size: Specifies the max UDP Packet size (bytes) that your
+        network can handle.
+    
+    @type dnssecOk: C{bool}
+    @ivar dnssecOk: Requests the server to send DNSSEC RRs and to do DNSSEC
+        validation (and set the AD bit if the response validates).
+        
+    @type version: C{int}
+    @ivar version: The version of DNSSEC used. Currently only version 0 
+        is defined.
+    """
+    implements(IEncodable, IRecord)
+    TYPE = OPT
+    fmt = '!HBBHH'
+    
+    fancybasename = 'OPT'
+    showAttributes = ('payload_size', ('flags', 'flags', '0x%x'), 'version')
+    compareAttributes = ('payload_size', 'flags', 'version')
+    
+    def __init__(self, payload_size=512, dnssecOk=0, version=0, ttl=None):
+        self.payload_size = payload_size
+        self.version = version
+        self.flags = (dnssecOk & 1) << 15
+        
+    def encode(self, strio, compDict = None):
+        OPTHeader().encode(strio)
+        strio.write(struct.pack('!H', self.payload_size))
+        strio.write(struct.pack('!B', 0)) # high order 0
+        strio.write(struct.pack('!B', self.version))
+        strio.write(struct.pack('!H', self.flags)) # DO(bit) + Z's
+        strio.write(struct.pack('!H', 0)) # Data length: 0
+        
+    def decode(self, strio, length=None):
+        '''
+        are OPT Records always 0 rdlength?
+        '''
+        l = struct.calcsize(self.fmt)
+        buff = readPrecisely(strio, l)
+        r = struct.unpack(self.fmt, buff)
+        self.payload_size, z, self.version, self.flags, length = r
+        assert length == 0
+        
+    def __hash__(self):
+        return hash((self.payload_size, self.version, self.flags))
 
+class Record_RRSIG(tputil.FancyEqMixin, tputil.FancyStrMixin):
+    """
+    DNSSEC RRSIG record.  See RFC 4034 for details.
+    
+    @type type_covered: C{int}
+    @ivar type_covered: Identifies the type of the RRset that this RRSIG covers. 
+    
+    @type algo: C{int}
+    @ivar algo: Identifies the crypto algorithm type used to create the signature.
+        (5 - RSH/SHA-1 is mandatory. See RFC 4034 App A.1 for the full list.)
+    
+    @type labels: C{int}
+    @ivar labels: Specifies the number of labels in the original RRSIG RR owner name.
+        A validator can use this to determine whether the answer was synthesized from
+        a wildcard.
+    
+    @type original_ttl: C{int}
+    @ivar original_ttl: Specifies the TTL of the covered RRset as it appears in the 
+        authoritative zone.
+    
+    @type sig_expiration: C{int} 
+    @ivar sig_expiration: This RRSIG must NOT be used after this time. Seconds
+        since 1/1/1970, Modulo 2**32, compare using DNS Serial Number Arithmetic
+    
+    @type sig_inception: C{int} 
+    @ivar sig_inception: This RRSIG must NOT be used prior to this time. Seconds
+        since 1/1/1970, Modulo 2**32, compare using DNS Serial Number Arithmetic
+    
+    @type key_tag: C{int}
+    @ivar key_tag: Contains the key tag value of the DNSKEY RR that validates this
+        signature, in network byte order. See RFC 4034 App B.
+    
+    @type signers_name: L{Name}
+    @ivar signers_name: Identifies the owner name of the DNSKEY RR that a validator
+        should use to validate this signature. Must not use DNS name compression.
+    
+    @type signature: L{Sigstr}
+    @ivar signature: Contains the cryptographic signature that covers the RRSIG
+        RDATA (excluding the Signature field and the RRset specified by the  RRSIG
+        owner name, RRSIG class and RRSIG Type Covered fields.
+    
+    @type ttl: C{int}
+    @ivar ttl: The maximum number of seconds which this record should be
+        cached.
+    """
+    implements(IEncodable, IRecord)
+    TYPE = RRSIG
+    fmt = '!HBBIIIH'
+    
+    fancybasename = 'RRSIG'
+    showAttributes = ('type_covered', 'algo', 'labels', 'original_ttl', 
+                         ('_sig_expiration', 'sig_expiration', '%s'),
+                         ('_sig_inception', 'sig_inception', '%s'),
+                         'key_tag', 
+                         ('signers_name', 'signers_name', '%s'), 
+                         ('_signature', 'signature', '%s'), 'ttl')
+    compareAttributes = ('type_covered', 'algo', 'labels', 'original_ttl', 
+                         'sig_expiration', 'sig_inception', 'key_tag', 
+                         'signers_name', 'signature', 'ttl')
+    
+    _sig_expiration = property(lambda self: str(self.sig_expiration))
+    _sig_inception = property(lambda self: str(self.sig_inception))
+    _signature = property(lambda self: self.signature.string)
+    
+    def __init__(self, type_covered=A, algo=0, labels=0, original_ttl=0, 
+                 sig_expiration='', sig_inception='', key_tag=0, 
+                 signers_name='', signature='', ttl=None):
+        self.type_covered = type_covered
+        self.algo = algo
+        self.labels = labels
+        self.original_ttl = original_ttl
+        self.sig_expiration = DateSNA(sig_expiration)
+        self.sig_inception = DateSNA(sig_inception)
+        self.key_tag = key_tag
+        self.signers_name = Name(signers_name)
+        self.signature = Sigstr(signature)
+        self.ttl = str2time(ttl)
+        
+    def encode(self, strio, compDict = None):
+        strio.write(struct.pack(self.fmt, 
+                                self.type_covered,
+                                self.algo, 
+                                self.labels,
+                                self.original_ttl,
+                                self.sig_expiration.asInt(),
+                                self.sig_inception.asInt(),
+                                self.key_tag))
+        self.signers_name.encode(strio, None)
+        self.signature.encode(strio, compDict)        
+        
+    def decode(self, strio, length=None):
+        start = strio.tell()
+        l = struct.calcsize(self.fmt)
+        buff = readPrecisely(strio, l)
+        r = struct.unpack(self.fmt, buff)        
+        self.type_covered, self.algo, self.labels, self.original_ttl, \
+            sig_expiration, sig_inception, self.key_tag = r 
+        self.sig_expiration = DateSNA.fromInt(sig_expiration)
+        self.sig_inception = DateSNA.fromInt(sig_inception)
+        self.signers_name.decode(strio)
+        here = strio.tell()
+        self.signature.decode(strio, length + start - here if length else None)
+                
+    def __hash__(self):
+        return hash((self.type_covered, self.algo, self.labels, self.original_ttl,
+                     self.sig_expiration, self.sig_inception, self.key_tag,
+                     self.signers_name, self.signature))    
+
+class Record_DS(tputil.FancyEqMixin, tputil.FancyStrMixin):
+    """
+    A DNSSEC DS record.
+    
+    @type key_tag: C{int}
+    @ivar key_tag: Lists the key tag of the DNSKEY RR referred to by this DS record.
+    
+    @type algo: C{int}
+    @ivar algo: Lists the algorithm number of the DNSKEY RR referenced by this DS record.    
+
+    @type digest_type: C{int}
+    @ivar digest_type: Identifies the algorithm used to construct the digest field.
+    
+    @type digest: L{Sigstr}
+    @ivar digest: Contains a digest of the refeerenced DNSKEY RR calculated by the
+        algorithm identified by the digest_type field.
+
+    @type ttl: C{int}
+    @ivar ttl: The maximum number of seconds which this record should be
+        cached.    
+    """
+    implements(IEncodable, IRecord)
+    TYPE = DS
+    fmt = '!HBB'
+    
+    fancybasename = 'DS'
+    showAttributes = ('key_tag', 'algo', 'digest_type', ('_digest', 'digest', '%s'), 'ttl')
+    compareAttributes = ('key_tag', 'algo', 'digest_type', 'digest', 'ttl')
+    
+    _digest = property(lambda self: self.digest.string)
+
+    def __init__(self, key_tag=0, algo=0, digest_type=0, digest='', ttl=None):
+        self.key_tag = key_tag
+        self.algo = algo
+        self.digest_type = digest_type
+        self.digest = Sigstr(digest)
+        self.ttl = str2time(ttl)
+        
+    def encode(self, strio, compDict = None):
+        strio.write(struct.pack(self.fmt, 
+                                self.key_tag,
+                                self.algo, 
+                                self.digest_type))
+        self.digest.encode(strio, None)
+        
+    def decode(self, strio, length=None):
+        start = strio.tell()
+        l = struct.calcsize(self.fmt)
+        buff = readPrecisely(strio, l)
+        r = struct.unpack(self.fmt, buff)        
+        self.key_tag, self.algo, self.digest_type = r        
+        here = strio.tell()
+        self.digest.decode(strio, length + start - here if length else None)
+                
+    def __hash__(self):
+        return hash((self.key_tag, self.algo, self.digest_type, self.digest))    
+
+class Record_DNSKEY(tputil.FancyEqMixin, tputil.FancyStrMixin):
+    """
+    A DNSSEC DNSKEY record. Holds the public key for a signed RRset.
+    
+    @type flags: C{int}
+    @ivar flags: Bit 7 is the Zone Key flag. If bit 7 has value 1, then the
+        DNSKEY record holds a DNS zone key and the DNSKEY RR's owner name is
+        the name of a zone.  If bit 7 has value 0, then the DNSKEY record
+        holds some other type of DNS public key and MUST NOT be used to 
+        verify RRSIGs that cover RRsets.  
+        Bit 15 is the Secure Entry Point flag. See RFC 3757.) 
+        All other bits are reserved and must be zero.
+        
+    @type protocol: C{int}
+    @ivar protocol: Must have value 3. The DNSKEY RR must be treated as invalid
+        if this field does not contain 3.
+    
+    @type algo: C{int}
+    @ivar algo: Identifies the public key's cryptographic algorithm and determines
+        the format of the pub_key field.  See RFC 4034 App A.
+    
+    @type pub_key: L{Sigstr}
+    @ivar pub_key: Holds the public key material.
+
+    @type ttl: C{int}
+    @ivar ttl: The maximum number of seconds which this record should be
+        cached.    
+    """
+    implements(IEncodable, IRecord)
+    TYPE = DNSKEY
+    fmt = '!HBB'
+    
+    fancybasename = 'DNSKEY'
+    showAttributes = ('flags', 'protocol', 'algo', ('_pub_key', 'pub_key', '%s'), 'ttl')
+    compareAttributes = ('flags', 'protocol', 'algo', 'pub_key', 'ttl')
+    
+    _pub_key = property(lambda self: self.pub_key.string)
+    
+    def __init__(self, flags=0, protocol=0, algo=0, pub_key='', ttl=None):
+        self.flags = flags
+        self.protocol = protocol
+        self.algo = algo
+        self.pub_key = Sigstr(pub_key)
+        self.ttl = str2time(ttl)
+        
+    def encode(self, strio, compDict = None):
+        strio.write(struct.pack(self.fmt, 
+                                self.flags,
+                                self.protocol, 
+                                self.algo))
+        self.pub_key.encode(strio, None)
+        
+    def decode(self, strio, length=None):
+        start = strio.tell()
+        l = struct.calcsize(self.fmt)
+        buff = readPrecisely(strio, l)
+        r = struct.unpack(self.fmt, buff)        
+        self.flags, self.protocol, self.algo = r        
+        here = strio.tell()
+        self.pub_key.decode(strio, length + start - here if length else None)
+                
+    def __hash__(self):
+        return hash((self.flags, self.protocol, self.algo, self.pub_key))
+
+class Record_NSEC(tputil.FancyEqMixin, tputil.FancyStrMixin):
+    """
+    A DNSSEC NSEC record provides authenticated denial of existance for DNS data. 
+
+    A DNSSEC NSEC record lists:
+    
+        1) the next owner name in canonical ordering of the zone that contains authoritataive
+           data or a delegation point NS RRset.
+        
+        2) the set of RR types present at the NSEC RR's owner name.
+
+    @type nxt_name: L{Name}
+    @ivar nxt_name: The next owner name that has authoritative data or contains a
+        delegation point NS RRset.
+    
+    @type type_bitmaps: L{TypeBitmaps}
+    @ivar type_bitmaps: Identifies the RRset types that exist at the NSEC RR's owner name.
+    
+    @type ttl: C{int}
+    @ivar ttl: The maximum number of seconds which this record should be
+        cached.
+    """
+    implements(IEncodable, IRecord)
+    TYPE = NSEC
+    
+    fancybasename = 'NSEC'
+    showAttributes = (('nxt_name', 'nxt_name', '%s'), ('_type_bitmaps', 'type_bitmaps', '%s'), 'ttl')
+    compareAttributes = ('nxt_name', 'type_bitmaps', 'ttl')
+    
+    _type_bitmaps = property(lambda self: self.type_bitmaps.string)
+    
+    def __init__(self, nxt_name='', type_bitmaps=None, ttl=None):
+        self.nxt_name = Name(nxt_name)
+        self.type_bitmaps = TypeBitmaps(type_bitmaps)
+        self.ttl = str2time(ttl)
+        
+    def encode(self, strio, compDict = None):
+        self.nxt_name.encode(strio, None)
+        self.type_bitmaps.encode(strio, None)
+        
+    def decode(self, strio, length=None):
+        start = strio.tell()
+        self.nxt_name.decode(strio, None)
+        here = strio.tell()
+        self.type_bitmaps.decode(strio, length + start - here if length else None)
+                
+    def __hash__(self):
+        return hash((self.nxt_name, self.type_bitmaps))
+
+class Record_NSEC3PARAM(tputil.FancyEqMixin, tputil.FancyStrMixin):
+    """
+    A DNSSEC NSEC3PARAM record contains the NSEC3 parameters (hash algorithm,
+    flags, iterations and salt) needed by authoritative servers to calculate
+    hashed owner names.  The presence of an NSEC3PARAM RR at a zone apex
+    indicates that the specified parameters may be used by authoritative 
+    servers to choose an appropriate set of NSEC3 RRs for negative responses. 
+
+    @type hash_algo: C{int}
+    @ivar hash_algo: Identifies the cryptographic hash algorithm used to construct the hash value.
+    
+    @type flags: C{int}
+    @ivar flags: Identifies 8 1-bit flags. The only flag presently defined is the 
+        opt-out flag. If the opt-out flag is set, the NSEC3 record covers zero or more unsigned
+        delegations. If the opt-out flag is clear, the NSEC3 record covers zero unsigned delegations.
+    
+    @type iterations: C{int}
+    @ivar iterations: Defines the nubmer of additional times the hash algorithm has been performed.
+    
+    @type salt: L{Charset}
+    @ivar salt: Identifies the salt value provided to the hash.
+
+    @type ttl: C{int}
+    @ivar ttl: The maximum number of seconds which this record should be
+        cached.
+    """
+    implements(IEncodable, IRecord)
+    TYPE = NSEC3
+    fmt = '!BBH'
+    
+    fancybasename = 'NSEC3'
+    showAttributes = ('hash_algo', 'flags', 'iterations', ('_salt', 'salt', '%s'), 'ttl')
+    compareAttributes = ('hash_algo', 'flags', 'iterations', 'salt', 'ttl')
+    
+    _salt = property(lambda self: self.salt.string)
+    
+    def __init__(self, hash_algo=0, flags=0, iterations=0, salt='', ttl=None):
+        self.hash_algo = hash_algo
+        self.flags = flags
+        self.iterations = iterations
+        self.salt = Charstr(salt)
+        self.ttl = str2time(ttl)
+        
+    def encode(self, strio, compDict = None):
+        strio.write(struct.pack(self.fmt,
+                                self.hash_algo,
+                                self.flags,
+                                self.iterations))
+        self.salt.encode(strio, None)
+        
+    def decode(self, strio, length=None):
+        start = strio.tell()
+        l = struct.calcsize(self.fmt)
+        buff = readPrecisely(strio, l)
+        r = struct.unpack(self.fmt, buff)        
+        self.hash_algo, self.flags, self.iterations = r   
+        self.salt.decode(strio)
+        here = strio.tell()
+                
+    def __hash__(self):
+        return hash((self.hash_algo, self.flags, self.iterations, self.salt))
+    
+class Record_NSEC3(Record_NSEC3PARAM):
+    """
+    A DNSSEC NSEC3 record provides non-zone-enumerable authenticated denial of existance 
+    for DNS data and permits a gradual expansion of delegation-centric zones. 
+
+    A DNSSEC NSEC3 record lists:
+    
+        1) the set of RR types present at the original owner name of the NSEC RR.
+
+        2) the next hashed owner name in the hash order of the zone.
+
+    @type hash_algo: C{int}
+    @ivar hash_algo: Identifies the cryptographic hash algorithm used to construct the hash value.
+    
+    @type flags: C{int}
+    @ivar flags: Identifies 8 1-bit flags. The only flag presently defined is the 
+        opt-out flag. If the opt-out flag is set, the NSEC3 record covers zero or more unsigned
+        delegations. If the opt-out flag is clear, the NSEC3 record covers zero unsigned delegations.
+    
+    @type iterations: C{int}
+    @ivar iterations: Defines the nubmer of additional times the hash algorithm has been performed.
+    
+    @type salt: L{Charset}
+    @ivar salt: Identifies the salt value provided to the hash.
+    
+    @type nxt_hash_owner_name: L{Charset}
+    @ivar nxt_hash_owner_name: Contains the next hashed owner name in the zone in hash order.
+    
+    @type type_bitmaps: L{TypeBitmaps}
+    @ivar type_bitmaps: Identifies the RRset types that exist at the NSEC3 RR's original owner name.
+    
+    @type ttl: C{int}
+    @ivar ttl: The maximum number of seconds which this record should be
+        cached.
+    """
+    implements(IEncodable, IRecord)
+    TYPE = NSEC3
+    fmt = '!BBH'
+    
+    fancybasename = 'NSEC3'
+    showAttributes = ('hash_algo', 'flags', 'iterations', ('_salt', 'salt', '%s'), ('nxt_hash_owner_name', 'nxt_hash_owner_name', '%s'), ('_type_bitmaps', 'type_bitmaps', '%s'), 'ttl')
+    compareAttributes = ('hash_algo', 'flags', 'iterations', 'salt', 'nxt_hash_owner_name', 'type_bitmaps', 'ttl')
+    
+    _salt = property(lambda self: self.salt.string)
+    _type_bitmaps = property(lambda self: self.type_bitmaps.string)
+    
+    def __init__(self, hash_algo=0, flags=0, iterations=0, salt='', nxt_hash_owner='', type_bitmaps=None, ttl=None):
+        Record_NSEC3PARAM.__init__( self, hash_algo, flags, iterations, salt, ttl)
+        self.nxt_hash_owner_name = Charstr(nxt_hash_owner)
+        self.type_bitmaps = TypeBitmaps(type_bitmaps)
+        
+    def encode(self, strio, compDict = None):
+        Record_NSEC3PARAM.encode(self, strio, compDict)
+        self.nxt_hash_owner_name.encode(strio, None)
+        self.type_bitmaps.encode(strio, None)
+        
+    def decode(self, strio, length=None):
+        start = strio.tell()
+        Record_NSEC3PARAM.decode(self, strio, compDict)
+        self.nxt_hash_owner_name.decode(strio)
+        here = strio.tell()
+        self.type_bitmaps.decode(strio, length + start - here if length else None)
+                
+    def __hash__(self):
+        return hash((self.hash_algo, self.flags, self.iterations, self.salt,
+                     self.nxt_hash_owner_name, self.type_bitmaps))
+
 # This is a fallback record
 class UnknownRecord(tputil.FancyEqMixin, tputil.FancyStrMixin, object):
     """
@@ -1546,15 +2298,17 @@
     queries = answers = add = ns = None
 
     def __init__(self, id=0, answer=0, opCode=0, recDes=0, recAv=0,
-                       auth=0, rCode=OK, trunc=0, maxSize=512):
+                 auth=0, rCode=OK, trunc=0, maxSize=512, authData=0, chkDis=0):
         self.maxSize = maxSize
         self.id = id
         self.answer = answer
         self.opCode = opCode
-        self.auth = auth
-        self.trunc = trunc
-        self.recDes = recDes
-        self.recAv = recAv
+        self.auth = auth            #AA - Authoritative Answer
+        self.trunc = trunc          #TC - TrunCated
+        self.recDes = recDes        #RD - Recursion Desired
+        self.recAv = recAv          #RA - Recursion Available
+        self.authData = authData    #AD - Authentic Data
+        self.chkDis = chkDis        #CD - Checking Disabled
         self.rCode = rCode
         self.queries = []
         self.answers = []
@@ -1599,7 +2353,9 @@
                  | ((self.auth & 1 ) << 2 )
                  | ((self.trunc & 1 ) << 1 )
                  | ( self.recDes & 1 ) )
-        byte4 = ( ( (self.recAv & 1 ) << 7 )
+        byte4 = (  ((self.recAv & 1 ) << 7 )
+                  |((self.authData & 1 ) << 5 )
+                  |((self.chkDis & 1 ) << 4 )
                   | (self.rCode & 0xf ) )
 
         strio.write(struct.pack(self.headerFmt, self.id, byte3, byte4,
@@ -1619,6 +2375,8 @@
         self.trunc = ( byte3 >> 1 ) & 1
         self.recDes = byte3 & 1
         self.recAv = ( byte4 >> 7 ) & 1
+        self.authData = ( byte4 >> 5 ) & 1
+        self.chkDis = ( byte4 >> 4 ) & 1
         self.rCode = byte4 & 0xf
 
         self.queries = []
@@ -1637,9 +2395,8 @@
 
     def parseRecords(self, list, num, strio):
         for i in range(num):
-            header = RRHeader(auth=self.auth)
             try:
-                header.decode(strio)
+                header = OPTHeader.factory(strio, auth=self.auth)
             except EOFError:
                 return
             t = self.lookupRecordType(header.type)
Index: names/ser_num_arith.py
===================================================================
--- names/ser_num_arith.py	(revision 0)
+++ names/ser_num_arith.py	(working copy)
@@ -0,0 +1,136 @@
+# -*- test-case-name: twisted.names.test.test_ser_num_arith -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Serial Number Arithmetic
+
+This module implements RFC 1982 DNS Serial Number Arithmetic 
+(see http://tools.ietf.org/pdf/rfc1982.pdf).
+SNA is used in DNS and specifically in DNSSEC as defined in 
+RFC 4034 in the DNSSEC Signature Expiration and Inception Fields.
+
+bob.novas@shinkuro.com
+"""
+
+import calendar, time
+
+class SNA(object):
+    """
+    implements RFC 1982 - DNS Serial Number Arithmetic
+    """
+    SERIAL_BITS = 32
+    MODULOVAL = 2**SERIAL_BITS
+    HLFRNG = 2**(SERIAL_BITS-1)
+    MAXADD = (2**(SERIAL_BITS-1)-1)
+    
+    def __init__(self, number):
+        self._number = int(number)%self.MODULOVAL
+        
+    def __repr__(self):
+        return str(self._number)
+    
+    def asInt(self):
+        return self._number
+        
+    def eq(self, sna2):
+        assert isinstance(sna2, SNA)
+        return sna2._number == self._number
+    
+    def lt(self, sna2):
+        assert isinstance(sna2, SNA)
+        return not self.eq(sna2) and \
+               ((self._number < sna2._number) and ((sna2._number - self._number) < self.HLFRNG) or \
+                (self._number > sna2._number) and ((self._number - sna2._number) > self.HLFRNG))
+
+    def gt(self, sna2):
+        assert isinstance(sna2, SNA)
+        return not self.eq(sna2) and \
+               ((self._number < sna2._number) and ((sna2._number - self._number) > self.HLFRNG) or \
+                (self._number > sna2._number) and ((self._number - sna2._number) < self.HLFRNG))
+    
+    def le(self, sna2):
+        return self.eq(sna2) or self.lt(sna2)
+    
+    def ge(self, sna2):
+        return self.eq(sna2) or self.gt(sna2)
+
+    def add(self, sna2):
+        assert isinstance(sna2, SNA)
+        if sna2.le(SNA(self.MAXADD)):
+            return SNA( (self._number + sna2._number)%self.MODULOVAL )
+        else:
+            raise ArithmeticError
+        
+    def __hash__(self):
+        return hash(self._number)
+    
+    __eq__  = eq
+    __lt__  = lt
+    __gt__  = gt
+    __le__  = le
+    __ge__  = ge
+    __add__ = add
+        
+    @staticmethod
+    def max(snaList):
+        """
+        this takes a list of sna's from which it will pick the sn with the highest value
+        """
+        if len(snaList) == 0:
+            return None
+        trialMax = snaList[0]
+        for s in snaList[1:]:
+            if not trialMax:
+                trialMax = s
+            elif s and s > trialMax:
+                trialMax = s
+        return trialMax
+
+class DateSNA(SNA):
+    """
+    implements DNS Serial Number Arithmetic
+    for dates 'YYYYMMDDHHMMSS' per RFC 4034 P3.1.5
+    """
+    fmt = '%Y%m%d%H%M%S'
+
+    def __init__(self, utcDateTime=''):
+        '''
+        accept a UTC date/time string as YYMMDDHHMMSS
+        and convert it to seconds since the epoch
+        '''
+        if not utcDateTime:
+            utcDateTime = '19700101000000'
+        dtstruct = time.strptime(utcDateTime, DateSNA.fmt)
+        assert dtstruct.tm_year < 1970+136
+        secondsSinceE = calendar.timegm(dtstruct)
+        super(DateSNA, self).__init__(secondsSinceE)
+    
+    def add(self, sna2):
+        if not isinstance(sna2, SNA):
+            return NotImplemented
+        
+        if sna2.le(SNA(self.MAXADD)) and (self._number + sna2._number < self.MODULOVAL):
+            sna = SNA((self._number + sna2._number)%self.MODULOVAL)
+            return DateSNA.fromSNA(sna)
+        else:
+            raise ArithmeticError
+    
+    def asDate(self):
+        dtstruct = time.gmtime(self._number)
+        return time.strftime(DateSNA.fmt, dtstruct)
+    
+    @staticmethod
+    def fromSNA(sna):
+        assert isinstance(sna, SNA)
+        d = DateSNA()
+        d._number = sna._number
+        return d
+    
+    @staticmethod
+    def fromInt(i):
+        return DateSNA.fromSNA(SNA(i))
+        
+    def __str__(self):
+        return self.asDate()
+
Index: names/test/test_dns.py
===================================================================
--- names/test/test_dns.py	(revision 33585)
+++ names/test/test_dns.py	(working copy)
@@ -8,13 +8,14 @@
 
 from cStringIO import StringIO
 
+import re
 import struct
 
 from twisted.python.failure import Failure
 from twisted.internet import address, task
 from twisted.internet.error import CannotListenError, ConnectionDone
 from twisted.trial import unittest
-from twisted.names import dns
+from twisted.names import dns, common
 
 from twisted.test import proto_helpers
 
@@ -25,6 +26,8 @@
     dns.Record_WKS, dns.Record_SRV, dns.Record_AFSDB, dns.Record_RP,
     dns.Record_HINFO, dns.Record_MINFO, dns.Record_MX, dns.Record_TXT,
     dns.Record_AAAA, dns.Record_A6, dns.Record_NAPTR, dns.UnknownRecord,
+    dns.Record_OPT, dns.Record_RRSIG, dns.Record_DS, dns.Record_DNSKEY,
+    dns.Record_NSEC, dns.Record_NSEC3PARAM, dns.Record_NSEC3,
     ]
 
 class NameTests(unittest.TestCase):
@@ -189,7 +192,18 @@
     """Encoding and then decoding various objects."""
 
     names = ["example.org", "go-away.fish.tv", "23strikesback.net"]
-
+    sigs = ["Qm12VZVaZgKS0/DZx35SGECDwPiTTf3ngChb7OkgSv5iupVmJGhPWudm /18qBSXKyv9hxMlEXFFgpBieNqLfSBkP1bwKnlqPfr1Hx7ctDwDUpkT3 cS8u/ms9yo3Fu1ybpO4Hfsb1HbA2N3zzQnjWKnyk26AAQSz8KgjNTFzD tJM=",
+            "ZH2kahMD1g2WOieIotAcBwB0e/o30Zq6YR//M/xwP1ktkYuclmcR56iv XiR3QFWqmN5Xz3YpgmM4tZkjIeSMp2doYa7XYORZ7OpzG7oyfo8IoXxc j1VGDeAn1CeNCpBtoSGapRABG1gjY7oeRj/smPQPp2Gkf79+WZfuzRom /t4=", 
+            "AZpaboyNQAmbnBO1K66QmZ0c+VCdY/wu9QpEdRnMpnIOLPD28pNVu6hk GQMz6eg5WYkPYDdJK+1D/oyAQkDmRgn10+O9EdeFDyLqYqq/htEAvDm4 CziMSOpD/mkg1bSWCZ2mdln/GBk8WooCeeM7LEHmRjmHMMj0xb6N4SKa MEc=", 
+            "AwEAAbi5VQa3x+R3WQouBDNts+ZX2zIKZNoj9pzl7ew446kI/2omv3j6 c/4RQ6VneYE3mK7r0fFIKhVagmiRroFO1rRUJ8sVidssZ35CldE0sju+ E7wymVg3tV+ZUUO/+5v6Sfj+tw3rlp6eKqm7EGKKM88t+KuXiGYMu0Vw Rm9OUO1n",    
+            "AwEAAbmTL+kuV45kAxGN//iBKz93Y6lutgxoptp+I1+PZZMsBkhm/dZj q57040Pz/Hr3f2zQX7z6fFu7/Ml3MHPH1eQDiVXDvOkeNq2x4IbCO7x+ 0p6bGYj4fw/tEfh/8dUzyzvMwfuAMsOvXza8Kh+UP4jvFc95cUuGgYus uEjUOp40PsL7EtYvAks3UssA6/OZP4w/1Z5m/VFx4PzgY0dkEuc=",    
+            "VGPxa8A81eV1dtUxVhz9b9Jsp6FF4M5H6J0QhzbNCUTHTHjLNR2VHYfE fM+Akwo3/qKq3D6vzTfzqtyPAXP8CmGfdD8hfv0s7Hae9c7Is8usdlrk ZpoXEFMW+YVG8G9OieYViq6tBIpUvKgMVZ+oXKo63KJ/tC/yBW0H0VQP YwdzZ3ZvYRDmZDvrXoX7T0YNU+0HYHnb7g7nUECIJ/4HHg==",
+            "M+u8Uxm2y2Q142qED0kVNIiSOHBkfiU3xBhMq9H4T/K+oeC7Y81HIOFE h9k6ZS/Ba5X0/Fr1yyq5Z/+0/Q845Kya8Lmkp/ikJVe/9id2TC2hoffp Z9pbZRjIeBTAvdTboGmGuqG/ljnDLJrJpoF6g8g6fHR9eekIWis8LJ55 Y1k="]
+    type_bitmaps = ["A MX RRSIG NSEC TYPE1234",
+                    "AAAA NS CNAME MX TXT RRSIG NSEC3 DNAME",
+                    "NSEC3 A AAAA RRSIG DS NS TYPE5000 TYPE1000 TYPE2000 TYPE3000 TYPE4000",
+                    None]
+                    
     def testName(self):
         for n in self.names:
             # encode the name
@@ -276,6 +290,64 @@
             self.assertEqual(result.string, n)
 
 
+    def test_Sigstr(self):
+        """
+        Test L{dns.Sigstr} encode and decode.
+        """
+        for s in self.sigs:
+            # encode the signature/key
+            f = StringIO()
+            dns.Sigstr(s).encode(f)
+            l = f.tell()
+
+            # decode the signature/key
+            f.seek(0, 0)
+            result = dns.Sigstr()
+            result.decode(f,l)
+            #spaces are free, and dig sticks them in
+            self.assertEqual(result.string, s.replace(' ', ''))
+
+
+    def test_TypeBitmaps(self):
+        """
+        Test L{dns.TypeBitmaps} encode and decode.
+        """
+        typeRegex = re.compile('TYPE(\d+)')
+        
+        for b in self.type_bitmaps:
+            # encode the type_bitmaps
+            f = StringIO()
+            dns.TypeBitmaps(b).encode(f)
+            l = f.tell()
+
+            # decode the type_bitmaps
+            f.seek(0, 0)
+            result = dns.TypeBitmaps()
+            result.decode(f,l)
+            
+            def mnuVal(mnu):
+                mnuVal = dns.REV_TYPES.get(mnu, None)
+                if not mnuVal:
+                    m = typeRegex.match(mnu)
+                    if m.groups():
+                        mnuVal = int(m.group(1))
+                        assert mnuVal < 65536
+                    else:
+                        log.err("can't parse %s in %s" % (mnu, self.string, ))
+                        mnuVal = 0
+                return mnuVal
+            
+            def sorttok(string):
+                if not string:
+                    return ''
+                
+                toks = string.split(' ')
+                toks.sort(key = mnuVal)
+                return ' '.join(toks)
+            
+            self.assertEqual(result.type_bitmaps, sorttok(b))
+
+
     def test_NAPTR(self):
         """
         Test L{dns.Record_NAPTR} encode and decode.
@@ -439,6 +511,7 @@
         Initialize the controller: create a list of messages.
         """
         self.messages = []
+        self.dnssecConfig = common.DnssecConfig()
 
 
     def messageReceived(self, msg, proto, addr):
@@ -893,7 +966,80 @@
             repr(dns.UnknownRecord("foo\x1fbar", 12)),
             "<UNKNOWN data='foo\\x1fbar' ttl=12>")
 
+    def test_dnskey(self):
+        """
+        The repr of a L{dns.DNSKEY} instance includes the flags, protocol,
+        algo, pub_key and ttl fields of the record.
+        """
+        self.assertEqual(
+            repr(dns.Record_DNSKEY(10, 20, 30, "foo\x1fbar", ttl=20)),
+            "<DNSKEY flags=10 protocol=20 algo=30 pub_key=foo\x1fbar ttl=20>")
+    
+    def test_ds(self):
+        """
+        The repr of a L{dns.DS} instance includes the key_tag, algo, digest_type,
+        digest and ttl fields of the record.
+        """
+        self.assertEqual(
+            repr(dns.Record_DS(11, 22, 33, "foo\x1fbar1", ttl=21)),
+            "<DS key_tag=11 algo=22 digest_type=33 digest=foo\x1fbar1 ttl=21>")
+    
+    def test_nsec(self):
+        """
+        The repr of a L{dns.NSEC} instance includes the nxt_name, type_bitmaps and
+        ttl fields of the record.
+        """
+        self.assertEqual(
+            repr(dns.Record_NSEC('bob', "\x1fabcd", ttl=31)),
+            "<NSEC nxt_name=bob type_bitmaps=\x1fabcd ttl=31>")
+    
+    def test_nsec3param(self):
+        """
+        The repr of a L{dns.NSEC3PARAM} instance includes the hash_algo, flags, 
+        iterations, salt and ttl fields of the record.
+        """
+        self.assertEqual(
+            repr(dns.Record_NSEC3PARAM(1, 2, 3, '\x12\x34', ttl=31)),
+            "<NSEC3 hash_algo=1 flags=2 iterations=3 salt=\x12\x34 ttl=31>")
+    
+    def test_nsec3(self):
+        """
+        The repr of a L{dns.NSEC3} instance includes the hash_algo, flags, iterations,
+        salt, nxt_hash_owner_name, type_bitmaps and ttl fields of the record.
+        """
+        self.assertEqual(
+            repr(dns.Record_NSEC3(1, 2, 3, '\x12\x34', 'bob', "\x1fabcd", ttl=31)),
+            "<NSEC3 hash_algo=1 flags=2 iterations=3 salt=\x12\x34 nxt_hash_owner_name=bob type_bitmaps=\x1fabcd ttl=31>")
+    
+    def test_opt(self):
+        """
+        The repr of a L{dns.OPT} instance includes the payload_size, dnssecOk flag, 
+        and version fields of the record. (The OPT record has no ttl field.)
+        """
+        self.assertEqual(
+            repr(dns.Record_OPT(payload_size=1492, dnssecOk=1, version=0)),
+                 "<OPT payload_size=1492 flags=0x8000 version=0>")
 
+    def test_rrsig(self):
+        """
+        The repr of a L{dns.RRSIG} instance includes the algo, labels, original_ttl
+        sig_expiration, sig_inception, key_tag, signers_name, signature and ttl 
+        fields of the record.
+        """
+        self.assertEqual(
+            repr(dns.Record_RRSIG(type_covered=dns.A,
+                                  algo=2,
+                                  labels=3,
+                                  original_ttl=30,
+                                  sig_expiration='20110101123456',
+                                  sig_inception= '20110202112233',
+                                  key_tag=60,
+                                  signers_name='bob',
+                                  signature='\x12\x34sig',
+                                  ttl=70)),
+                 "<RRSIG type_covered=1 algo=2 labels=3 original_ttl=30"
+                + " sig_expiration=20110101123456 sig_inception=20110202112233 key_tag=60"
+                + " signers_name=bob signature=\x12\x34sig ttl=70>")
 
 class _Equal(object):
     """
@@ -961,6 +1107,15 @@
             cls('example.com', 123),
             cls('example.org', 123))
 
+    def test_optheader(self):
+        """
+        Two OptHeader instances comapare equal iff the have the same
+        (Record_OPT) payload and auth bit.
+        """
+        self._equalityTest(
+            dns.OPTHeader(payload=dns.Record_OPT(payload_size=1024, dnssecOk=True, version=0, ttl=30)),
+            dns.OPTHeader(payload=dns.Record_OPT(payload_size=1024, dnssecOk=True, version=0, ttl=30)),
+            dns.OPTHeader(payload=dns.Record_OPT(payload_size=1492, dnssecOk=False, version=0, ttl=40), auth=True))
 
     def test_rrheader(self):
         """
@@ -1483,3 +1638,183 @@
             dns.UnknownRecord('foo', ttl=10),
             dns.UnknownRecord('foo', ttl=10),
             dns.UnknownRecord('foo', ttl=100))
+
+    def test_rrsig(self):
+        """
+        L(dns.RRSIG) instances compare equal iff they have the same
+        type_covered, algo, labels, original_ttl, sig_expiration, sig_inception, 
+        key_tag, signers_name, signature, and ttl
+        """
+        self._equalityTest(
+            dns.Record_RRSIG(type_covered=dns.A),
+            dns.Record_RRSIG(type_covered=dns.A),
+            dns.Record_RRSIG(type_covered=dns.AAAA))
+        self._equalityTest(
+            dns.Record_RRSIG(algo=1),
+            dns.Record_RRSIG(algo=1),
+            dns.Record_RRSIG(algo=2))
+        self._equalityTest(
+            dns.Record_RRSIG(labels=3),
+            dns.Record_RRSIG(labels=3),
+            dns.Record_RRSIG(labels=4))
+        self._equalityTest(
+            dns.Record_RRSIG(original_ttl=5),
+            dns.Record_RRSIG(original_ttl=5),
+            dns.Record_RRSIG(original_ttl=6))
+        self._equalityTest(
+            dns.Record_RRSIG(sig_expiration='20110101000000'),
+            dns.Record_RRSIG(sig_expiration='20110101000000'),
+            dns.Record_RRSIG(sig_expiration='20110101000001'))
+        self._equalityTest(
+            dns.Record_RRSIG(sig_inception='20120101000000'),
+            dns.Record_RRSIG(sig_inception='20120101000000'),
+            dns.Record_RRSIG(sig_inception='20120101000001'))
+        self._equalityTest(
+            dns.Record_RRSIG(key_tag=11),
+            dns.Record_RRSIG(key_tag=11),
+            dns.Record_RRSIG(key_tag=12))
+        self._equalityTest(
+            dns.Record_RRSIG(signers_name='bob'),
+            dns.Record_RRSIG(signers_name='bob'),
+            dns.Record_RRSIG(signers_name='joe'))
+        self._equalityTest(
+            dns.Record_RRSIG(signature='abcdef'),
+            dns.Record_RRSIG(signature='abcdef'),
+            dns.Record_RRSIG(signature='abcdefg'))
+        self._equalityTest(
+            dns.Record_RRSIG(ttl=10),
+            dns.Record_RRSIG(ttl=10),
+            dns.Record_RRSIG(ttl=20))
+        
+    def test_ds(self):
+        """
+        L(dns.DS) instances compare equal iff they have the same
+        key_tag, algo, digest_type, digest and ttl
+        """
+        self._equalityTest(
+            dns.Record_DS(key_tag=1),
+            dns.Record_DS(key_tag=1),
+            dns.Record_DS(key_tag=2))
+        self._equalityTest(
+            dns.Record_DS(algo=3),
+            dns.Record_DS(algo=3),
+            dns.Record_DS(algo=4))
+        self._equalityTest(
+            dns.Record_DS(digest_type=5),
+            dns.Record_DS(digest_type=5),
+            dns.Record_DS(digest_type=6))
+        self._equalityTest(
+            dns.Record_DS(digest='abcdef-digest'),
+            dns.Record_DS(digest='abcdef-digest'),
+            dns.Record_DS(digest='abcdef-digest-f'))
+        self._equalityTest(
+            dns.Record_DS(ttl=10),
+            dns.Record_DS(ttl=10),
+            dns.Record_DS(ttl=20))
+
+    def test_dnskey(self):
+        """
+        L(dns.DNSKEY) instances compare equal iff they have the same
+        flags, protocol, algo, pub_key and ttl
+        """
+        self._equalityTest(
+            dns.Record_DNSKEY(flags=1),
+            dns.Record_DNSKEY(flags=1),
+            dns.Record_DNSKEY(flags=2))
+        self._equalityTest(
+            dns.Record_DNSKEY(protocol=3),
+            dns.Record_DNSKEY(protocol=3),
+            dns.Record_DNSKEY(protocol=4))
+        self._equalityTest(
+            dns.Record_DNSKEY(algo=5),
+            dns.Record_DNSKEY(algo=5),
+            dns.Record_DNSKEY(algo=6))
+        self._equalityTest(
+            dns.Record_DNSKEY(pub_key='abcdef-digest'),
+            dns.Record_DNSKEY(pub_key='abcdef-digest'),
+            dns.Record_DNSKEY(pub_key='abcdef-digest-f'))
+        self._equalityTest(
+            dns.Record_DNSKEY(ttl=10),
+            dns.Record_DNSKEY(ttl=10),
+            dns.Record_DNSKEY(ttl=20))
+
+    def test_nsec(self):
+        """
+        L(dns.DNSKEY) instances compare equal iff they have the same
+        nxt_name, type_bitmaps and ttl
+        """
+        self._equalityTest(
+            dns.Record_NSEC(nxt_name="example.com"),
+            dns.Record_NSEC(nxt_name="example.com"),
+            dns.Record_NSEC(nxt_name="a.example.com"))
+        self._equalityTest(
+            dns.Record_NSEC(type_bitmaps="A AAAA NS NSEC3"),
+            dns.Record_NSEC(type_bitmaps="A AAAA NS NSEC3"),
+            dns.Record_NSEC(type_bitmaps="A AAAA NS NSEC3 RRSIG"))
+        self._equalityTest(
+            dns.Record_NSEC(ttl=5),
+            dns.Record_NSEC(ttl=5),
+            dns.Record_NSEC(ttl=6))
+
+    def test_nsec3param(self):
+        """
+        L(dns.DNSKEY) instances compare equal iff they have the same
+        hash_algo, flags, iterations, salt, nxt_hash_owner_name, type_bitmaps and ttl
+        """
+        self._equalityTest(
+            dns.Record_NSEC3PARAM(hash_algo=1),
+            dns.Record_NSEC3PARAM(hash_algo=1),
+            dns.Record_NSEC3PARAM(hash_algo=2))
+        self._equalityTest(
+            dns.Record_NSEC3PARAM(flags=1),
+            dns.Record_NSEC3PARAM(flags=1),
+            dns.Record_NSEC3PARAM(flags=2))
+        self._equalityTest(
+            dns.Record_NSEC3PARAM(iterations=5),
+            dns.Record_NSEC3PARAM(iterations=5),
+            dns.Record_NSEC3PARAM(iterations=6))
+        self._equalityTest(
+            dns.Record_NSEC3PARAM(salt="abcdef"),
+            dns.Record_NSEC3PARAM(salt="abcdef"),
+            dns.Record_NSEC3PARAM(salt="abcdefg"))
+        self._equalityTest(
+            dns.Record_NSEC3PARAM(ttl=5),
+            dns.Record_NSEC3PARAM(ttl=5),
+            dns.Record_NSEC3PARAM(ttl=6))
+
+    def test_nsec3(self):
+        """
+        L(dns.DNSKEY) instances compare equal iff they have the same
+        hash_algo, flags, iterations, salt, nxt_hash_owner_name, type_bitmaps and ttl
+        """
+        self._equalityTest(
+            dns.Record_NSEC3(hash_algo=1),
+            dns.Record_NSEC3(hash_algo=1),
+            dns.Record_NSEC3(hash_algo=2))
+        self._equalityTest(
+            dns.Record_NSEC3(flags=1),
+            dns.Record_NSEC3(flags=1),
+            dns.Record_NSEC3(flags=2))
+        self._equalityTest(
+            dns.Record_NSEC3(iterations=5),
+            dns.Record_NSEC3(iterations=5),
+            dns.Record_NSEC3(iterations=6))
+        self._equalityTest(
+            dns.Record_NSEC3(salt="abcdef"),
+            dns.Record_NSEC3(salt="abcdef"),
+            dns.Record_NSEC3(salt="abcdefg"))
+        self._equalityTest(
+            dns.Record_NSEC3(nxt_hash_owner="example.com"),
+            dns.Record_NSEC3(nxt_hash_owner="example.com"),
+            dns.Record_NSEC3(nxt_hash_owner="a.example.com"))
+        self._equalityTest(
+            dns.Record_NSEC3(type_bitmaps="A AAAA NS NSEC3"),
+            dns.Record_NSEC3(type_bitmaps="A AAAA NS NSEC3"),
+            dns.Record_NSEC3(type_bitmaps="A AAAA NS NSEC3 RRSIG"))
+        self._equalityTest(
+            dns.Record_NSEC3(ttl=5),
+            dns.Record_NSEC3(ttl=5),
+            dns.Record_NSEC3(ttl=6))
+
+
+                
\ No newline at end of file
