# Copyright (C) 2001-2007, 2009, 2010 Nominum, Inc.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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.

"""DNS Rdata Types.

@var _by_text: The rdata type textual name to value mapping
@type _by_text: dict
@var _by_value: The rdata type value to textual name mapping
@type _by_value: dict
@var _metatypes: If an rdatatype is a metatype, there will be a mapping
whose key is the rdatatype value and whose value is True in this dictionary.
@type _metatypes: dict
@var _singletons: If an rdatatype is a singleton, there will be a mapping
whose key is the rdatatype value and whose value is True in this dictionary.
@type _singletons: dict"""

import re

import dns.exception

NONE = 0
A = 1
NS = 2
MD = 3
MF = 4
CNAME = 5
SOA = 6
MB = 7
MG = 8
MR = 9
NULL = 10
WKS = 11
PTR = 12
HINFO = 13
MINFO = 14
MX = 15
TXT = 16
RP = 17
AFSDB = 18
X25 = 19
ISDN = 20
RT = 21
NSAP = 22
NSAP_PTR = 23
SIG = 24
KEY = 25
PX = 26
GPOS = 27
AAAA = 28
LOC = 29
NXT = 30
SRV = 33
NAPTR = 35
KX = 36
CERT = 37
A6 = 38
DNAME = 39
OPT = 41
APL = 42
DS = 43
SSHFP = 44
IPSECKEY = 45
RRSIG = 46
NSEC = 47
DNSKEY = 48
DHCID = 49
NSEC3 = 50
NSEC3PARAM = 51
HIP = 55
SPF = 99
UNSPEC = 103
TKEY = 249
TSIG = 250
IXFR = 251
AXFR = 252
MAILB = 253
MAILA = 254
ANY = 255
TA = 32768
DLV = 32769

_by_text = {
    'NONE' : NONE,
    'A' : A,
    'NS' : NS,
    'MD' : MD,
    'MF' : MF,
    'CNAME' : CNAME,
    'SOA' : SOA,
    'MB' : MB,
    'MG' : MG,
    'MR' : MR,
    'NULL' : NULL,
    'WKS' : WKS,
    'PTR' : PTR,
    'HINFO' : HINFO,
    'MINFO' : MINFO,
    'MX' : MX,
    'TXT' : TXT,
    'RP' : RP,
    'AFSDB' : AFSDB,
    'X25' : X25,
    'ISDN' : ISDN,
    'RT' : RT,
    'NSAP' : NSAP,
    'NSAP-PTR' : NSAP_PTR,
    'SIG' : SIG,
    'KEY' : KEY,
    'PX' : PX,
    'GPOS' : GPOS,
    'AAAA' : AAAA,
    'LOC' : LOC,
    'NXT' : NXT,
    'SRV' : SRV,
    'NAPTR' : NAPTR,
    'KX' : KX,
    'CERT' : CERT,
    'A6' : A6,
    'DNAME' : DNAME,
    'OPT' : OPT,
    'APL' : APL,
    'DS' : DS,
    'SSHFP' : SSHFP,
    'IPSECKEY' : IPSECKEY,
    'RRSIG' : RRSIG,
    'NSEC' : NSEC,
    'DNSKEY' : DNSKEY,
    'DHCID' : DHCID,
    'NSEC3' : NSEC3,
    'NSEC3PARAM' : NSEC3PARAM,
    'HIP' : HIP,
    'SPF' : SPF,
    'UNSPEC' : UNSPEC,
    'TKEY' : TKEY,
    'TSIG' : TSIG,
    'IXFR' : IXFR,
    'AXFR' : AXFR,
    'MAILB' : MAILB,
    'MAILA' : MAILA,
    'ANY' : ANY,
    'TA' : TA,
    'DLV' : DLV,
    }

# We construct the inverse mapping programmatically to ensure that we
# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
# would cause the mapping not to be true inverse.

_by_value = dict([(y, x) for x, y in _by_text.iteritems()])


_metatypes = {
    OPT : True
    }

_singletons = {
    SOA : True,
    NXT : True,
    DNAME : True,
    NSEC : True,
    # CNAME is technically a singleton, but we allow multiple CNAMEs.
    }

_unknown_type_pattern = re.compile('TYPE([0-9]+)$', re.I);

class UnknownRdatatype(dns.exception.DNSException):
    """Raised if a type is unknown."""
    pass

def from_text(text):
    """Convert text into a DNS rdata type value.
    @param text: the text
    @type text: string
    @raises dns.rdatatype.UnknownRdatatype: the type is unknown
    @raises ValueError: the rdata type value is not >= 0 and <= 65535
    @rtype: int"""

    value = _by_text.get(text.upper())
    if value is None:
        match = _unknown_type_pattern.match(text)
        if match == None:
            raise UnknownRdatatype
        value = int(match.group(1))
        if value < 0 or value > 65535:
            raise ValueError("type must be between >= 0 and <= 65535")
    return value

def to_text(value):
    """Convert a DNS rdata type to text.
    @param value: the rdata type value
    @type value: int
    @raises ValueError: the rdata type value is not >= 0 and <= 65535
    @rtype: string"""

    if value < 0 or value > 65535:
        raise ValueError("type must be between >= 0 and <= 65535")
    text = _by_value.get(value)
    if text is None:
        text = 'TYPE' + `value`
    return text

def is_metatype(rdtype):
    """True if the type is a metatype.
    @param rdtype: the type
    @type rdtype: int
    @rtype: bool"""

    if rdtype >= TKEY and rdtype <= ANY or _metatypes.has_key(rdtype):
        return True
    return False

def is_singleton(rdtype):
    """True if the type is a singleton.
    @param rdtype: the type
    @type rdtype: int
    @rtype: bool"""

    if _singletons.has_key(rdtype):
        return True
    return False
