From 034b775e5c69d223c9b0e2101a6acae6874d327f Mon Sep 17 00:00:00 2001
From: Willem Toorop <willem@nlnetlabs.nl>
Date: Fri, 15 Feb 2019 13:36:39 +0100
Subject: [PATCH] DOA & AMTRELAY RR types implementation

---
 ChangeLog              |   2 +
 src/const-info.c       |   1 +
 src/getdns/getdns.h.in |   1 +
 src/gldns/rrdef.c      |  15 +++
 src/gldns/rrdef.h      |   8 +-
 src/gldns/str2wire.c   |  76 ++++++++++++++
 src/gldns/str2wire.h   |   9 ++
 src/gldns/wire2str.c   |  58 +++++++++++
 src/gldns/wire2str.h   |  15 +++
 src/rr-dict.c          | 228 ++++++++++++++++++++++++++++++++++++++++-
 10 files changed, 407 insertions(+), 6 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index dccc733d..b50e946c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,8 @@
 * 2019-??-??: Version 1.?.?
   * Issue #419: Escape backslashed when printing in JSON format.
     Thanks boB Rudis
+  * DOA rr-type
+  * AMTRELAY rr-type
 
 * 2019-01-11: Version 1.5.1
   * Introduce proof of concept GnuTLS implementation. Incomplete support
