/* Copyright (c) 2014, Vsevolod Stakhov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *       * Redistributions of source code must retain the above copyright
 *         notice, this list of conditions and the following disclaimer.
 *       * Redistributions in binary form must reproduce the above copyright
 *         notice, this list of conditions and the following disclaimer in the
 *         documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef DNS_PRIVATE_H_
#define DNS_PRIVATE_H_

#include "config.h"
#include "uthash.h"
#include "utlist.h"
#include "khash.h"
#include "rdns.h"
#include "upstream.h"
#include "ref.h"

static const int dns_port = 53;
static const int default_io_cnt = 8;
static const int default_tcp_io_cnt = 1;

#define UDP_PACKET_SIZE (4096)

#define DNS_COMPRESSION_BITS 0xC0

#define DNS_D_MAXLABEL  63      /* + 1 '\0' */
#define DNS_D_MAXNAME   255     /* + 1 '\0' */

#define RESOLV_CONF "/etc/resolv.conf"

struct dns_header {
	unsigned int qid :16;

#if BYTE_ORDER == BIG_ENDIAN
	unsigned int qr:1;
	unsigned int opcode:4;
	unsigned int aa:1;
	unsigned int tc:1;
	unsigned int rd:1;

	unsigned int ra:1;
	unsigned int cd : 1;
	unsigned int ad : 1;
	unsigned int z : 1;
	unsigned int rcode:4;
#else
	unsigned int rd :1;
	unsigned int tc :1;
	unsigned int aa :1;
	unsigned int opcode :4;
	unsigned int qr :1;

	unsigned int rcode :4;
	unsigned int z : 1;
	unsigned int ad : 1;
	unsigned int cd : 1;
	unsigned int ra :1;
#endif

	unsigned int qdcount :16;
	unsigned int ancount :16;
	unsigned int nscount :16;
	unsigned int arcount :16;
};

/**
 * Represents DNS server
 */
struct rdns_server {
	char *name;
	unsigned int port;
	unsigned int io_cnt;
	unsigned int tcp_io_cnt;

	struct rdns_io_channel **io_channels;
	struct rdns_io_channel **tcp_io_channels;
	void *ups_elt;
	upstream_entry_t up;
};

enum rdns_request_state {
	RDNS_REQUEST_NEW = 0,
	RDNS_REQUEST_REGISTERED = 1,
	RDNS_REQUEST_WAIT_SEND,
	RDNS_REQUEST_WAIT_REPLY,
	RDNS_REQUEST_REPLIED,
	RDNS_REQUEST_FAKE,
	RDNS_REQUEST_ERROR,
	RDNS_REQUEST_TCP,
};

struct rdns_request {
	struct rdns_resolver *resolver;
	struct rdns_async_context *async;
	struct rdns_io_channel *io;
	struct rdns_reply *reply;
	enum rdns_request_type type;

	double timeout;
	unsigned int retransmits;

	int id;
	struct rdns_request_name *requested_names;
	unsigned int qcount;
	enum rdns_request_state state;

	uint8_t *packet;
	off_t pos;
	unsigned int packet_len;

	dns_callback_type func;
	void *arg;

	void *async_event;

#if defined(TWEETNACL) || defined(USE_RSPAMD_CRYPTOBOX)
	void *curve_plugin_data;
#endif

	ref_entry_t ref;
};


enum rdns_io_channel_flags {
	RDNS_CHANNEL_CONNECTED = 1u << 0u,
	RDNS_CHANNEL_ACTIVE = 1u << 1u,
	RDNS_CHANNEL_TCP = 1u << 2u,
	RDNS_CHANNEL_TCP_CONNECTING = 1u << 3u,
};

#define IS_CHANNEL_CONNECTED(ioc) (((ioc)->flags & RDNS_CHANNEL_CONNECTED) != 0)
#define IS_CHANNEL_ACTIVE(ioc) (((ioc)->flags & RDNS_CHANNEL_ACTIVE) != 0)
#define IS_CHANNEL_TCP(ioc) (((ioc)->flags & RDNS_CHANNEL_TCP) != 0)

/**
 * Used to chain output DNS requests for a TCP connection
 */
struct rdns_tcp_output_chain {
	uint16_t next_write_size; /* Network byte order! */
	uint16_t cur_write; /* Cur bytes written including `next_write_size` */
	unsigned char *write_buf;
	struct rdns_tcp_output_chain *prev, *next;
};

/**
 * Specific stuff for a TCP IO chain
 */
struct rdns_tcp_channel {
	uint16_t next_read_size; /* Network byte order on read, then host byte order */
	uint16_t cur_read; /* Cur bytes read including `next_read_size` */
	unsigned char *cur_read_buf;
	unsigned read_buf_allocated;

	/* Chained set of the planned writes */
	struct rdns_tcp_output_chain *output_chain;
	unsigned cur_output_chains;

	void *async_read; /** async read event */
	void *async_write; /** async write event */
};

KHASH_DECLARE(rdns_requests_hash, int, struct rdns_request *);
#define RDNS_IO_CHANNEL_TAG UINT64_C(0xe190a5ba12f094c8)
/**
 * IO channel for a specific DNS server
 */
