no|*)
;;
esac
+AC_ARG_ENABLE(rrtype-svcb-https, AC_HELP_STRING([--enable-rrtype-svcb-https], [Enable draft RR types SVCB and HTTPS.]))
+case "$enable_rrtype_svcb_https" in
+ yes)
+ AC_DEFINE_UNQUOTED([RRTYPE_SVCB_HTTPS], [], [Define this to enable RR types SVCB and HTTPS.])
+ ;;
+ no|*)
+ ;;
+esac
"but it is not found in the zone" },
{ LDNS_STATUS_NO_VALID_ZONEMD,
"No ZONEMD matching the zone data was found" },
+ { LDNS_STATUS_SYNTAX_SVCPARAM_KEY_ERR, "Syntax error in a key in "
+ "the ServiceParam rdata field of SVCB or HTTPS RR" },
+ { LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR, "Syntax error in a value in "
+ "the ServiceParam rdata field of SVCB or HTTPS RR" },
+ { LDNS_STATUS_RESERVED_SVCPARAM_KEY,
+ "key65535 is reserved and MUST NOT be used "
+ "in the ServiceParam rdata field of SVCB or HTTPS RR" },
+ { LDNS_STATUS_NO_SVCPARAM_VALUE_EXPECTED,
+ "A value was found for a key that SHOULD not have a value "
+ "in the ServiceParam rdata field of SVCB or HTTPS RR" },
+ { LDNS_STATUS_SVCPARAM_KEY_MORE_THAN_ONCE,
+ "A key was found more than once "
+ "in the ServiceParam rdata field of SVCB or HTTPS RR" },
+ { LDNS_STATUS_INVALID_SVCPARAM_VALUE,
+ "Invalid wireformat of a value "
+ "in the ServiceParam rdata field of SVCB or HTTPS RR" },
{ 0, NULL }
};
return ldns_buffer_status(output);
}
+#ifdef RRTYPE_SVCB_HTTPS
+ldns_status svcparam_key2buffer_str(ldns_buffer *output, uint16_t key);
+
+static ldns_status
+svcparam_mandatory2buffer_str(ldns_buffer *output, size_t sz, uint8_t *data)
+{
+ if (sz % 2)
+ return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+ svcparam_key2buffer_str(output, ldns_read_uint16(data));
+ for (data += 2, sz -= 2; sz; data += 2, sz -= 2) {
+ ldns_buffer_write_u8(output, ',');
+ svcparam_key2buffer_str(output, ldns_read_uint16(data));
+ }
+ return ldns_buffer_status(output);
+}
+
+static ldns_status
+svcparam_alpn2buffer_str(ldns_buffer *output, size_t sz, uint8_t *data)
+{
+ uint8_t *eod = data + sz, *dp;
+ bool quote = false;
+ size_t i;
+
+ for (dp = data; dp < eod && !quote; dp += 1 + *dp) {
+ if (dp + 1 + *dp > eod)
+ return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+ for (i = 0; i < *dp; i++)
+ if (isspace(dp[i + 1]))
+ break;
+ quote = i < *dp;
+ }
+ if (quote)
+ ldns_buffer_write_u8(output, '"');
+ while (data < eod) {
+ uint8_t *eot = data + 1 + *data;
+
+ if (eot > eod)
+ return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+ if (eod - data < (int)sz)
+ ldns_buffer_write_u8(output, ',');
+
+ for (data += 1; data < eot; data += 1) {
+ uint8_t ch = *data;
+
+ if (isprint(ch) || ch == '\t') {
+ if (ch == '"' || ch == ',' || ch == '\\')
+ ldns_buffer_write_u8(output, '\\');
+ ldns_buffer_write_u8(output, ch);
+ } else
+ ldns_buffer_printf(output, "\\%03u"
+ , (unsigned)ch);
+ }
+ }
+ if (quote)
+ ldns_buffer_write_u8(output, '"');
+ return ldns_buffer_status(output);
+}
+
+static ldns_status
+svcparam_port2buffer_str(ldns_buffer *output, size_t sz, uint8_t *data)
+{
+ if (sz != 2)
+ return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+ ldns_buffer_printf(output, "%d", (int)ldns_read_uint16(data));
+ return ldns_buffer_status(output);
+}
+
+static ldns_status
+svcparam_ipv4hint2buffer_str(ldns_buffer *output, size_t sz, uint8_t *data)
+{
+ char str[INET_ADDRSTRLEN];
+
+ if (sz % 4 || !inet_ntop(AF_INET, data, str, INET_ADDRSTRLEN))
+ return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+ ldns_buffer_write_string(output, str);
+
+ for (data += 4, sz -= 4; sz ; data += 4, sz -= 4 ) {
+ ldns_buffer_write_u8(output, ',');
+ if (!inet_ntop(AF_INET, data, str, INET_ADDRSTRLEN))
+ return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+ ldns_buffer_write_string(output, str);
+ }
+ return ldns_buffer_status(output);
+}
+
+static ldns_status
+svcparam_echconfig2buffer_str(ldns_buffer *output, size_t sz, uint8_t *data)
+{
+ size_t str_sz = ldns_b64_ntop_calculate_size(sz);
+ int written;
+
+ if (!ldns_buffer_reserve(output, str_sz))
+ return LDNS_STATUS_MEM_ERR;
+
+ written = ldns_b64_ntop( data, sz
+ , (char *)ldns_buffer_current(output), str_sz);
+ if (written > 0)
+ ldns_buffer_skip(output, written);
+ else
+ return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+ return ldns_buffer_status(output);
+}
+
+static ldns_status
+svcparam_ipv6hint2buffer_str(ldns_buffer *output, size_t sz, uint8_t *data)
+{
+ char str[INET6_ADDRSTRLEN];
+
+ if (sz % 16 || !inet_ntop(AF_INET6, data, str, INET6_ADDRSTRLEN))
+ return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+ ldns_buffer_write_string(output, str);
+
+ for (data += 16, sz -= 16; sz ; data += 16, sz -= 16) {
+ ldns_buffer_write_u8(output, ',');
+ if (!inet_ntop(AF_INET6, data, str, INET6_ADDRSTRLEN))
+ return LDNS_STATUS_INVALID_SVCPARAM_VALUE;
+
+ ldns_buffer_write_string(output, str);
+ }
+ return ldns_buffer_status(output);
+}
+
+static ldns_status
+svcparam_value2buffer_str(ldns_buffer *output, size_t sz, uint8_t *data)
+{
+ uint8_t *eod = data + sz, *dp;
+ bool quote = false;
+
+ for (dp = data; dp < eod && !isspace(*dp); dp++)
+ ; /* pass */
+
+ if ((quote = dp < eod))
+ ldns_buffer_write_u8(output, '"');
+
+ for (dp = data; dp < eod; dp++) {
+ uint8_t ch = *dp;
+
+ if (isprint(ch) || ch == '\t') {
+ if (ch == '"' || ch == '\\')
+ ldns_buffer_write_u8(output, '\\');
+ ldns_buffer_write_u8(output, ch);
+ } else
+ ldns_buffer_printf(output, "\\%03u", (unsigned)ch);
+ }
+ if (quote)
+ ldns_buffer_write_u8(output, '"');
+ return ldns_buffer_status(output);
+}
+
+ldns_status
+ldns_rdf2buffer_str_svcparams(ldns_buffer *output, const ldns_rdf *rdf)
+{
+ uint8_t *data, *dp, *next_dp = NULL;
+ size_t sz;
+ ldns_status st;
+
+ if (!output)
+ return LDNS_STATUS_NULL;
+
+ if (!rdf || !(data = ldns_rdf_data(rdf)) || !(sz = ldns_rdf_size(rdf)))
+ /* No svcparams is just fine. Just nothing to print. */
+ return LDNS_STATUS_OK;
+
+ for (dp = data; dp + 4 < data + sz; dp = next_dp) {
+ ldns_svcparam_key key = ldns_read_uint16(dp);
+ uint16_t val_sz = ldns_read_uint16(dp + 2);
+
+ if ((next_dp = dp + 4 + val_sz) > data + sz)
+ return LDNS_STATUS_RDATA_OVERFLOW;
+
+ if (dp > data)
+ ldns_buffer_write_u8(output, ' ');
+
+ if ((st = svcparam_key2buffer_str(output, key)))
+ return st;
+
+ if (val_sz == 0)
+ continue;
+ dp += 4;
+ ldns_buffer_write_u8(output, '=');
+ switch (key) {
+ case LDNS_SVCPARAM_KEY_MANDATORY:
+ st = svcparam_mandatory2buffer_str(output, val_sz, dp);
+ break;
+ case LDNS_SVCPARAM_KEY_ALPN:
+ st = svcparam_alpn2buffer_str(output, val_sz, dp);
+ break;
+ case LDNS_SVCPARAM_KEY_NO_DEFAULT_ALPN:
+ return LDNS_STATUS_NO_SVCPARAM_VALUE_EXPECTED;
+ case LDNS_SVCPARAM_KEY_PORT:
+ st = svcparam_port2buffer_str(output, val_sz, dp);
+ break;
+ case LDNS_SVCPARAM_KEY_IPV4HINT:
+ st = svcparam_ipv4hint2buffer_str(output, val_sz, dp);
+ break;
+ case LDNS_SVCPARAM_KEY_ECHCONFIG:
+ st = svcparam_echconfig2buffer_str(output, val_sz, dp);
+ break;
+ case LDNS_SVCPARAM_KEY_IPV6HINT:
+ st = svcparam_ipv6hint2buffer_str(output, val_sz, dp);
+ break;
+ default:
+ st = svcparam_value2buffer_str(output, val_sz, dp);
+ break;
+ }
+ if (st)
+ return st;
+ }
+ return ldns_buffer_status(output);
+}
+#else /* #ifdef RRTYPE_SVCB_HTTPS */
+ldns_status
+ldns_rdf2buffer_str_svcparams(ldns_buffer *output, const ldns_rdf *rdf)
+{
+ (void)output; (void)rdf;
+ return LDNS_STATUS_NOT_IMPL;
+}
+#endif /* #ifdef RRTYPE_SVCB_HTTPS */
static ldns_status
ldns_rdf2buffer_str_fmt(ldns_buffer *buffer,
case LDNS_RDF_TYPE_AMTRELAY:
res = ldns_rdf2buffer_str_amtrelay(buffer, rdf);
break;
+ case LDNS_RDF_TYPE_SVCPARAMS:
+ res = ldns_rdf2buffer_str_svcparams(buffer, rdf);
+ break;
}
} else {
/** This will write mangled RRs */
LDNS_STATUS_ZONEMD_UNKNOWN_HASH,
LDNS_STATUS_ZONEMD_INVALID_SOA,
LDNS_STATUS_NO_ZONEMD,
- LDNS_STATUS_NO_VALID_ZONEMD
+ LDNS_STATUS_NO_VALID_ZONEMD,
+ LDNS_STATUS_SYNTAX_SVCPARAM_KEY_ERR,
+ LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR,
+ LDNS_STATUS_RESERVED_SVCPARAM_KEY,
+ LDNS_STATUS_NO_SVCPARAM_VALUE_EXPECTED,
+ LDNS_STATUS_SVCPARAM_KEY_MORE_THAN_ONCE,
+ LDNS_STATUS_INVALID_SVCPARAM_VALUE
};
typedef enum ldns_enum_status ldns_status;
ldns_status ldns_rdf2buffer_str_amtrelay(ldns_buffer *output,
const ldns_rdf *rdf);
+/**
+ * Converts an LDNS_RDF_TYPE_SVCPARAMS rdata element to presentation format.
+ * \param[in] *rdf The rdata to convert
+ * \param[in] *output The buffer to add the data to
+ * \return LDNS_STATUS_OK on success, and error status on failure
+ */
+ldns_status ldns_rdf2buffer_str_svcparams(ldns_buffer *output,
+ const ldns_rdf *rdf);
/**
* Converts the data in the rdata field to presentation format and
/** draft-ietf-mboned-driad-amt-discovery **/
LDNS_RDF_TYPE_AMTRELAY,
+ /** draft-ietf-dnsop-svcb-https **/
+ LDNS_RDF_TYPE_SVCPARAMS,
+
/* Aliases */
LDNS_RDF_TYPE_BITMAP = LDNS_RDF_TYPE_NSEC
};
};
typedef enum ldns_enum_cert_algorithm ldns_cert_algorithm;
-
+/**
+ * keys types in SVCPARAMS rdata fields
+ */
+enum ldns_enum_svcparam_key
+{
+ LDNS_SVCPARAM_KEY_MANDATORY = 0,
+ LDNS_SVCPARAM_KEY_ALPN = 1,
+ LDNS_SVCPARAM_KEY_NO_DEFAULT_ALPN = 2,
+ LDNS_SVCPARAM_KEY_PORT = 3,
+ LDNS_SVCPARAM_KEY_IPV4HINT = 4,
+ LDNS_SVCPARAM_KEY_ECHCONFIG = 5,
+ LDNS_SVCPARAM_KEY_IPV6HINT = 6,
+ LDNS_SVCPARAM_KEY_LAST_KEY = 6,
+ LDNS_SVCPARAM_KEY_RESERVED = 65535
+};
+typedef enum ldns_enum_svcparam_key ldns_svcparam_key;
/**
* Resource record data field.
LDNS_RR_TYPE_CDNSKEY = 60, /* RFC 7344 */
LDNS_RR_TYPE_OPENPGPKEY = 61, /* RFC 7929 */
LDNS_RR_TYPE_CSYNC = 62, /* RFC 7477 */
- LDNS_RR_TYPE_ZONEMD = 63, /* draft-wessels-dns-zone-digest */
+ LDNS_RR_TYPE_ZONEMD = 63, /* draft-ietf-dnsop-dns-zone-digest */
+ LDNS_RR_TYPE_SVCB = 64, /* draft-ietf-dnsop-svcb-https */
+ LDNS_RR_TYPE_HTTPS = 65, /* draft-ietf-dnsop-svcb-https */
LDNS_RR_TYPE_SPF = 99, /* RFC 4408 */
ldns_status ldns_str2rdf_hip(ldns_rdf **rd, const char *str);
/**
- * Concert a"<precedence> <D-bit> <type> <relay>" encoding
+ * Convert a "<precedence> <D-bit> <type> <relay>" encoding
* of the value field as specified in Section 4.3.1 of
* [draft-ietf-mboned-driad-amt-discovery], encoded as wireformat as specified in
- * ection 4.2 of [draft-ietf-mboned-driad-amt-discovery]
+ * Section 4.2 of [draft-ietf-mboned-driad-amt-discovery]
* \param[in] rd the rdf where to put the data
* \param[in] str the string to be converted
* \return ldns_status
*/
ldns_status ldns_str2rdf_amtrelay(ldns_rdf **rd, const char *str);
+/**
+ * Convert a series of "key[=<value>]" encodings to wireformat as described in
+ * [draft-ietf-dnsop-svcb-https].
+ * \param[in] rd the rdf where to put the data
+ * \param[in] str the string to be converted
+ * \return ldns_status
+ */
+ldns_status ldns_str2rdf_svcparams(ldns_rdf **rd, const char *str);
+
+
#ifdef __cplusplus
}
#endif
case LDNS_RDF_TYPE_AMTRELAY:
status = ldns_str2rdf_amtrelay(&rdf, str);
break;
+ case LDNS_RDF_TYPE_SVCPARAMS:
+ status = ldns_str2rdf_svcparams(&rdf, str);
+ break;
case LDNS_RDF_TYPE_NONE:
default:
/* default default ??? */
switch (ldns_rr_descriptor_field_type(desc, r_cnt)) {
case LDNS_RDF_TYPE_B64 :
case LDNS_RDF_TYPE_HEX : /* These rdf types may con- */
- case LDNS_RDF_TYPE_LOC : /* tain whitespace, only if */
- case LDNS_RDF_TYPE_WKS : /* it is the last rd field. */
+ case LDNS_RDF_TYPE_NSEC : /* tain whitespace, only if */
+ case LDNS_RDF_TYPE_LOC : /* it is the last rd field. */
+ case LDNS_RDF_TYPE_WKS :
case LDNS_RDF_TYPE_IPSECKEY :
case LDNS_RDF_TYPE_AMTRELAY :
- case LDNS_RDF_TYPE_NSEC : if (r_cnt == r_max - 1) {
+ case LDNS_RDF_TYPE_SVCPARAMS : if (r_cnt == r_max - 1) {
delimiters = "\n";
break;
}
LDNS_RDF_TYPE_INT32,
LDNS_RDF_TYPE_INT8, LDNS_RDF_TYPE_INT8, LDNS_RDF_TYPE_HEX
};
-
+#ifdef RRTYPE_SVCB_HTTPS
+static const ldns_rdf_type type_svcb_wireformat[] = {
+ LDNS_RDF_TYPE_INT16,
+ LDNS_RDF_TYPE_DNAME,
+ LDNS_RDF_TYPE_SVCPARAMS
+};
+#endif
/* nsec3 is some vars, followed by same type of data of nsec */
static const ldns_rdf_type type_nsec3_wireformat[] = {
/* LDNS_RDF_TYPE_NSEC3_VARS, LDNS_RDF_TYPE_NSEC3_NEXT_OWNER, LDNS_RDF_TYPE_NSEC*/
#else
{LDNS_RR_TYPE_NULL, "TYPE61", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
#endif
+ /* 62 */
+ {LDNS_RR_TYPE_CSYNC, "CSYNC", 3, 3, type_csync_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
+ /* 63 */
+ {LDNS_RR_TYPE_ZONEMD, "ZONEMD", 4, 4, type_zonemd_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
+#ifdef RRTYPE_SVCB_HTTPS
+ /* 64 */
+ {LDNS_RR_TYPE_SVCB, "SVCB", 2, 3, type_svcb_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
+ /* 65 */
+ {LDNS_RR_TYPE_HTTPS, "HTTPS", 1, 1, type_svcb_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
-{LDNS_RR_TYPE_CSYNC, "CSYNC", 3, 3, type_csync_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
-{LDNS_RR_TYPE_ZONEMD, "ZONEMD", 4, 4, type_zonemd_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
+#else
{LDNS_RR_TYPE_NULL, "TYPE64", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
{LDNS_RR_TYPE_NULL, "TYPE65", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
+#endif
{LDNS_RR_TYPE_NULL, "TYPE66", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
{LDNS_RR_TYPE_NULL, "TYPE67", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
{LDNS_RR_TYPE_NULL, "TYPE68", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 },
if(!*rd) return LDNS_STATUS_MEM_ERR;
return LDNS_STATUS_OK;
}
+
+#ifdef RRTYPE_SVCB_HTTPS
+static int
+network_uint16_cmp(const void *a, const void *b)
+{
+ return ((int)ldns_read_uint16(a)) - ((int)ldns_read_uint16(b));
+}
+
+static ldns_status parse_svcparam_key(const char **s, ldns_svcparam_key *key);
+static ldns_status
+parse_svcparam_mandatory(const char **s, uint8_t **dp, uint8_t *eod)
+{
+ bool quoted = false;
+ uint8_t *keys = *dp;
+ int prev_key;
+
+ if (**s == '"') {
+ *s += 1;
+ quoted = true;
+ }
+ for (;;) {
+ ldns_status st;
+ ldns_svcparam_key key;
+
+ if ((st = parse_svcparam_key(s, &key)))
+ return st;
+
+ if (*dp + 2 > eod)
+ return LDNS_STATUS_RDATA_OVERFLOW;
+
+ ldns_write_uint16(*dp, key);
+ *dp += 2;
+
+ if (**s == ',')
+ *s += 1;
+ else
+ break;
+ }
+ if (quoted) {
+ if (**s != '"')
+ return LDNS_STATUS_INVALID_STR;
+ *s += 1;
+ }
+ if (*dp - keys == 0)
+ return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+ if (**s && !isspace(**s))
+ return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+ /* In draft-ietf-dnsop-svcb-https-02 Section 7:
+ *
+ * In wire format, the keys are represented by their numeric
+ * values in network byte order, concatenated in ascending order.
+ */
+ qsort(keys, (*dp - keys) / 2, 2, network_uint16_cmp);
+
+ /* In draft-ietf-dnsop-svcb-https-02 Section 7:
+ *
+ * Keys ...<snip>... MUST NOT appear more than once.
+ */
+ prev_key = -1;
+ while (keys < *dp) {
+ uint16_t key = ldns_read_uint16(keys);
+
+ if (key == prev_key) {
+ /* "Be conservative in what you send,
+ * be liberal in what you accept"
+ *
+ * Instead of
+ * `return LDNS_STATUS_SVCPARAM_KEY_MORE_THAN_ONCE;`,
+ *
+ * we eliminate the double occurrence.
+ */
+ memmove(keys - 2, keys, *dp - keys);
+ *dp -= 2;
+ } else {
+ prev_key = key;
+ keys += 2;
+ }
+ }
+ return LDNS_STATUS_OK;
+}
+
+INLINE bool parse_escape2(uint8_t *ch_p, const char** str_p)
+{ *str_p += 1; return parse_escape(ch_p, str_p); }
+
+static ldns_status
+parse_svcparam_alpn(const char **s, uint8_t **dp, uint8_t *eod)
+{
+ uint8_t *val;
+ size_t len;
+
+ if (*dp + 1 > eod)
+ return LDNS_STATUS_RDATA_OVERFLOW;
+ *dp += 1;
+ val = *dp;
+ if (**s == '"') {
+ *s += 1;
+ while (**s != '"') {
+ if (**s == 0)
+ return LDNS_STATUS_INVALID_STR;
+
+ else if (**s == ',') {
+ len = *dp - val;
+ if (len == 0 || len > 255)
+ return LDNS_STATUS_INVALID_STR;
+ val[-1] = len;
+ if (*dp + 1 > eod)
+ return LDNS_STATUS_RDATA_OVERFLOW;
+ *dp += 1;
+ val = *dp;
+ *s += 1;
+
+ } else if (*dp + 1 > eod)
+ return LDNS_STATUS_RDATA_OVERFLOW;
+
+ else if (**s != '\\')
+ *(*dp)++ = (uint8_t)*(*s)++;
+
+ else if (!parse_escape2(*dp, s))
+ return LDNS_STATUS_SYNTAX_BAD_ESCAPE;
+ else
+ *dp += 1;
+ }
+ *s += 1;
+
+ } else while (**s && !isspace(**s)) {
+ if (**s == ',') {
+ len = *dp - val;
+ if (len == 0 || len > 255)
+ return LDNS_STATUS_INVALID_STR;
+ val[-1] = len;
+ if (*dp + 1 > eod)
+ return LDNS_STATUS_RDATA_OVERFLOW;
+ *dp += 1;
+ val = *dp;
+ *s += 1;
+
+ } else if (*dp + 1 > eod)
+ return LDNS_STATUS_RDATA_OVERFLOW;
+
+ else if (**s != '\\')
+ *(*dp)++ = (uint8_t)*(*s)++;
+
+ else if (!parse_escape2(*dp, s))
+ return LDNS_STATUS_SYNTAX_BAD_ESCAPE;
+ else
+ *dp += 1;
+ }
+ len = *dp - val;
+ if (len == 0 || len > 255)
+ return LDNS_STATUS_INVALID_STR;
+ val[-1] = len;
+ return **s && !isspace(**s) ? LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR
+ : LDNS_STATUS_OK;
+}
+
+static ldns_status
+parse_svcparam_value(const char **s, uint8_t **dp, uint8_t *eod)
+{
+ if (**s == '"') {
+ *s += 1;
+ while (**s != '"') {
+ if (**s == 0)
+ return LDNS_STATUS_INVALID_STR;
+
+ else if (*dp + 1 > eod)
+ return LDNS_STATUS_RDATA_OVERFLOW;
+
+ else if (**s != '\\')
+ *(*dp)++ = (uint8_t)*(*s)++;
+
+ else if (!parse_escape2(*dp, s))
+ return LDNS_STATUS_SYNTAX_BAD_ESCAPE;
+ else
+ *dp += 1;
+ }
+ *s += 1;
+
+ } else while (**s && !isspace(**s)) {
+ if (*dp + 1 > eod)
+ return LDNS_STATUS_RDATA_OVERFLOW;
+
+ else if (**s != '\\')
+ *(*dp)++ = (uint8_t)*(*s)++;
+
+ else if (!parse_escape2(*dp, s))
+ return LDNS_STATUS_SYNTAX_BAD_ESCAPE;
+ else
+ *dp += 1;
+ }
+ return **s && !isspace(**s) ? LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR
+ : LDNS_STATUS_OK;
+}
+
+static ldns_status
+parse_svcparam_port(const char **s, uint8_t **dp, uint8_t *eod)
+{
+ uint8_t *val = *dp;
+ ldns_status st;
+ size_t len;
+ char num_str[6];
+ char *endptr;
+ unsigned long int num;
+
+ if ((st = parse_svcparam_value(s, dp, eod)))
+ return st;
+ len = *dp - val;
+ if (len == 0 || len > 5)
+ return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+ memcpy(num_str, val, len);
+ num_str[len] = 0;
+ num = strtoul(num_str, &endptr, 10);
+ if (*endptr)
+ return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+ ldns_write_uint16(val, num);
+ *dp = val + 2;
+ return LDNS_STATUS_OK;
+}
+
+static ldns_status
+parse_svcparam_ipv4hint(const char **s, uint8_t **dp, uint8_t *eod)
+{
+ bool quoted = false;
+
+ if (**s == '"') {
+ *s += 1;
+ quoted = true;
+ }
+ for (;;) {
+ const char *ipv4_start = *s;
+ char ipv4_str[16];
+ size_t len;
+
+ while (isdigit(**s) || **s == '.')
+ *s += 1;
+
+ len = *s - ipv4_start;
+ if (len == 0 || len > 15)
+ return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+ if (*dp + 4 > eod)
+ return LDNS_STATUS_RDATA_OVERFLOW;
+
+ memcpy(ipv4_str, ipv4_start, len);
+ ipv4_str[len] = 0;
+ if (inet_pton(AF_INET, ipv4_str, *dp) != 1)
+ return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+ *dp += 4;
+ if (**s == ',')
+ *s += 1;
+ else
+ break;
+ }
+ if (quoted) {
+ if (**s != '"')
+ return LDNS_STATUS_INVALID_STR;
+ *s += 1;
+ }
+ return **s && !isspace(**s) ? LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR
+ : LDNS_STATUS_OK;
+}
+
+static ldns_status
+parse_svcparam_echconfig(const char **s, uint8_t **dp, uint8_t *eod)
+{
+ bool quoted = false;
+ const char *b64_str;
+ size_t len, pad, out_len;
+ char in_buf[4096];
+ char *in = in_buf;
+ int out;
+
+ if (**s == '"') {
+ *s += 1;
+ quoted = true;
+ }
+ b64_str = *s;
+ while (isalnum(**s) || **s == '+' || **s == '/' || **s == '=')
+ *s += 1;
+
+ len = *s - b64_str;
+ pad = len % 4;
+ pad = pad ? 4 - pad : 0;
+ if (len == 0 || pad == 3)
+ return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+ if (quoted) {
+ if (**s != '"')
+ return LDNS_STATUS_INVALID_STR;
+ *s += 1;
+ }
+ if (**s && !isspace(**s))
+ return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+ out_len = ldns_b64_pton_calculate_size(len);
+ if (*dp + out_len > eod)
+ return LDNS_STATUS_RDATA_OVERFLOW;
+
+ if (len + pad > sizeof(in_buf) - 1
+ && !(in = LDNS_XMALLOC(char, len + pad + 1)))
+ return LDNS_STATUS_MEM_ERR;
+
+ memcpy(in, b64_str, len);
+ while (pad--)
+ in[len++] = '=';
+ in[len] = 0;
+ out = ldns_b64_pton(in, *dp, out_len);
+ if (in != in_buf)
+ LDNS_FREE(in);
+
+ if (out <= 0)
+ return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+ *dp += out;
+ return LDNS_STATUS_OK;
+}
+
+static ldns_status
+parse_svcparam_ipv6hint(const char **s, uint8_t **dp, uint8_t *eod)
+{
+ bool quoted = false;
+
+ if (**s == '"') {
+ *s += 1;
+ quoted = true;
+ }
+ for (;;) {
+ const char *ipv6_start = *s;
+ char ipv6_str[40];
+ size_t len;
+
+ while (isxdigit(**s) || **s == ':')
+ *s += 1;
+
+ len = *s - ipv6_start;
+ if (len == 0 || len > 39)
+ return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+ if (*dp + 16 > eod)
+ return LDNS_STATUS_RDATA_OVERFLOW;
+
+ memcpy(ipv6_str, ipv6_start, len);
+ ipv6_str[len] = 0;
+ if (inet_pton(AF_INET6, ipv6_str, *dp) != 1)
+ return LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR;
+
+ *dp += 16;
+ if (**s == ',')
+ *s += 1;
+ else
+ break;
+ }
+ if (quoted) {
+ if (**s != '"')
+ return LDNS_STATUS_INVALID_STR;
+ *s += 1;
+ }
+ return **s && !isspace(**s) ? LDNS_STATUS_SYNTAX_SVCPARAM_VALUE_ERR
+ : LDNS_STATUS_OK;
+}
+
+struct struct_svcparam_key_def {
+ const char *str;
+ size_t len;
+};
+typedef struct struct_svcparam_key_def svcparam_key_def;
+
+static svcparam_key_def svcparam_key_defs[] = { { "mandatory" , 9 }
+ , { "alpn" , 4 }
+ , { "no-default-alpn", 15 }
+ , { "port" , 4 }
+ , { "ipv4hint" , 8 }
+ , { "echconfig" , 9 }
+ , { "ipv6hint" , 8 } };
+
+static const size_t svcparam_key_defs_len = sizeof(svcparam_key_defs)
+ / sizeof(svcparam_key_def);
+
+/* svcparam_key2buffer_str() should actually be in host2str.c, but we need the
+ * svcparam_key_defs for it and it is not an exposed symbol anyway.
+ */
+ldns_status svcparam_key2buffer_str(ldns_buffer *output, uint16_t key)
+{
+ if (key <= LDNS_SVCPARAM_KEY_LAST_KEY)
+ ldns_buffer_write_string(output, svcparam_key_defs[key].str);
+ else
+ ldns_buffer_printf(output, "key%d", (int)key);
+ return ldns_buffer_status(output);
+}
+
+static ldns_status
+parse_svcparam_key(const char **s, ldns_svcparam_key *key)
+{
+ size_t i, len;
+ const char *key_str = *s;
+ char num_str[6];
+ char *endptr;
+ unsigned long int num;
+
+ /* parse key */
+ while (islower(**s) || isdigit(**s) || **s == '-')
+ *s += 1;
+
+ len = *s - key_str;
+ for (i = 0; i < svcparam_key_defs_len; i++) {
+ if (len == svcparam_key_defs[i].len
+ && !strncmp(key_str, svcparam_key_defs[i].str, len)) {
+ *key = i;
+ return LDNS_STATUS_OK;
+ }
+ }
+ if (len < 4 || len > 8 || strncmp(key_str, "key", 3))
+ return LDNS_STATUS_SYNTAX_SVCPARAM_KEY_ERR;
+
+ memcpy(num_str, key_str + 3, len - 3);
+ num_str[len - 3] = 0;
+ num = strtoul(num_str, &endptr, 10);
+ if (*endptr || num > 65535)
+ return LDNS_STATUS_SYNTAX_SVCPARAM_KEY_ERR;
+
+ /* key65535 is Reserved to be an ("Invalid key"), though there is no
+ * physiological reason to deny usage. We restrict ourselves to the
+ * anatomical limitations only to maximize serviceability.
+ * ```
+ * if (num == 65535)
+ * return LDNS_STATUS_RESERVED_SVCPARAM_KEY;
+ * ```
+ */
+ *key = num;
+ return LDNS_STATUS_OK;
+}
+
+static ldns_status
+parse_svcparam(const char **s, uint8_t **dp, uint8_t *eod)
+{
+ ldns_svcparam_key key;
+ ldns_status st;
+ uint8_t *val;
+
+ if (*dp + 4 > eod)
+ return LDNS_STATUS_RDATA_OVERFLOW;
+
+ if ((st = parse_svcparam_key(s, &key)))
+ return st;
+
+ ldns_write_uint16(*dp, key);
+ ldns_write_uint16(*dp + 2, 0);
+ *dp += 4;
+ if (isspace(**s) || !**s)
+ return LDNS_STATUS_OK;
+
+ else if (**s != '=')
+ return LDNS_STATUS_SYNTAX_ERR;
+ *s += 1;
+ val = *dp;
+ switch(key) {
+ case LDNS_SVCPARAM_KEY_MANDATORY:
+ st = parse_svcparam_mandatory(s, dp, eod);
+ break;
+ case LDNS_SVCPARAM_KEY_ALPN:
+ st = parse_svcparam_alpn(s, dp, eod);
+ break;
+ case LDNS_SVCPARAM_KEY_NO_DEFAULT_ALPN:
+ return LDNS_STATUS_NO_SVCPARAM_VALUE_EXPECTED;
+ case LDNS_SVCPARAM_KEY_PORT:
+ st = parse_svcparam_port(s, dp, eod);
+ break;
+ case LDNS_SVCPARAM_KEY_IPV4HINT:
+ st = parse_svcparam_ipv4hint(s, dp, eod);
+ break;
+ case LDNS_SVCPARAM_KEY_ECHCONFIG:
+ st = parse_svcparam_echconfig(s, dp, eod);
+ break;
+ case LDNS_SVCPARAM_KEY_IPV6HINT:
+ st = parse_svcparam_ipv6hint(s, dp, eod);
+ break;
+ default:
+ st = parse_svcparam_value(s, dp, eod);
+ break;
+ }
+ if (st)
+ return st;
+ ldns_write_uint16(val - 2, *dp - val);
+ return LDNS_STATUS_OK;
+}
+
+static int
+svcparam_ptr_cmp(const void *a, const void *b)
+{
+ uint8_t *x = *(uint8_t **)a , *y = *(uint8_t **)b;
+ uint16_t x_type = ldns_read_uint16(x), y_type = ldns_read_uint16(y);
+ uint16_t x_len , y_len;
+
+ if (x_type != y_type)
+ return x_type > y_type ? 1 : -1;
+
+ x_len = ldns_read_uint16(x + 2);
+ y_len = ldns_read_uint16(y + 2);
+
+ return x_len != y_len
+ ? (x_len > y_len ? 1 : -1)
+ : (x_len == 0 ? 0 : memcmp(x + 4, y + 4, x_len));
+}
+
+ldns_status
+ldns_str2rdf_svcparams(ldns_rdf **rd, const char *str)
+{
+ uint8_t *data, *dp, *eod, *p, *new_data;
+ ldns_status st = LDNS_STATUS_OK;
+ size_t length, i;
+ size_t nparams = 0;
+ uint8_t **svcparams;
+ int prev_key;
+
+ if (!rd || !str)
+ return LDNS_STATUS_NULL;
+
+ length = strlen(str);
+ /* Worst case space requirement. We'll realloc to actual size later. */
+ if (!(dp = data = LDNS_XMALLOC(uint8_t, length)))
+ return LDNS_STATUS_MEM_ERR;
+ eod = data + length;
+
+ /* Fill data with parsed bytes */
+ for (;;) {
+ while (isspace(*str))
+ str += 1;
+ if(!*str)
+ break;
+ if ((st = parse_svcparam(&str, &dp, eod))) {
+ LDNS_FREE(data);
+ return st;
+ }
+ nparams += 1;
+ }
+
+ /* draft-ietf-dnsop-svcb-https-02 in Section 2.2:
+ *
+ * SvcParamKeys SHALL appear in increasing numeric order
+ *
+ * A svcparams array (with pointers to the individual key, value pairs)
+ * is created to qsort the pairs in increasing numeric order.
+ */
+ if (!(svcparams = LDNS_XMALLOC(uint8_t *, nparams))) {
+ LDNS_FREE(data);
+ return LDNS_STATUS_MEM_ERR;
+ }
+ for ( p = data, i = 0
+ ; p < dp && i < nparams
+ ; p += 4 + ldns_read_uint16(p + 2))
+ svcparams[i++] = p;
+
+ qsort(svcparams, i, sizeof(uint8_t *), svcparam_ptr_cmp);
+
+ /* Write out the (key, value) pairs to a newly allocated data in
+ * sorted order.
+ */
+ length = dp - data;
+ if (!(new_data = LDNS_XMALLOC(uint8_t, length))) {
+ LDNS_FREE(data);
+ LDNS_FREE(svcparams);
+ return LDNS_STATUS_MEM_ERR;
+ }
+ prev_key = -1;
+ for ( p = new_data, i = 0
+ ; p < new_data + length && i < nparams
+ ; p += 4 + ldns_read_uint16(p + 2), i += 1) {
+ uint16_t key = ldns_read_uint16(svcparams[i]);
+
+ /* In draft-ietf-dnsop-svcb-https-02 Section 2.1:
+ *
+ * SvcParams ...<snip>... keys MUST NOT be repeated.
+ *
+ * ldns will not impose this limitation on the library user,
+ * but we can merge completely equal repetitions into one.
+ * So, not doing
+ * ```
+ * if (key == prev_key)
+ * return LDNS_STATUS_SVCPARAM_KEY_MORE_THAN_ONCE;
+ * ```
+ * but instead:
+ */
+ if (key == prev_key && ldns_read_uint16(svcparams[i] + 2)
+ == ldns_read_uint16(svcparams[i - 1] + 2)
+ && 0 == memcmp( svcparams[i ] + 4
+ , svcparams[i - 1] + 4
+ , ldns_read_uint16(svcparams[i] + 2))) {
+ p -= 4 + ldns_read_uint16(svcparams[i] + 2);
+ continue;
+ }
+ memcpy(p, svcparams[i], 4 + ldns_read_uint16(svcparams[i] + 2));
+ prev_key = key;
+ }
+ LDNS_FREE(data);
+ LDNS_FREE(svcparams);
+
+ /* Create rdf */
+ *rd = ldns_rdf_new(LDNS_RDF_TYPE_SVCPARAMS, p - new_data, new_data);
+ if (! *rd) {
+ LDNS_FREE(new_data);
+ return LDNS_STATUS_MEM_ERR;
+ }
+ return LDNS_STATUS_OK;
+}
+#else /* #ifdef RRTYPE_SVCB_HTTPS */
+ldns_status
+ldns_str2rdf_svcparams(ldns_rdf **rd, const char *str)
+{
+ (void)rd; (void)str;
+ return LDNS_STATUS_NOT_IMPL;
+}
+#endif /* #ifdef RRTYPE_SVCB_HTTPS */
case LDNS_RDF_TYPE_IPSECKEY:
case LDNS_RDF_TYPE_LONG_STR:
case LDNS_RDF_TYPE_AMTRELAY:
+ case LDNS_RDF_TYPE_SVCPARAMS:
case LDNS_RDF_TYPE_NONE:
/*
* Read to end of rr rdata