## Overview

The dns rdata routines (`dns_rdata_fromtext()`, `dns_rdata_totext()`, `dns_rdata_fromwire()`, `dns_rdata_towire()` `dns_rdata_fromstruct()`, `dns_rdata_tostruct()` and `dns_rdata_compare()`) are designed to provide a single set of routines for encoding, decoding and comparing dns data preventing the problems that occurred in BIND 8.x and earlier where there were multiple places in the code base that decoded wire format to internal format or compared rdata sometimes with subtly different behaviour (bugs) or didn't support a particular type leading to internal inconsistancy.

Each of these generic routines calls type specific routines that provide the type specific details.

From time to time new types are defined and it is necessary to add these types into the existing structure. This document is written to provide instruction on how to do this.

## Adding new RDATA types

Adding a new rdata type requires determining if the new rdata type is class specific or generic. Writing code to perform the following set of operations and then integrating it into the build by placing the code into the rdata hierachy at the correct place. Running `make clean` followed `make` in `lib/dns` will cause the new rdata type to be picked up.

Each rdata module must perform the following operations:

Convert from text format to internal format
Convert from internal format to text format
Convert from wire format to internal format
Convert from internal format to wire format
Convert from a structure to internal format
Convert from internal format to a structure
Compare two rdata in internal format

There is an additional set of support functions and macros only available to to rdata code.

## RDATA Hierarchy

The `rdata` hierarchy has the following format.

##

##

##

##

##

##

##

##

##

##