struct rdns_io_channel {
	uint64_t struct_magic; /**< tag for this structure */
	struct rdns_server *srv;
	struct rdns_resolver *resolver;
	struct sockaddr *saddr;
	socklen_t slen;
	int sock; /**< persistent socket                                          */
	int flags; /**< see enum rdns_io_channel_flags */
	void *async_io; /** async opaque ptr */
	khash_t(rdns_requests_hash) *requests;
	/*
	 * For DNS replies parsing we use per-channel structure
	 * which is used for two purposes:
	 * 1) We read the next DNS header
	 * 2) We find the corresponding request (if any)
	 * 3) We read the remaining packet (associated with a request or dangling)
	 * This structure is filled on each read-readiness for an IO channel
	 */
	struct rdns_tcp_channel *tcp;
	uint64_t uses;
	ref_entry_t ref;
};

struct rdns_fake_reply_idx {
	enum rdns_request_type type;
	unsigned len;
	char request[0];
};

struct rdns_fake_reply {
	enum dns_rcode rcode;
	struct rdns_reply_entry *result;
	UT_hash_handle hh;
	struct rdns_fake_reply_idx key;
};


struct rdns_resolver {
	struct rdns_server *servers;
	struct rdns_async_context *async; /** async callbacks */
	void *periodic; /** periodic event for resolver */
	struct rdns_upstream_context *ups;
	struct rdns_plugin *curve_plugin;
	struct rdns_fake_reply *fake_elts;

#ifdef __GNUC__
	__attribute__((format(printf, 4, 0)))
#endif
	rdns_log_function logger;
	void *log_data;
	enum rdns_log_level log_level;

	uint64_t max_ioc_uses;
	void *refresh_ioc_periodic;

	bool async_binded;
	bool initialized;
	bool enable_dnssec;
	int flags;
	ref_entry_t ref;
};

struct dns_query;

/* Internal DNS structs */

enum dns_section {
	DNS_S_QD = 0x01,
#define DNS_S_QUESTION          DNS_S_QD

	DNS_S_AN = 0x02,
#define DNS_S_ANSWER            DNS_S_AN

	DNS_S_NS = 0x04,
#define DNS_S_AUTHORITY         DNS_S_NS

	DNS_S_AR = 0x08,
#define DNS_S_ADDITIONAL        DNS_S_AR

	DNS_S_ALL = 0x0f
};
/* enum dns_section */

enum dns_opcode {
	DNS_OP_QUERY = 0,
	DNS_OP_IQUERY = 1,
	DNS_OP_STATUS = 2,
	DNS_OP_NOTIFY = 4,
	DNS_OP_UPDATE = 5,
};
/* dns_opcode */

enum dns_class {
	DNS_C_IN = 1,

	DNS_C_ANY = 255
};
/* enum dns_class */

struct dns_query {
	char *qname;
	unsigned int qtype :16;
	unsigned int qclass :16;
};

enum dns_type {
	DNS_T_A = RDNS_REQUEST_A,
	DNS_T_NS = RDNS_REQUEST_NS,
	DNS_T_CNAME = 5,
	DNS_T_SOA = RDNS_REQUEST_SOA,
	DNS_T_PTR = RDNS_REQUEST_PTR,
	DNS_T_MX = RDNS_REQUEST_MX,
	DNS_T_TXT = RDNS_REQUEST_TXT,
	DNS_T_AAAA = RDNS_REQUEST_AAAA,
	DNS_T_SRV = RDNS_REQUEST_SRV,
	DNS_T_OPT = 41,
	DNS_T_SSHFP = 44,
	DNS_T_TLSA = RDNS_REQUEST_TLSA,
	DNS_T_SPF = RDNS_REQUEST_SPF,
	DNS_T_ALL = RDNS_REQUEST_ANY
};
/* enum dns_type */

static const char dns_rcodes[][32] = {
	[RDNS_RC_NOERROR]  = "no error",
	[RDNS_RC_FORMERR]  = "query format error",
	[RDNS_RC_SERVFAIL] = "server fail",
	[RDNS_RC_NXDOMAIN] = "no records with this name",
	[RDNS_RC_NOTIMP]   = "not implemented",
	[RDNS_RC_REFUSED]  = "query refused",
	[RDNS_RC_YXDOMAIN] = "YXDOMAIN",
	[RDNS_RC_YXRRSET]  = "YXRRSET",
	[RDNS_RC_NXRRSET]  = "NXRRSET",
	[RDNS_RC_NOTAUTH]  = "not authorized",
	[RDNS_RC_NOTZONE]  = "no such zone",
	[RDNS_RC_TIMEOUT]  = "query timed out",
	[RDNS_RC_NETERR]  = "network error",
	[RDNS_RC_NOREC]  = "requested record is not found"
};

static const char dns_types[][16] = {
	[RDNS_REQUEST_A] = "A request",
	[RDNS_REQUEST_NS] = "NS request",
	[RDNS_REQUEST_PTR] = "PTR request",
	[RDNS_REQUEST_MX] = "MX request",
	[RDNS_REQUEST_TXT] = "TXT request",
	[RDNS_REQUEST_SRV] = "SRV request",
	[RDNS_REQUEST_SPF] = "SPF request",
	[RDNS_REQUEST_AAAA] = "AAAA request",
	[RDNS_REQUEST_TLSA] = "TLSA request",
	[RDNS_REQUEST_ANY] = "ANY request"
};


#endif /* DNS_PRIVATE_H_ */