#include "fmt.h"
#include "iobuf.h"
#include "ipv4.h"
#include "msg.h"
#include "socket.h"
#include "uint16.h"
#include "uint32.h"
#include <sys/wait.h>

#define DUMP void dump_rrs(int count, const union dns_result_rrs* rr)
#define NODUMP DUMP { (void)count; (void)rr; }
#define RESPONSE static const struct dns_response

extern DUMP;

static int dns_responder_pid = 0;
extern ipv4port dns_use_port;
struct dns_response_rr
{
  const char* name;
  uint16 namelen;
  uint16 type;
  uint16 class;
  uint32 ttl;
  const char* rdata;
  uint16 rdatalen;
};
struct dns_response
{
  uint16 ancount, nscount, arcount;
  struct dns_response_rr rr[8];
};

static long dump_name(const unsigned char* data)
{
  long o = 0;
  unsigned char l;
  while ((l = data[o++]) > 0) {
    obuf_write(&outbuf, (const char*)data+o, l);
    obuf_putc(&outbuf, '.');
    o += l;
  };
  return o;
}

static long dump_request(const unsigned char* packet, long len)
{
  uint16 first = uint16_get_msb(packet+2);
  uint16 qdcount = uint16_get_msb(packet+4);
  uint16 ancount = uint16_get_msb(packet+6);
  uint16 nscount = uint16_get_msb(packet+8);
  uint16 arcount = uint16_get_msb(packet+10);
  long o = 12;
  obuf_putf(&outbuf, "d{: ID=XX QR=}d{ opcode=}d{ AA=}d{ TC=}d{ RD=}d{ RA=}d{ Z=}d{ RCODE=}d{ QDCOUNT=}d{ ANCOUNT=}d{ NSCOUNT=}d{ ARCOUNT=}d{\n}",
	    len,
	    first >> 15,	/* QR */
	    (first >> 11) & 0xf, /* Opcode */
	    (first >> 10) & 1,	 /* AA */
	    (first >> 9) & 1,	 /* TC */
	    (first >> 8) & 1,	 /* RD */
	    (first >> 7) & 1,	 /* RA */
	    (first >> 4) & 7,	 /* Z */
	    first & 0xf,	 /* RCODE */
	    qdcount, ancount, nscount, arcount);
  for (; qdcount > 0; qdcount--) {
    obuf_puts(&outbuf, "Question: ");
    o += dump_name(packet+o);
    obuf_putf(&outbuf, "{ QTYPE=}d{ QCLASS=}d{\n}", uint16_get_msb(packet+o), uint16_get_msb(packet+o+2));
    o += 4;
  }
  obuf_flush(&outbuf);
  return o;
}

static int make_response(unsigned char* buf, int offset, const struct dns_response* response)
{
  int r;
  uint16_pack_msb(response->ancount, buf+6);
  uint16_pack_msb(response->nscount, buf+8);
  uint16_pack_msb(response->arcount, buf+10);
  for (r = 0; r < response->ancount + response->nscount + response->arcount; r++) {
    const struct dns_response_rr* rr = &response->rr[r];
    memcpy((char*)buf+offset, rr->name, rr->namelen);
    offset += rr->namelen;
    uint16_pack_msb(rr->type, buf+offset);
    uint16_pack_msb(rr->class, buf+offset+2);
    uint32_pack_msb(rr->ttl, buf+offset+4);
    uint16_pack_msb(rr->rdatalen, buf+offset+8);
    offset += 10;
    memcpy((char*)buf+offset, rr->rdata, rr->rdatalen);
    offset += rr->rdatalen;
  }
  return offset;
}

static void start_dns_responder(const struct dns_response* response, int count)
{
  int sockfd;
  ipv4addr addr;
  ipv4port port;
  unsigned char buf[512];
  long r;
  long offset;

  if ((sockfd = socket_udp()) < 0)
    die1sys(111, "Could not create DNS socket");
  ipv4_scan("127.0.0.1", &addr);
  if (!socket_bind4(sockfd, &addr, 0))
    die1sys(111, "Could not bind DNS socket");
  if (!socket_getaddr4(sockfd, &addr, &port))
    die1sys(111, "Could not get DNS socket address");
  if (0 && !socket_listen(sockfd, 0))
    die1sys(111, "Could not listen to DNS socket");
  dns_use_port = port;
  buf[fmt_sdec((char*)buf, port)] = 0;
  if (setenv("DNSCACHEIP", "127.0.0.1", 1) < 0
      || setenv("DNSCACHEPORT", (char*)buf, 1) < 0)
    die1sys(111, "Could not set up DNS responder environment variables");
  if ((dns_responder_pid = fork()) < 0)
    die1sys(111, "Could not fork");
  if (dns_responder_pid != 0)
    return;
  for (; count > 0; ++response, --count) {
    if ((r = socket_recv4(sockfd, (char*)buf, sizeof buf, &addr, &port)) < 0)
      die1sys(111, "Could not receive DNS packet");
    offset = dump_request(buf, r);
    offset = make_response(buf, offset, response);
    if ((r = socket_send4(sockfd, (char*)buf, offset, &addr, port)) != offset)
      die1sys(111, "Could not send DNS response");
  }
  exit(0);
}

static void wait_dns_responder(void)
{
  waitpid(dns_responder_pid, NULL, 0);
}

void do_dns_test(const char* fqdn, int (*fn)(struct dns_result*, const char*))
{
  struct dns_result out = {0};
  debugfn(fn(&out, fqdn));
  obuf_putf(&outbuf, "s{: count=}d{\n}", fqdn, out.count);
  dump_rrs(out.count, &out.rr);
}

void do_dns_respond_tests(int (*fn)(struct dns_result*, const char*),
                          const char** fqdn,
                          const struct dns_response* responses, int count)
{
  start_dns_responder(responses, count);
  while (*fqdn) {
    do_dns_test(*fqdn++, fn);
    obuf_flush(&outbuf);
  }
  wait_dns_responder();
}
