text: /*
   * Copyright (c) 2016 Intel Corporation
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  
  #include <string.h>
  #include <net/buf.h>
  
  #include "dns_pack.h"
  
  #include "dns_internal.h"
  
  static inline uint16_t dns_strlen(const char *str)
  {
  	if (str == NULL) {
  		return 0;
  	}
  	return (uint16_t)strlen(str);
  }
  
  int dns_msg_pack_qname(uint16_t *len, uint8_t *buf, uint16_t size,
  		       const char *domain_name)
  {
  	uint16_t dn_size;
  	uint16_t lb_start;
  	uint16_t lb_index;
  	uint16_t lb_size;
  	uint16_t i;
  
  	lb_start = 0U;
  	lb_index = 1U;
  	lb_size = 0U;
  
  	dn_size = dns_strlen(domain_name);
  	if (dn_size == 0U) {
  		return -EINVAL;
  	}
  
  	/* traverse the domain name str, including the null-terminator :) */
  	for (i = 0U; i < dn_size + 1; i++) {
  		if (lb_index >= size) {
  			return -ENOMEM;
  		}
  
  		switch (domain_name[i]) {
  		default:
  			buf[lb_index] = domain_name[i];
  			lb_size += 1U;
  			break;
  		case '.':
  			buf[lb_start] = lb_size;
  			lb_size = 0U;
  			lb_start = lb_index;
  			break;
  		case '\0':
  			buf[lb_start] = lb_size;
  			buf[lb_index] = 0U;
  			break;
  		}
  		lb_index += 1U;
  	}
  
  	*len = lb_index;
  
  	return 0;
  }
  
  static inline void set_dns_msg_response(struct dns_msg_t *dns_msg, int type,
  					uint16_t pos, uint16_t len)
  {
  	dns_msg->response_type = type;
  	dns_msg->response_position = pos;
  	dns_msg->response_length = len;
  }
  
  /*
   * Skip encoded FQDN in DNS message.
   * Returns size in bytes of encoded FQDN, or negative error code.
   */
  static int skip_fqdn(uint8_t *answer, int buf_sz)
  {
  	int i = 0;
  
  	while (1) {
  		if (i >= buf_sz) {
  			return -EINVAL;
  		}
  
  		if (answer[i] == 0) {
  			i += 1;
  			break;
  		} else if (answer[i] >= 0xc0) {
  			i += 2;
  			if (i > buf_sz) {
  				return -EINVAL;
  			}
  			break;
  		} else if (answer[i] < DNS_LABEL_MAX_SIZE) {
  			i += answer[i] + 1;
  		} else {
  			return -EINVAL;
  		}
  	}
  
  	return i;
  }
  
  int dns_unpack_answer(struct dns_msg_t *dns_msg, int dname_ptr, uint32_t *ttl,
  		      enum dns_rr_type *type)
  {
  	int dname_len;
  	uint16_t rem_size;
  	uint16_t pos;
  	uint16_t len;
  	uint8_t *answer;
  
  	answer = dns_msg->msg + dns_msg->answer_offset;
  
  	dname_len = skip_fqdn(answer,
  			      dns_msg->msg_size - dns_msg->answer_offset);
  	if (dname_len < 0) {
  		return dname_len;
  	}
  
  	/*
  	 * We need to be sure this buffer has enough space
  	 * to contain the answer.
  	 *
  	 * size: dname_size + type + class + ttl + rdlength + rdata
  	 *            2     +   2  +   2   +  4  +     2    +  ?
  	 *
  	 * So, answer size >= 12
  	 *
  	 * See RFC-1035 4.1.3. Resource record format
  	 */
  	rem_size = dns_msg->msg_size - dname_len;
  	if (rem_size < 2 + 2 + 4 + 2) {
  		return -EINVAL;
  	}
  
  	/* Only DNS_CLASS_IN answers. If mDNS is enabled, strip away the
  	 * Cache-Flush bit (highest one).
  	 */
  	if ((dns_answer_class(dname_len, answer) &
  	     (IS_ENABLED(CONFIG_MDNS_RESOLVER) ? 0x7fff : 0xffff))
  							!= DNS_CLASS_IN) {
  		return -EINVAL;
  	}
  
  	/* TTL value */
  	*ttl = dns_answer_ttl(dname_len, answer);
  	len = dns_answer_rdlength(dname_len, answer);
  	pos = dns_msg->answer_offset + dname_len +
  		DNS_COMMON_UINT_SIZE + /* class length */
  		DNS_COMMON_UINT_SIZE + /* type length */
  		DNS_TTL_LEN +
  		DNS_RDLENGTH_LEN;
  	*type = dns_answer_type(dname_len, answer);
  
  	switch (*type) {
  	case DNS_RR_TYPE_A:
  	case DNS_RR_TYPE_AAAA:
  		set_dns_msg_response(dns_msg, DNS_RESPONSE_IP, pos, len);
  		return 0;
  
  	case DNS_RR_TYPE_CNAME:
  		set_dns_msg_response(dns_msg, DNS_RESPONSE_CNAME_NO_IP,
  				     pos, len);
  		return 0;
  
  	default:
  		/* malformed dns answer */
  		return -EINVAL;
  	}
  
  	return 0;
  }
  
  int dns_unpack_response_header(struct dns_msg_t *msg, int src_id)
  {
  	uint8_t *dns_header;
  	uint16_t size;
  	int qdcount;
  	int ancount;
  	int rc;
  
  	dns_header = msg->msg;
  	size = msg->msg_size;
  
  	if (size < DNS_MSG_HEADER_SIZE) {
  		return -ENOMEM;
  	}
  
  	if (dns_unpack_header_id(dns_header) != src_id) {
  		return -EINVAL;
  	}
  
  	if (dns_header_qr(dns_header) != DNS_RESPONSE) {
  		return -EINVAL;
  	}
  
  	if (dns_header_opcode(dns_header) != DNS_QUERY) {
  		return -EINVAL;
  	}
  
  	if (dns_header_z(dns_header) != 0) {
  		return -EINVAL;
  	}
  
  	rc = dns_header_rcode(dns_header);
  	switch (rc) {
  	case DNS_HEADER_NOERROR:
  		break;
  	default:
  		return rc;
  
  	}
  
  	qdcount = dns_unpack_header_qdcount(dns_header);
  	ancount = dns_unpack_header_ancount(dns_header);
  
  	/* For mDNS (when src_id == 0) the query count is 0 so accept
  	 * the packet in that case.
  	 */
  	if ((qdcount < 1 && src_id > 0) || ancount < 1) {
  		return -EINVAL;
  	}
  
  	return 0;
  }
  
  static int dns_msg_pack_query_header(uint8_t *buf, uint16_t size, uint16_t id)
  {
  	uint16_t offset;
  
  	if (size < DNS_MSG_HEADER_SIZE) {
  		return -ENOMEM;
  	}
  
  	UNALIGNED_PUT(htons(id), (uint16_t *)(buf));
  
  	/* RD = 1, TC = 0, AA = 0, Opcode = 0, QR = 0 <-> 0x01 (1B)
  	 * RCode = 0, Z = 0, RA = 0		      <-> 0x00 (1B)
  	 *
  	 * QDCOUNT = 1				      <-> 0x0001 (2B)
  	 */
  
  	offset = DNS_HEADER_ID_LEN;
  	/* Split the following assignments just in case we need to alter
  	 * the flags in future releases
  	 */
  	*(buf + offset) = DNS_FLAGS1;		/* QR, Opcode, AA, TC and RD */
  	*(buf + offset + 1) = DNS_FLAGS2;	/* RA, Z and RCODE */
  
  	offset += DNS_HEADER_FLAGS_LEN;
  	/* set question counter */
  	UNALIGNED_PUT(htons(1), (uint16_t *)(buf + offset));
  
  	offset += DNS_QDCOUNT_LEN;
  	/* set answer and ns rr */
  	UNALIGNED_PUT(0, (uint32_t *)(buf + offset));
  
  	offset += DNS_ANCOUNT_LEN + DNS_NSCOUNT_LEN;
  	/* set the additional records */
  	UNALIGNED_PUT(0, (uint16_t *)(buf + offset));
  
  	return 0;
  }
  
  int dns_msg_pack_query(uint8_t *buf, uint16_t *len, uint16_t size,
  		       uint8_t *qname, uint16_t qname_len, uint16_t id,
  		       enum dns_rr_type qtype)
  {
  	uint16_t msg_size;
  	uint16_t offset;
  	int rc;
  
  	msg_size = DNS_MSG_HEADER_SIZE + DNS_QTYPE_LEN + DNS_QCLASS_LEN;
  	if (msg_size + qname_len > size) {
  		return -ENOMEM;
  	}
  
  	rc = dns_msg_pack_query_header(buf, size, id);
  	if (rc != 0) {
  		return rc;
  	}
  
  	offset = DNS_MSG_HEADER_SIZE;
  	memcpy(buf + offset, qname, qname_len);
  
  	offset += qname_len;
  
  	/* QType */
  	UNALIGNED_PUT(htons(qtype), (uint16_t *)(buf + offset + 0));
  	offset += DNS_QTYPE_LEN;
  
  	/* QClass */
  	UNALIGNED_PUT(htons(DNS_CLASS_IN), (uint16_t *)(buf + offset));
  
  	*len = offset + DNS_QCLASS_LEN;
  
  	return 0;
  }
  
  static int dns_find_null(int *qname_size, uint8_t *buf, uint16_t size)
  {
  	*qname_size = 0;
  	while (*qname_size < size) {
  		if (buf[(*qname_size)++] == 0x00) {
  			return 0;
  		}
  	}
  
  	return -ENOMEM;
  }
  
  int dns_unpack_response_query(struct dns_msg_t *dns_msg)
  {
  	uint8_t *dns_query;
  	uint8_t *buf;
  	int remaining_size;
  	int qname_size;
  	int offset;
  	int rc;
  
  	dns_msg->query_offset = DNS_MSG_HEADER_SIZE;
  	dns_query = dns_msg->msg + dns_msg->query_offset;
  	remaining_size = dns_msg->msg_size - dns_msg->query_offset;
  
  	rc = dns_find_null(&qname_size, dns_query, remaining_size);
  	if (rc != 0) {
  		return rc;
  	}
  
  	/* header already parsed + qname size */
  	offset = dns_msg->query_offset + qname_size;
  
  	/* 4 bytes more due to qtype and qclass */
  	offset += DNS_QTYPE_LEN + DNS_QCLASS_LEN;
  	if (offset >= dns_msg->msg_size) {
  		return -ENOMEM;
  	}
  
  	buf = dns_query + qname_size;
  	if (dns_unpack_query_qtype(buf) != DNS_RR_TYPE_A &&
  	    dns_unpack_query_qtype(buf) != DNS_RR_TYPE_AAAA) {
  		return -EINVAL;
  	}
  
  	if (dns_unpack_query_qclass(buf) != DNS_CLASS_IN) {
  		return -EINVAL;
  	}
  
  	dns_msg->answer_offset = dns_msg->query_offset + qname_size +
  				 DNS_QTYPE_LEN + DNS_QCLASS_LEN;
  
  	return 0;
  }
  
  int dns_copy_qname(uint8_t *buf, uint16_t *len, uint16_t size,
  		   struct dns_msg_t *dns_msg, uint16_t pos)
  {
  	uint16_t msg_size = dns_msg->msg_size;
  	uint8_t *msg = dns_msg->msg;
  	uint16_t lb_size;
  	int rc = -EINVAL;
  
  	*len = 0U;
  
  	while (1) {
  		if (pos >= msg_size) {
  			rc = -ENOMEM;
  			break;
  		}
  
  		lb_size = msg[pos];
  
  		/* pointer */
  		if (lb_size > DNS_LABEL_MAX_SIZE) {
  			uint8_t mask = DNS_LABEL_MAX_SIZE;
  
  			if (pos + 1 >= msg_size) {
  				rc = -ENOMEM;
  				break;
  			}
  
  			/* See: RFC 1035, 4.1.4. Message compression */
  			pos = ((msg[pos] & mask) << 8) + msg[pos + 1];
  
  			continue;
  		}
  
  		/* validate that the label (i.e. size + elements),
  		 * fits the current msg buffer
  		 */
  		if (DNS_LABEL_LEN_SIZE + lb_size > size - *len) {
  			rc = -ENOMEM;
  			break;
  		}
  
  		/* copy the lb_size value and label elements */
  		memcpy(buf + *len, msg + pos, DNS_LABEL_LEN_SIZE + lb_size);
  		/* update destination buffer len */
  		*len += DNS_LABEL_LEN_SIZE + lb_size;
  		/* update msg ptr position */
  		pos += DNS_LABEL_LEN_SIZE + lb_size;
  
  		/* The domain name terminates with the zero length octet
  		 * for the null label of the root
  		 */
  		if (lb_size == 0U) {
  			rc = 0;
  			break;
  		}
  	}
  
  	return rc;
  }
  
  int mdns_unpack_query_header(struct dns_msg_t *msg, uint16_t *src_id)
  {
  	uint8_t *dns_header;
  	uint16_t size;
  	int qdcount;
  
  	dns_header = msg->msg;
  	size = msg->msg_size;
  
  	if (size < DNS_MSG_HEADER_SIZE) {
  		return -ENOMEM;
  	}
  
  	if (dns_header_qr(dns_header) != DNS_QUERY) {
  		return -EINVAL;
  	}
  
  	if (dns_header_opcode(dns_header) != DNS_QUERY) {
  		return -EINVAL;
  	}
  
  	if (dns_header_opcode(dns_header) != 0) {
  		return -EINVAL;
  	}
  
  	if (dns_header_rcode(dns_header) != 0) {
  		return -EINVAL;
  	}
  
  	qdcount = dns_unpack_header_qdcount(dns_header);
  	if (qdcount < 1) {
  		return -EINVAL;
  	}
  
  	if (src_id) {
  		*src_id = dns_unpack_header_id(dns_header);
  	}
  
  	msg->query_offset = DNS_MSG_HEADER_SIZE;
  
  	return qdcount;
  }
  
  /* Returns the length of the unpacked name */
  static int dns_unpack_name(const uint8_t *msg, int maxlen, const uint8_t *src,
  			   struct net_buf *buf, const uint8_t **eol)
  {
  	int dest_size = net_buf_tailroom(buf);
  	const uint8_t *end_of_label = NULL;
  	const uint8_t *curr_src = src;
  	int loop_check = 0, len = -1;
  	int label_len;
  	int val;
  
  	if (curr_src < msg || curr_src >= (msg + maxlen)) {
  		return -EMSGSIZE;
  	}
  
  	while ((val = *curr_src++)) {
  		if (val & NS_CMPRSFLGS) {
  			/* Follow pointer */
  			int pos;
  
  			if (curr_src >= (msg + maxlen)) {
  				return -EMSGSIZE;
  			}
  
  			if (len < 0) {
  				len = curr_src - src + 1;
  			}
  
  			end_of_label = curr_src + 1;
  
  			/* Strip compress bits from length calculation */
  			pos = ((val & 0x3f) << 8) | (*curr_src & 0xff);
  
  			curr_src = msg + pos;
  			if (curr_src >= (msg + maxlen)) {
  				return -EMSGSIZE;
  			}
  
  			loop_check += 2;
  			if (loop_check >= maxlen) {
  				return -EMSGSIZE;
  			}
  		} else {
  			/* Max label length is 64 bytes (because 2 bits are
  			 * used for pointer)
  			 */
  			label_len = val;
  			if (label_len > 63) {
  				return -EMSGSIZE;
  			}
  
  			if (((buf->data + label_len + 1) >=
  			     (buf->data + dest_size)) ||
  			    ((curr_src + label_len) >= (msg + maxlen))) {
  				return -EMSGSIZE;
  			}
  
  			loop_check += label_len + 1;
  
  			net_buf_add_u8(buf, '.');
  			net_buf_add_mem(buf, curr_src, label_len);
  
  			curr_src += label_len;
  		}
  	}
  
  	buf->data[buf->len] = '\0';
  
  	if (eol) {
  		if (!end_of_label) {
  			end_of_label = curr_src;
  		}
  
  		*eol = end_of_label;
  	}
  
  	return buf->len;
  }
  
  int dns_unpack_query(struct dns_msg_t *dns_msg, struct net_buf *buf,
  		     enum dns_rr_type *qtype, enum dns_class *qclass)
  {
  	const uint8_t *end_of_label;
  	uint8_t *dns_query;
  	int remaining_size;
  	int ret;
  	int query_type, query_class;
  
  	dns_query = dns_msg->msg + dns_msg->query_offset;
  	remaining_size = dns_msg->msg_size - dns_msg->query_offset;
  
  	ret = dns_unpack_name(dns_msg->msg, dns_msg->msg_size, dns_query,
  			      buf, &end_of_label);
  	if (ret < 0) {
  		return ret;
  	}
  
  	query_type = dns_unpack_query_qtype(end_of_label);
  	if (query_type != DNS_RR_TYPE_A && query_type != DNS_RR_TYPE_AAAA
  		&& query_type != DNS_RR_TYPE_PTR
  		&& query_type != DNS_RR_TYPE_SRV
  		&& query_type != DNS_RR_TYPE_TXT) {
  		return -EINVAL;
  	}
  
  	query_class = dns_unpack_query_qclass(end_of_label);
  	if (query_class != DNS_CLASS_IN) {
  		return -EINVAL;
  	}
  
  	if (qtype) {
  		*qtype = query_type;
  	}
  
  	if (qclass) {
  		*qclass = query_class;
  	}
  
  	dns_msg->query_offset = end_of_label - dns_msg->msg + 2 + 2;
  
  	return ret;
  }
  