/*
 * Copyright (c) 2013 by Wil Tan <wil@cloudregistry.net>
 *
 * Based on dump_dns.c from the dnscap <https://www.dns-oarc.net/tools/dnscap>
 * originally written by Paul Vixie.
 *
 * Copyright (c) 2007 by Internet Systems Consortium, Inc. ("ISC")
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "libc/calls/typedef/u.h"
#include "libc/errno.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/sock.h"
#include "libc/stdio/internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/af.h"
#include "third_party/musl/nameser.h"
#include "third_party/musl/resolv.h"

const char *_res_opcodes[] = {
    "QUERY",     //
    "IQUERY",    //
    "CQUERYM",   //
    "CQUERYU",   //
    "NOTIFY",    //
    "UPDATE",    //
    "6",         //
    "7",         //
    "8",         //
    "9",         //
    "10",        //
    "11",        //
    "12",        //
    "13",        //
    "ZONEINIT",  //
    "ZONEREF",   //
};

#define MY_GET16(s, cp)                                     \
  do {                                                      \
    register const u_char *t_cp = (const u_char *)(cp);     \
    (s) = ((u_int16_t)t_cp[0] << 8) | ((u_int16_t)t_cp[1]); \
    (cp) += NS_INT16SZ;                                     \
  } while (0)

#define MY_GET32(l, cp)                                             \
  do {                                                              \
    register const u_char *t_cp = (const u_char *)(cp);             \
    (l) = ((u_int32_t)t_cp[0] << 24) | ((u_int32_t)t_cp[1] << 16) | \
          ((u_int32_t)t_cp[2] << 8) | ((u_int32_t)t_cp[3]);         \
    (cp) += NS_INT32SZ;                                             \
  } while (0)

static void dump_dns_rr(ns_msg *msg, ns_rr *rr, ns_sect sect, FILE *trace) {
  char buf[NS_MAXDNAME];
  u_int class, type;
  const u_char *rd;
  u_int32_t soa[5];
  u_int16_t mx;
  int n;

  class = ns_rr_class(*rr);
  type = ns_rr_type(*rr);
  fprintf(trace, "%s,%d,%d", ns_rr_name(*rr), class, type);
  if (sect == ns_s_qd)
    return;
  fprintf(trace, ",%lu", (u_long)ns_rr_ttl(*rr));
  rd = ns_rr_rdata(*rr);
  switch (type) {
    case ns_t_soa:
      n = ns_name_uncompress(ns_msg_base(*msg), ns_msg_end(*msg), rd, buf,
                             sizeof buf);
      if (n < 0)
        goto error;
      putc(',', trace);
      fputs(buf, trace);
      rd += n;
      n = ns_name_uncompress(ns_msg_base(*msg), ns_msg_end(*msg), rd, buf,
                             sizeof buf);
      if (n < 0)
        goto error;
      putc(',', trace);
      fputs(buf, trace);
      rd += n;
      if (ns_msg_end(*msg) - rd < 5 * NS_INT32SZ)
        goto error;
      for (n = 0; n < 5; n++)
        MY_GET32(soa[n], rd);
      sprintf(buf, "%u,%u,%u,%u,%u", soa[0], soa[1], soa[2], soa[3], soa[4]);
      break;
    case ns_t_a:
      inet_ntop(AF_INET, rd, buf, sizeof buf);
      break;
    case ns_t_aaaa:
      inet_ntop(AF_INET6, rd, buf, sizeof buf);
      break;
    case ns_t_mx:
      MY_GET16(mx, rd);
      fprintf(trace, ",%u", mx);
      /* FALLTHROUGH */
    case ns_t_ns:
    case ns_t_ptr:
    case ns_t_cname:
      n = ns_name_uncompress(ns_msg_base(*msg), ns_msg_end(*msg), rd, buf,
                             sizeof buf);
      if (n < 0)
        goto error;
      break;
    case ns_t_txt:
      snprintf(buf, (size_t)rd[0] + 1, "%s", rd + 1);
      break;
    default:
    error:
      sprintf(buf, "[%u]", ns_rr_rdlen(*rr));
  }
  if (buf[0] != '\0') {
    putc(',', trace);
    fputs(buf, trace);
  }
}

static void dump_dns_sect(ns_msg *msg, ns_sect sect, FILE *trace,
                          const char *endline) {
  int rrnum, rrmax;
  const char *sep;
  ns_rr rr;

  rrmax = ns_msg_count(*msg, sect);
  if (rrmax == 0) {
    fputs(" 0", trace);
    return;
  }
  fprintf(trace, " %s%d", endline, rrmax);
  sep = "";
  for (rrnum = 0; rrnum < rrmax; rrnum++) {
    if (ns_parserr(msg, sect, rrnum, &rr)) {
      fputs(strerror(errno), trace);
      return;
    }
    fprintf(trace, " %s", sep);
    dump_dns_rr(msg, &rr, sect, trace);
    sep = endline;
  }
}

void dump_dns(const u_char *payload, size_t paylen, FILE *trace,
              const char *endline) {
  u_int opcode, rcode, id;
  const char *sep;
  ns_msg msg;

  fprintf(trace, " %sdns ", endline);
  if (ns_initparse(payload, paylen, &msg) < 0) {
    fputs(strerror(errno), trace);
    return;
  }
  opcode = ns_msg_getflag(msg, ns_f_opcode);
  rcode = ns_msg_getflag(msg, ns_f_rcode);
  id = ns_msg_id(msg);
  fprintf(trace, "%s,%d,%u", _res_opcodes[opcode], rcode, id);
  sep = ",";
#define FLAG(t, f)                  \
  if (ns_msg_getflag(msg, f)) {     \
    fprintf(trace, "%s%s", sep, t); \
    sep = "|";                      \
  }
  FLAG("qr", ns_f_qr);
  FLAG("aa", ns_f_aa);
  FLAG("tc", ns_f_tc);
  FLAG("rd", ns_f_rd);
  FLAG("ra", ns_f_ra);
  FLAG("z", ns_f_z);
  FLAG("ad", ns_f_ad);
  FLAG("cd", ns_f_cd);
#undef FLAG

  dump_dns_sect(&msg, ns_s_an, trace, endline);
}

int main() {
  ShowCrashReports();
  u_char answer[1024] = "";
  res_init();
  int rv = res_query("google.com", ns_c_in, ns_t_txt, answer, sizeof(answer));
  // printf("rv=%d\n", rv);
  dump_dns(answer, rv, stdout, "\n");
  printf("\n");
}