##

    	rdata/
    		generic/
    			typename_typenumber.h
    		classname_classnumber/
    			typename_typenumber.h

    Initial rdata hierarchy:

    	rdata/
    		generic/
    			ns_2.h
    			md_3.h
    			mf_4.h
    			cname_5.h
    			soa_6.h
    			mb_7.h
    			mg_8.h
    			mr_9.h
    			null_10.h
    			ptr_12.h
    			hinfo_13.h
    			minfo_14.h
    			mx_15.h
    			txt_16.h
    			rp_17.h
    			afsdb_18.h
    			x25_19.h
    			isdn_20.h
    			rt_21.h
    			sig_24.h
    			key_25.h
    			gpos_27.h
    			loc_29.h
    			nxt_30.h
    			cert_37.h
    			dname_39.h
    			unspec_103.h
    			tkey_249.h
    		in_1/
    			a_1.h
    			wks_11.h
    			nsap_22.h
    			nsap-ptr_23.h
    			px_26.h
    			aaaa_28.h
    			srv_33.h
    			naptr_35.h
    			kx_36.h
    			a6_38.h
    		any_255/
    			tsig_250.h

    CLASSNAME and TYPENAME
    Class and type names must be from the following alphabet and less that 11
    characters in length or otherwise they will be ignored.
    Permissible alphabet: a to z, 0 to 9 and dash (-).
    Dash is mapped to underscore (_) for the C function names below.

    Internal Format
    The internal format chosen is DNS wire format without any compression being
    applied to domain names in the rdata.

    Convert from text format to internal format
    The functions to convert from text format has the following call formats and
    is declared as follows for class generic functions.

    static dns_result_t
    fromtext_typename(dns_rdataclass_t class, dns_rdatatype_t type,
    		  isc_lex_t *lexer, dns_name_t *origin,
    		  isc_boolean_t downcase, isc_buffer_t *target);

    Class specific functions contain the class name in addition to the
    type name.

    static dns_result_t
    fromtext_classname_typename(dns_rdataclass_t class, dns_rdatatype_t type,
    		           isc_lex_t *lexer, dns_name_t *origin,
    			   isc_boolean_t downcase, isc_buffer_t *target);

    class

    This argument should be ignored when used with a class generic RR type
    otherwise REQUIRE(class == #) should be present at the start
    of the function.
    type

    This should be tested with a REQUIRE(type == #) statement at
    the begining of the function.
    lexer

    This is used to read the input text stream.
    origin

    This is a absolute name used to qualify unqualified / partially qualified
    domainnames in the text stream.
    It is passed to the name parsing routines.
    downcase

    This is passed to the name parsing routines to determine whether to downcase
    the names it generates or leave them in the case they are pesented in.
    target

    This is a BINARY buffer used to write the internal format of the rdata record being read in to.

    fromtext_typename() reads tokens from lexer,
    up to but not including the end of line (EOL) token or end of file (EOF) token.
    If the EOL / EOF token is read it should be returned to the input stream.
    gettoken()
    should be used to read the next token from the input stream and
    will return EOL / EOF tokens
    automatically unless
    they are specifcally requested.
    isc_lex_ungettoken() should
    be used to return EOL / EOF (or any other token) to the input stream if
    the EOL / EOF token is read.
    Unused tokens will cause dns_rdata_fromtext() to return
    DNS_R_EXTRATOKEN if fromtext_typename() was successful.

    fromtext_typename() reads external input and as such is a high security area and must be paranoid about its input.
    Convert from internal format to text format

    static dns_result_t
    totext_typename(dns_rdata_t *rdata, dns_name_t *origin,
    		isc_buffer_t *target);

    static dns_result_t
    totext_classname_typename(dns_rdata_t *rdata, dns_name_t *origin,
    			  isc_buffer_t *target);

    rdata

    This is the rdata record to be converted from internal format to text.
    rdata->type and rdata->class for class specific
    RR types should be checked at the start of the function with
    REQUIRE(rdata->type == #) statements.
    origin

    If this in non NULL then any domainnames with this suffix
    should be written out unqualified.
    name_prefix() can be used to
    check if origin is NULL and provide the correct
    arguments to the name conversion routines.
    target

    This is a TEXT buffer used to hold the output.

    Convert from wire format to internal format

    static dns_result_t
    fromwire_typename(dns_rdataclass_t class, dns_rdatatype_t type,
                      isc_buffer_t *source, dns_decompress_t *dctx,
    	          isc_boolean_t downcase, isc_buffer_t *target);

    static dns_result_t
    fromwire_classname_typename(dns_rdataclass_t class, dns_rdatatype_t type,
    			    isc_buffer_t *source, dns_decompress_t *dctx,
    			    isc_boolean_t downcase, isc_buffer_t *target);

    fromwire_classname_typename() is required to set the valid
    decompression methods if there is a domain name in the rdata.

    if (dns_decompress_edns(dctx) >= # || !dns_decompress_strict(dctx))
    	dns_decompress_setmethods(dctx, DNS_COMPRESS_ALL);
    else
    	dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);

    class

    This argument should be ignored when used with a class generic RR type
    otherwise REQUIRE(class == #) should be present at the start
    of the function.
    type

    This should be tested with a REQUIRE(type == #) statement at
    the begining of the function.
    source

    This is a BINARY buffer with the active region
    containing a RR record in wire format.
    dctx

    This is the decompression context and is passed to
    dns_name_fromwire(),
    along with downcase, to enable a compressed domain name
    to be extracted from the source.
    downcase

    This is passed to dns_name_fromwire() to say whether the
    extracted domainname should be downcased during the extraction.
    target

    This is a BINARY buffer where the decompressed and checked
    RR record is written.

    fromwire_typename() is a security sensitive routine
    as it reads external data and should take extreme care to ensure that
    the input data matches its description.

    If the active buffer is not empty at completion and
    fromwire_typename() was otherwise successful
    dns_rdata_fromwire() will return DNS_R_EXTRADATA.
    Convert from internal format to wire format

    static dns_result_t
    towire_typename(dns_rdata_t *rdata, dns_compress_t *cctx,
                    isc_buffer_t *target);

    static dns_result_t
    towire_classname_typename(dns_rdata_t *rdata, dns_compress_t *cctx,
                              isc_buffer_t *target);

    towire_classname_typename() is required to set the
    allowed name compression methods based on EDNS version if there is a
    domain name in the rdata.

    if (dns_compress_getedns(cctx) >= #)
    	dns_compress_setmethods(cctx, DNS_COMPRESS_ALL);
    else
    	dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);

    rdata

    This is the rdata record to be converted from internal format to text.
    rdata->type and rdata->class for class specific
    RR types should be checked at the start of the function with
    REQUIRE(rdata->type == #) statements.
    cctx

    This is the compression context, it should be passed to dns_name_towire() when putting domainnames on the wire.
    target

    This is a BINARY buffer used to write the rdata to.

    Simple RR types without domainnames can use the following code to
    transfer the contents of the rdata to the target buffer.

    	return (mem_tobuffer(target, rdata->data, rdata->length));

    Convert from a structure to internal format

    static dns_result_t
    fromstruct_typename(dns_rdataclass_t class, dns_rdatatype_t type,
    		    void *source, isc_buffer_t *target);

    static dns_result_t
    fromstruct_classname_typename(dns_rdataclass_t class, dns_rdatatype_t type,
    		    	      void *source, isc_buffer_t *target);

    class

    This argument should be ignored when used with a class generic RR type
    otherwise REQUIRE(class == #) should be present at the start
    of the function.
    type

    This should be tested with a REQUIRE(type == #) statement at
    the beginning of the function.
    source

    This points to a type specific structure.
    target

    This is a BINARY buffer used to write the internal format of the rdata record being read in to.

    Convert from internal format to a structure

    static dns_result_t
    tostruct_typename(dns_rdata_t *rdata, void *target);

    static dns_result_t
    tostruct_classname_typename(dns_rdata_t *rdata, void *target);

    rdata

    This is the rdata record to be converted from internal format to a structure.
    rdata->type and rdata->class for class specific
    RR types should be checked at the start of the function with
    REQUIRE(rdata->type == #) statements.
    target

    Pointer to a type specific structure.

    Compare two rdata in internal format

    static int
    compare_typename(dns_rdata_t *rdata1, dns_rdata_t *rdata2);

    static int
    compare_classname_typename(dns_rdata_t *rdata1, dns_rdata_t *rdata2);

    Compares rdata1 and rdata2 as required for DNSSEC
    ordering.  The routine should
    ensure that the type and class of the two rdata
    match with REQUIRE(rdata1->type == rdata2->type); and
    REQUIRE(rdata1->class == rdata2->class); statements. The
    rdata->type should also be verified and if the RR type is
    class specific the rdata->class.

    compare_classname_typename() returns -1, 0, 1.
    Support Functions
    The following static support functions are available to use.

    static unsigned int

    name_length(dns_name_t *name);

    Returns the length of name.

    static dns_result_t

    txt_totext(isc_region_t *source, isc_buffer_t *target);

    Extracts the octet length tagged text string at the start of
    source and writes it as a quoted string to target.
    source is adjusted so that it points to first octet after the
    text string.

    Returns DNS_R_NOSPACE or DNS_R_SUCCESS.

    static dns_result_t

    txt_fromtext(isc_textregion_t *source, isc_buffer_t *target);

    Take the text region source and convert it to a length tagged
    text string writing it to target.

    Returns DNS_R_NOSPACE, DNS_R_TEXTTOLONG
    or DNS_R_SUCCESS.

    static dns_result_t

    txt_fromwire(isc_buffer_t *source, isc_buffer_t *target);

    Read a octet length tagged text string from source and
    write it to target.
    Ensures that octet length tagged text string was wholly within the active
    area of source.
    Adjusts the active area of source so that it refers to the first
    octet after the octet length tagged text string.

    Returns DNS_R_UNEXPECTEDEND, DNS_R_NOSPACE or
    DNS_R_SUCCESS.

    static isc_boolean_t

    name_prefix(dns_name_t *name, dns_name_t *origin, dns_name_t *target);

    If origin is NULL or the root label set target to
    refer to name and return ISC_FALSE.
    Otherwise see if name is a sub domain of origin
    and are not equal.
    If so make target refer to the prefix of name and
    return ISC_TRUE.
    Otherwise make target refer to name and return
    ISC_FALSE.

    Typical use:

    static dns_result_t
    totext_typename(dns_rdata_t *rdata, dns_name_t *origin,
    		isc_buffer_t * target)
    {
    	isc_region_t region;
    	dns_name_t name, prefix;
    	isc_boolean_t sub;

    	dns_name_init(&name, NULL);
    	dns_name_init(&prefix, NULL);
    	dns_rdata_toregion(rdata, &region);
    	dns_name_fromregion(&name, &region);
    	sub = name_prefix(&name, origin, &prefix);
    	return (dns_name_totext(&prefix, sub, target));
    }

    static dns_result_t

    str_totext(char *source, isc_buffer_t *target);

    This adds the NULL terminated string source
    up to but not including NULL to target.

    Returns DNS_R_NOSPACE and DNS_R_SUCCESS.

    static isc_boolean_t

    buffer_empty(isc_buffer_t *source);

    Returns ISC_TRUE if the active region of source is
    empty otherwise ISC_FALSE.

    static void

    buffer_fromregion(isc_buffer_t *buffer, isc_region_t *region,
    unsigned int type);

    Make buffer refer to the memory in region and
    make it active.

    static dns_result_t

    uint32_tobuffer(isc_uint32_t value, isc_buffer_t *target);

    Write the 32 bit value in network order to target.

    Returns DNS_R_NOSPACE and DNS_R_SUCCESS.

    static dns_result_t

    uint16_tobuffer(isc_uint32_t value, isc_buffer_t *target);

    Write them 16 bit value in network order to target.

    Returns ISC_R_RANGE, DNS_R_NOSPACE and DNS_R_SUCCESS.

    static isc_uint32_t

    uint32_fromregion(isc_region_t *region);

    Returns the 32 bit at the start of region in host order.

    Requires (region->length >= 4).

    static isc_uint16_t

    uint16_fromregion(isc_region_t *region);

    Returns the 16 bit at the start of region in host order.

    Requires (region->length >= 2).

    static dns_result_t

    gettoken(isc_lex_t *lexer, isc_token_t *token, isc_tokentype_t expect, isc_boolean_t eol);

    Gets the next token from the input stream lexer. Ensure that the
    returned token matches expect (isc_tokentype_qstring can also
    return isc_tokentype_string), or isc_tokentype_eol and isc_tokentype_eof if
    eol is ISC_TRUE.

    Returns DNS_R_UNEXPECTED, DNS_R_UNEXPECTEDEND,
    DNS_R_UNEXPECTEDTOKEN and DNS_R_SUCCESS.

    static dns_result_t

    mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length);

    Add the memory referred to by base to target.

    Returns DNS_R_NOSPACE and DNS_R_SUCCESS.

    static int

    compare_region(isc_region_t *r1, isc_region_t *r2)

    Compares two regions returning -1, 0, 1 based on their DNSSEC ordering.

    static int

    hexvalue(char value);

    Returns the hexadecimal value of value or -1 if not
    a hexadecimal character.

    static int

    decvalue(char value);

    Returns the decimal value of value or -1 if not
    a decimal character.

    static dns_result_t

    base64_totext(isc_region_t *source, isc_buffer_t *target);

    Convert the region referred to by source to base64 encoded text
    and put it into target.

    Returns DNS_R_NOSPACE or DNS_R_SUCCESS.

    static dns_result_t

    base64_tobuffer(isc_lex_t *lexer, isc_buffer_t *target,
    int length);

    Read a series of tokens from lexer that containing base64 data
    until one of end of line, length (length >= 0)
    bytes have been read or base64 pad characters are seen.
    If length < 0 it is ignored otherwise it is an error if there
    are not length octets of data or when processing a token
    length octets would have been exceeded.

    Returns DNS_R_BADBASE64, DNS_R_UNEXPECTED,
    DNS_R_UNEXPECTEDEND, DNS_R_UNEXPECTEDTOKEN
    and DNS_R_SUCCESS.

    static dns_result_t

    time_totext(unsigned long value, isc_buffer_t *target);

    Convert the date represented by value into YYYYMMDDHHMMSS format
    taking into account the active epochs. This code is Y2K and Y2038 compliant.

    Returns DNS_R_NOSPACE and DNS_R_SUCCESS.
    static dns_result_t

    time_tobuffer(char *source, isc_buffer_t *target);

    Take the date in source and convert it seconds since January 1,
    1970 (ignoring leap seconds) and place the least significant 32 bits into
    target.

    Returns ISC_R_RANGE, DNS_R_SYNTAX,
    DNS_R_NOSPACE and DNS_R_SUCCESS.

    Support Macros
    The following macro is available:

    RETERR(x)

    Evaluate x and call return (<value of x>); if the result is not DNS_R_SUCCESS.