diff --git a/src/const-info.c b/src/const-info.c
index ebff80a4..6265c523 100644
--- a/src/const-info.c
+++ b/src/const-info.c
@@ -303,6 +303,7 @@ static struct const_name_info consts_name_info[] = {
 	{ "GETDNS_RRTYPE_A6", 38 },
 	{ "GETDNS_RRTYPE_AAAA", 28 },
 	{ "GETDNS_RRTYPE_AFSDB", 18 },
+	{ "GETDNS_RRTYPE_AMTRELAY", 260 },
 	{ "GETDNS_RRTYPE_ANY", 255 },
 	{ "GETDNS_RRTYPE_APL", 42 },
 	{ "GETDNS_RRTYPE_ATMA", 34 },
diff --git a/src/getdns/getdns.h.in b/src/getdns/getdns.h.in
index 5e4873b5..b2702d43 100644
--- a/src/getdns/getdns.h.in
+++ b/src/getdns/getdns.h.in
@@ -439,6 +439,7 @@ typedef enum getdns_callback_type_t {
 #define GETDNS_RRTYPE_CAA       257
 #define GETDNS_RRTYPE_AVC       258
 #define GETDNS_RRTYPE_DOA       259
+#define GETDNS_RRTYPE_AMTRELAY  260
 #define GETDNS_RRTYPE_TA        32768
 #define GETDNS_RRTYPE_DLV       32769
 /** @}
diff --git a/src/gldns/rrdef.c b/src/gldns/rrdef.c
index 9f27a5a1..114f807e 100644
--- a/src/gldns/rrdef.c
+++ b/src/gldns/rrdef.c
@@ -232,6 +232,15 @@ static const gldns_rdf_type type_caa_wireformat[] = {
 	GLDNS_RDF_TYPE_TAG,
 	GLDNS_RDF_TYPE_LONG_STR
 };
+#ifdef DRAFT_RRTYPES
+static const gldns_rdf_type type_doa_wireformat[] = {
+	GLDNS_RDF_TYPE_INT32, GLDNS_RDF_TYPE_INT32, GLDNS_RDF_TYPE_INT8,
+	GLDNS_RDF_TYPE_STR, GLDNS_RDF_TYPE_B64
+};
+static const gldns_rdf_type type_amtrelay_wireformat[] = {
+	GLDNS_RDF_TYPE_AMTRELAY
+};
+#endif
 
 /* All RR's defined in 1035 are well known and can thus
  * be compressed. See RFC3597. These RR's are:
@@ -608,8 +617,14 @@ static gldns_rr_descriptor rdata_field_descriptors[] = {
 #ifdef DRAFT_RRTYPES
 	/* 258 */
 	{GLDNS_RR_TYPE_AVC, "AVC", 1, 0, NULL, GLDNS_RDF_TYPE_STR, GLDNS_RR_NO_COMPRESS, 0 },
+	/* 259 */
+	{GLDNS_RR_TYPE_DOA, "DOA", 1, 0, type_doa_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 },
+	/* 260 */
+	{GLDNS_RR_TYPE_AMTRELAY, "AMTRELAY", 1, 0, type_amtrelay_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 },
 #else
 {GLDNS_RR_TYPE_NULL, "TYPE258", 1, 1, type_0_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 },
+{GLDNS_RR_TYPE_NULL, "TYPE259", 1, 1, type_0_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 },
+{GLDNS_RR_TYPE_NULL, "TYPE260", 1, 1, type_0_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 },
 #endif
 
 /* split in array, no longer contiguous */
diff --git a/src/gldns/rrdef.h b/src/gldns/rrdef.h
index a11984b3..a393dc8e 100644
--- a/src/gldns/rrdef.h
+++ b/src/gldns/rrdef.h
@@ -38,7 +38,7 @@ extern "C" {
 #define GLDNS_KEY_REVOKE_KEY 0x0080 /* used to revoke KSK, rfc 5011 */
 
 /* The first fields are contiguous and can be referenced instantly */
-#define GLDNS_RDATA_FIELD_DESCRIPTORS_COMMON 259
+#define GLDNS_RDATA_FIELD_DESCRIPTORS_COMMON 260
 
 /** lookuptable for rr classes  */
 extern struct gldns_struct_lookup_table* gldns_rr_classes;
@@ -226,7 +226,8 @@ enum gldns_enum_rr_type
 	GLDNS_RR_TYPE_URI = 256, /* RFC 7553 */
 	GLDNS_RR_TYPE_CAA = 257, /* RFC 6844 */
 	GLDNS_RR_TYPE_AVC = 258,
-	GLDNS_RR_TYPE_DOA = 259,
+	GLDNS_RR_TYPE_DOA = 259, /* draft-durand-doa-over-dns */
+	GLDNS_RR_TYPE_AMTRELAY= 260, /* draft-ietf-mboned-driad-amt-discovery */
 
 	/** DNSSEC Trust Authorities */
 	GLDNS_RR_TYPE_TA = 32768,
@@ -351,6 +352,9 @@ enum gldns_enum_rdf_type
          */
         GLDNS_RDF_TYPE_LONG_STR,
 
+	/* draft-ietf-mboned-driad-amt-discovery */
+	GLDNS_RDF_TYPE_AMTRELAY,
+
 	/** TSIG extended 16bit error value */
 	GLDNS_RDF_TYPE_TSIGERROR,
 
diff --git a/src/gldns/str2wire.c b/src/gldns/str2wire.c
index 26c2ea6f..708df50e 100644
--- a/src/gldns/str2wire.c
+++ b/src/gldns/str2wire.c
@@ -997,6 +997,8 @@ int gldns_str2wire_rdf_buf(const char* str, uint8_t* rd, size_t* len,
 		return gldns_str2wire_hip_buf(str, rd, len);
 	case GLDNS_RDF_TYPE_INT16_DATA:
 		return gldns_str2wire_int16_data_buf(str, rd, len);
+	case GLDNS_RDF_TYPE_AMTRELAY:
+		return gldns_str2wire_amtrelay_buf(str, rd, len);
 	case GLDNS_RDF_TYPE_UNKNOWN:
 	case GLDNS_RDF_TYPE_SERVICE:
 		return GLDNS_WIREPARSE_ERR_NOT_IMPL;
@@ -2118,3 +2120,77 @@ int gldns_str2wire_int16_data_buf(const char* str, uint8_t* rd, size_t* len)
 	*len = ((size_t)n)+2;
 	return GLDNS_WIREPARSE_ERR_OK;
 }
+
+int gldns_str2wire_amtrelay_buf(const char* str, uint8_t* rd, size_t* len)
+{
+	size_t relay_len = 0;
+	int s;
+	uint8_t relay_type;
+	char token[512];
+	gldns_buffer strbuf;
+	gldns_buffer_init_frm_data(&strbuf, (uint8_t*)str, strlen(str));
+
+	if(*len < 2)
+		return GLDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL;
+	/* precedence */
+	if(gldns_bget_token(&strbuf, token, "\t\n ", sizeof(token)) <= 0)
+		return RET_ERR(GLDNS_WIREPARSE_ERR_INVALID_STR,
+			gldns_buffer_position(&strbuf));
+	rd[0] = (uint8_t)atoi(token);
+	/* discovery_optional */
+	if(gldns_bget_token(&strbuf, token, "\t\n ", sizeof(token)) <= 0)
+		return RET_ERR(GLDNS_WIREPARSE_ERR_INVALID_STR,
+			gldns_buffer_position(&strbuf));
+	if ((token[0] != '0' && token[0] != '1') || token[1] != 0)
+		return RET_ERR(GLDNS_WIREPARSE_ERR_INVALID_STR,
+			gldns_buffer_position(&strbuf));
+
+	rd[1] = *token == '1' ? 0x80 : 0x00;
+	/* relay_type */
+	if(gldns_bget_token(&strbuf, token, "\t\n ", sizeof(token)) <= 0)
+		return RET_ERR(GLDNS_WIREPARSE_ERR_INVALID_STR,
+			gldns_buffer_position(&strbuf));
+	relay_type = (uint8_t)atoi(token);
+	if (relay_type > 0x7F)
+		return RET_ERR(GLDNS_WIREPARSE_ERR_INVALID_STR,
+			gldns_buffer_position(&strbuf));
+	rd[1] |= relay_type;
+
+	if (relay_type == 0) {
+		*len = 2;
+		return GLDNS_WIREPARSE_ERR_OK;
+	}
+	/* relay */
+	if(gldns_bget_token(&strbuf, token, "\t\n ", sizeof(token)) <= 0)
+		return RET_ERR(GLDNS_WIREPARSE_ERR_INVALID_STR,
+			gldns_buffer_position(&strbuf));
+	if(relay_type == 1) {
+		/* IP4 */
+		relay_len = *len - 2;
+		s = gldns_str2wire_a_buf(token, rd+2, &relay_len);
+		if(s) return RET_ERR_SHIFT(s, gldns_buffer_position(&strbuf));
+	} else if(relay_type == 2) {
+		/* IP6 */
+		relay_len = *len - 2;
+		s = gldns_str2wire_aaaa_buf(token, rd+2, &relay_len);
+		if(s) return RET_ERR_SHIFT(s, gldns_buffer_position(&strbuf));
+	} else if(relay_type == 3) {
+		/* DNAME */
+		relay_len = *len - 2;
+		s = gldns_str2wire_dname_buf(token, rd+2, &relay_len);
+		if(s) return RET_ERR_SHIFT(s, gldns_buffer_position(&strbuf));
+	} else {
+		/* unknown gateway type */
+		return RET_ERR(GLDNS_WIREPARSE_ERR_INVALID_STR,
+			gldns_buffer_position(&strbuf));
+	}
+	/* double check for size */
+	if(*len < 2 + relay_len)
+		return RET_ERR(GLDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL,
+			gldns_buffer_position(&strbuf));
+
+	*len = 2 + relay_len;
+	return GLDNS_WIREPARSE_ERR_OK;
+}
+
+
diff --git a/src/gldns/str2wire.h b/src/gldns/str2wire.h
index 2aba0e10..293a4981 100644
--- a/src/gldns/str2wire.h
+++ b/src/gldns/str2wire.h
@@ -554,6 +554,15 @@ int gldns_str2wire_hip_buf(const char* str, uint8_t* rd, size_t* len);
  */
 int gldns_str2wire_int16_data_buf(const char* str, uint8_t* rd, size_t* len);
 
+/**
+ * Convert rdf of type GLDNS_RDF_TYPE_AMTRELAY from string to wireformat.
+ * @param str: the text to convert for this rdata element.
+ * @param rd: rdata buffer for the wireformat.
+ * @param len: length of rd buffer on input, used length on output.
+ * @return 0 on success, error on failure.
+ */
+int gldns_str2wire_amtrelay_buf(const char* str, uint8_t* rd, size_t* len);
+
 /**
  * Strip whitespace from the start and the end of line.
  * @param line: modified with 0 to shorten it.
diff --git a/src/gldns/wire2str.c b/src/gldns/wire2str.c
index 54e336d8..28b7863b 100644
--- a/src/gldns/wire2str.c
+++ b/src/gldns/wire2str.c
@@ -1004,6 +1004,9 @@ int gldns_wire2str_rdf_scan(uint8_t** d, size_t* dlen, char** s, size_t* slen,
 		return gldns_wire2str_tag_scan(d, dlen, s, slen);
 	case GLDNS_RDF_TYPE_LONG_STR:
 		return gldns_wire2str_long_str_scan(d, dlen, s, slen);
+	case GLDNS_RDF_TYPE_AMTRELAY:
+		return gldns_wire2str_amtrelay_scan(d, dlen, s, slen, pkt,
+			pktlen);
 	case GLDNS_RDF_TYPE_TSIGERROR:
 		return gldns_wire2str_tsigerror_scan(d, dlen, s, slen);
 	}
@@ -1707,6 +1710,61 @@ int gldns_wire2str_long_str_scan(uint8_t** d, size_t* dl, char** s, size_t* sl)
 	return w;
 }
 
+/* internal scan routine that can modify arguments on failure */
+static int gldns_wire2str_amtrelay_scan_internal(uint8_t** d, size_t* dl,
+	char** s, size_t* sl, uint8_t* pkt, size_t pktlen)
+{
+	/* https://www.ietf.org/id/draft-ietf-mboned-driad-amt-discovery-01.txt */
+	uint8_t precedence, discovery_optional, relay_type;
+	int w = 0;
+
+	if(*dl < 2) return -1;
+	precedence = (*d)[0];
+	discovery_optional= (*d)[1] >> 7;
+	relay_type = (*d)[1] % 0x7F;
+	if(relay_type > 3)
+		return -1; /* unknown */
+	(*d)+=2;
+	(*dl)-=2;
+	w += gldns_str_print(s, sl, "%d %d %d ",
+		(int)precedence, (int)discovery_optional, (int)relay_type);
+
+	switch(relay_type) {
+	case 0: /* no relay */
+		break;
+	case 1: /* ip4 */
+		w += gldns_wire2str_a_scan(d, dl, s, sl);
+		break;
+	case 2: /* ip6 */
+		w += gldns_wire2str_aaaa_scan(d, dl, s, sl);
+		break;
+	case 3: /* dname */
+		w += gldns_wire2str_dname_scan(d, dl, s, sl, pkt, pktlen);
+		break;
+	default: /* unknown */
+		return -1;
+	}
+	return w;
+}
+
+int gldns_wire2str_amtrelay_scan(uint8_t** d, size_t* dl, char** s, size_t* sl,
+	uint8_t* pkt, size_t pktlen)
+{
+	uint8_t* od = *d;
+	char* os = *s;
+	size_t odl = *dl, osl = *sl;
+	int w=gldns_wire2str_amtrelay_scan_internal(d, dl, s, sl, pkt, pktlen);
+	if(w == -1) {
+		*d = od;
+		*s = os;
+		*dl = odl;
+		*sl = osl;
+		return -1;
+	}
+	return w;
+}
+
+
 int gldns_wire2str_tsigerror_scan(uint8_t** d, size_t* dl, char** s, size_t* sl)
 {
 	gldns_lookup_table *lt;
diff --git a/src/gldns/wire2str.h b/src/gldns/wire2str.h
index a7a7c930..99a737a1 100644
--- a/src/gldns/wire2str.h
+++ b/src/gldns/wire2str.h
@@ -916,6 +916,21 @@ int gldns_wire2str_tag_scan(uint8_t** data, size_t* data_len, char** str,
 int gldns_wire2str_long_str_scan(uint8_t** data, size_t* data_len, char** str,
 	size_t* str_len);
 
+/**
+ * Scan wireformat AMTRELAY field to string, with user buffers.
+ * It shifts the arguments to move along (see gldns_wire2str_pkt_scan).
+ * @param data: wireformat data.
+ * @param data_len: length of data buffer.
+ * @param str: string buffer.
+ * @param str_len: length of string buffer.
+ * @param pkt: packet for decompression, if NULL no decompression.
+ * @param pktlen: length of packet buffer.
+ * @return number of characters (except null) needed to print.
+ * 	Can return -1 on failure.
+ */
+int gldns_wire2str_amtrelay_scan(uint8_t** data, size_t* data_len, char** str,
+	size_t* str_len, uint8_t* pkt, size_t pktlen);
+
 /**
  * Print EDNS LLQ option data to string.  User buffers, moves string pointers.
  * @param str: string buffer.
diff --git a/src/rr-dict.c b/src/rr-dict.c
index 79fb7bcc..0ad75a43 100644
--- a/src/rr-dict.c
+++ b/src/rr-dict.c
@@ -431,6 +431,214 @@ static _getdns_rdf_special hip_public_key = {
     hip_public_key_dict2wire, NULL
 };
 
+static const uint8_t *
+amtrelay_D_rdf_end(const uint8_t *pkt, const uint8_t *pkt_end, const uint8_t *rdf)
+{
+	(void)pkt;
+	return rdf < pkt_end ? rdf + 1 : NULL;
+}
+static getdns_return_t
+amtrelay_D_wire2dict(getdns_dict *dict, const uint8_t *rdf)
+{
+	return getdns_dict_set_int(dict, "discovery_optional", (*rdf  >> 7));
+}
+static getdns_return_t
+amtrelay_D_dict2wire(const getdns_dict *dict,
+    uint8_t *rdata, uint8_t *rdf, size_t *rdf_len)
+{
+	getdns_return_t r;
+	uint32_t        value;
+	(void)rdata; /* unused parameter */
+
+	if ((r = getdns_dict_get_int(dict, "discovery_optional", &value)))
+		return r;
+
+	*rdf_len = 1;
+	if (*rdf_len < 1)
+		return GETDNS_RETURN_NEED_MORE_SPACE;
+
+	*rdf_len = 1;
+	*rdf = value ? 0x80 : 0x00;
+	return GETDNS_RETURN_GOOD;
+}
+static _getdns_rdf_special amtrelay_D = {
+    amtrelay_D_rdf_end,
+    amtrelay_D_wire2dict, NULL,
+    amtrelay_D_dict2wire, NULL 
+};
+
+static const uint8_t *
+amtrelay_rtype_rdf_end(
+    const uint8_t *pkt, const uint8_t *pkt_end, const uint8_t *rdf)
+{
+	return rdf;
+}
+static getdns_return_t
+amtrelay_rtype_wire2dict(getdns_dict *dict, const uint8_t *rdf)
+{
+	return _getdns_dict_set_int(
+	    dict, "replay_type", (rdf[-1] & 0x7F));
+}
+static getdns_return_t
+amtrelay_rtype_dict2wire(
+    const getdns_dict *dict, uint8_t *rdata, uint8_t *rdf, size_t *rdf_len)
+{
+	getdns_return_t r;
+	uint32_t value;
+
+	if ((r = getdns_dict_get_int(dict, "relay_type", &value)))
+		return r;
+
+	if (rdf - 1 < rdata)
+		return GETDNS_RETURN_GENERIC_ERROR;
+
+	*rdf_len = 0;
+	rdf[-1] |= (value & 0x7F);
+
+	return GETDNS_RETURN_GOOD;
+}
+static _getdns_rdf_special amtrelay_rtype = {
+    amtrelay_rtype_rdf_end,
+    amtrelay_rtype_wire2dict, NULL,
+    amtrelay_rtype_dict2wire, NULL 
+};
+
+static const uint8_t *
+amtrelay_relay_rdf_end(
+    const uint8_t *pkt, const uint8_t *pkt_end, const uint8_t *rdf)
+{
+	const uint8_t *end;
+
+	if (rdf - 4 < pkt)
+		return NULL;
+	switch (rdf[-1] & 0x7F) {
+	case 0:	end = rdf;
+		break;
+	case 1: end = rdf + 4;
+		break;
+	case 2: end = rdf + 16;
+		break;
+	case 3: for (end = rdf; end < pkt_end; end += *end + 1)
+			if ((*end & 0xC0) == 0xC0)
+				end  += 2;
+			else if (*end & 0xC0)
+				return NULL;
+			else if (!*end) {
+				end += 1;
+				break;
+			}
+		break;
+	default:
+		return NULL;
+	}
+	return end <= pkt_end ? end : NULL;
+}
+static getdns_return_t
+amtrelay_relay_equip_const_bindata(
+    const uint8_t *rdf, size_t *size, const uint8_t **data)
+{
+	*data = rdf;
+	switch (rdf[-1] & 0x7F) {
+	case 0:	*size = 0;
+		break;
+	case 1: *size = 4;
+		break;
+	case 2: *size = 16;
+		break;
+	case 3: while (*rdf)
+			if ((*rdf & 0xC0) == 0xC0)
+				rdf += 2;
+			else if (*rdf & 0xC0)
+				return GETDNS_RETURN_GENERIC_ERROR;
+			else
+				rdf += *rdf + 1;
+		*size = rdf + 1 - *data;
+		break;
+	default:
+		return GETDNS_RETURN_GENERIC_ERROR;
+	}
+	return GETDNS_RETURN_GOOD;
+}
+
+static getdns_return_t
+amtrelay_relay_wire2dict(getdns_dict *dict, const uint8_t *rdf)
+{
+	size_t size;
+	const uint8_t *data;
+
+	if (amtrelay_relay_equip_const_bindata(rdf, &size, &data))
+		return GETDNS_RETURN_GENERIC_ERROR;
+
+	else if (! size)
+		return GETDNS_RETURN_GOOD;
+	else
+		return _getdns_dict_set_const_bindata(dict, "relay", size, data);
+}
+static getdns_return_t
+amtrelay_relay_2wire(
+    const getdns_bindata *value, uint8_t *rdata, uint8_t *rdf, size_t *rdf_len)
+{
+	assert(rdf - 1 >= rdata && (rdf[-1] & 0x7F) > 0);
+
+	switch (rdf[-1] & 0x7F) {
+	case 1: if (!value || value->size != 4)
+			return GETDNS_RETURN_INVALID_PARAMETER;
+		if (*rdf_len < 4) {
+			*rdf_len = 4;
+			return GETDNS_RETURN_NEED_MORE_SPACE;
+		}
+		*rdf_len = 4;
+		(void)memcpy(rdf, value->data, 4);
+		return GETDNS_RETURN_GOOD;
+	case 2: if (!value || value->size != 16)
+			return GETDNS_RETURN_INVALID_PARAMETER;
+		if (*rdf_len < 16) {
+			*rdf_len = 16;
+			return GETDNS_RETURN_NEED_MORE_SPACE;
+		}
+		*rdf_len = 16;
+		(void)memcpy(rdf, value->data, 16);
+		return GETDNS_RETURN_GOOD;
+	case 3: if (!value || value->size == 0)
+			return GETDNS_RETURN_INVALID_PARAMETER;
+		/* Assume bindata is a valid dname; garbage in, garbage out */
+		if (*rdf_len < value->size) {
+			*rdf_len = value->size;
+			return GETDNS_RETURN_NEED_MORE_SPACE;
+		}
+		*rdf_len = value->size;
+		(void)memcpy(rdf, value->data, value->size);
+		return GETDNS_RETURN_GOOD;
+	default:
+		return GETDNS_RETURN_GENERIC_ERROR;
+	}
+	return GETDNS_RETURN_GOOD;
+}
+static getdns_return_t
+amtrelay_relay_dict2wire(
+    const getdns_dict *dict, uint8_t *rdata, uint8_t *rdf, size_t *rdf_len)
+{
+	getdns_return_t r;
+	getdns_bindata *value;
+
+	if (rdf - 1 < rdata)
+		return GETDNS_RETURN_GENERIC_ERROR;
+
+	else if ((rdf[-1] & 0x7F) == 0) {
+		*rdf_len = 0;
+		return GETDNS_RETURN_GOOD;
+	}
+	else if ((r = getdns_dict_get_bindata(dict, "relay", &value)))
+		return r;
+	else
+		return amtrelay_relay_2wire(value, rdata, rdf, rdf_len);
+}
+static _getdns_rdf_special amtrelay_relay = {
+    amtrelay_relay_rdf_end,
+    amtrelay_relay_wire2dict, NULL,
+    amtrelay_relay_dict2wire, NULL 
+};
+
 
 static _getdns_rdata_def          a_rdata[] = {
 	{ "ipv4_address"                , GETDNS_RDF_A      , NULL }};
@@ -665,6 +873,17 @@ static _getdns_rdata_def        dlv_rdata[] = {
 	{ "algorithm"                   , GETDNS_RDF_I1     , NULL },
 	{ "digest_type"                 , GETDNS_RDF_I1     , NULL },
 	{ "digest"                      , GETDNS_RDF_X      , NULL }};
+static _getdns_rdata_def        doa_rdata[] = {
+	{ "enterprise"                  , GETDNS_RDF_I4     , NULL },
+	{ "type"                        , GETDNS_RDF_I4     , NULL },
+	{ "location"                    , GETDNS_RDF_I1     , NULL },
+	{ "media_type"                  , GETDNS_RDF_S      , NULL },
+	{ "data"                        , GETDNS_RDF_B      , NULL }};
+static _getdns_rdata_def   amtrelay_rdata[] = {
+	{ "precedence"                  , GETDNS_RDF_I1     , NULL },
+	{ "discovery_optional"          , GETDNS_RDF_SPECIAL, &amtrelay_D},
+	{ "relay_type"                  , GETDNS_RDF_SPECIAL, &amtrelay_rtype },
+	{ "relay"                       , GETDNS_RDF_SPECIAL, &amtrelay_relay }};
 
 static _getdns_rr_def _getdns_rr_defs[] = {
 	{         NULL,             NULL, 0                      },
@@ -926,7 +1145,8 @@ static _getdns_rr_def _getdns_rr_defs[] = {
 	{        "URI",        uri_rdata, ALEN(       uri_rdata) }, /* 256 - */
 	{        "CAA",        caa_rdata, ALEN(       caa_rdata) },
 	{        "AVC",        txt_rdata, ALEN(       txt_rdata) },
-	{        "DOA",    UNKNOWN_RDATA, 0                      }, /* - 259 */
+	{        "DOA",        doa_rdata, ALEN(       doa_rdata) },
+	{   "AMTRELAY",   amtrelay_rdata, ALEN(  amtrelay_rdata) }, /* - 260 */
 	{         "TA",         ds_rdata, ALEN(        ds_rdata) }, /* 32768 */
 	{        "DLV",        dlv_rdata, ALEN(       dlv_rdata) }  /* 32769 */
 };
@@ -934,12 +1154,12 @@ static _getdns_rr_def _getdns_rr_defs[] = {
 const _getdns_rr_def *
 _getdns_rr_def_lookup(uint16_t rr_type)
 {
-	if (rr_type <= 259)
+	if (rr_type <= 260)
 		return &_getdns_rr_defs[rr_type];
 	else if (rr_type == 32768)
-		return &_getdns_rr_defs[260];
-	else if (rr_type == 32769)
 		return &_getdns_rr_defs[261];
+	else if (rr_type == 32769)
+		return &_getdns_rr_defs[262];
 	return _getdns_rr_defs;
 }