#include "dns.h"
#include "log.h"
#include "printf.h"
#include "str.h"
#include "timer.h"
#include "url.h"
#include "util.h"

struct dns_data {
  struct dns_data *next;
  struct mg_connection *c;
  uint64_t expire;
  uint16_t txnid;
};

static void sendnsreq(struct mg_connection *, struct mg_str *, int,
                      struct mg_dns *, bool);

struct mdns_data {
  struct mdns_data *next;
  struct mg_connection *c;
  uint64_t expire;
  struct mg_str name;
};

static void sendmdnsreq(struct mg_connection *, struct mg_str *, int,
                        struct mg_connection *, bool);

static void dns_free(struct dns_data **head, struct dns_data *d) {
  LIST_DELETE(struct dns_data, head, d);
  mg_free(d);
}

static void mdns_free(struct mdns_data **head, struct mdns_data *d) {
  LIST_DELETE(struct mdns_data, head, d);
  mg_free((void *) d->name.buf);
  mg_free(d);
}

void mg_resolve_cancel(struct mg_connection *c) {
  struct dns_data *tmp, *d;
  struct mdns_data *mtmp, *md;
  struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests;
  struct mdns_data **mhead =
      (struct mdns_data **) &c->mgr->active_mdns_requests;
  for (d = *head; d != NULL; d = tmp) {
    tmp = d->next;
    if (d->c == c) dns_free(head, d);
  }
  for (md = *mhead; md != NULL; md = mtmp) {
    mtmp = md->next;
    if (md->c == c) mdns_free(mhead, md);
  }
}

static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs,
                                      char *to, size_t tolen, size_t j,
                                      int depth) {
  size_t i = 0;
  if (tolen > 0 && depth == 0) to[0] = '\0';
  if (depth > 5) return 0;
  // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1]));
  while (ofs + i + 1 < len) {
    size_t n = s[ofs + i];
    if (n == 0) {
      i++;
      break;
    }
    if (n & 0xc0) {
      size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]);  // 12 is hdr len
      // MG_INFO(("PTR %lx", (unsigned long) ptr));
      if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
          mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1) == 0)
        return 0;
      i += 2;
      break;
    }
    if (ofs + i + n + 1 >= len) return 0;
    if (j > 0) {
      if (j < tolen) to[j] = '.';
      j++;
    }
    if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
    j += n;
    i += n + 1;
    if (j < tolen) to[j] = '\0';  // Zero-terminate this chunk
    // MG_INFO(("--> [%s]", to));
  }
  if (tolen > 0) to[tolen - 1] = '\0';  // Make sure it is nul-term
  return i;
}

static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs,
                                char *dst, size_t dstlen) {
  return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0);
}

size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
                       bool is_question, struct mg_dns_rr *rr) {
  const uint8_t *s = buf + ofs, *e = &buf[len];

  memset(rr, 0, sizeof(*rr));
  if (len < sizeof(struct mg_dns_header)) return 0;  // Too small
  if (len > 512) return 0;  //  Too large, we don't expect that
  if (s >= e) return 0;     //  Overflow

  if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0)
    return 0;
  s += rr->nlen + 4;
  if (s > e) return 0;
  rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
  rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
  if (is_question) return (size_t) (rr->nlen + 4);

  s += 6;
  if (s > e) return 0;
  rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
  if (s + rr->alen > e) return 0;
  return (size_t) (rr->nlen + rr->alen + 10);
}

bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) {
  const struct mg_dns_header *h = (struct mg_dns_header *) buf;
  struct mg_dns_rr rr;
  size_t i, n, num_answers, ofs = sizeof(*h);
  bool is_response;
  memset(dm, 0, sizeof(*dm));

  if (len < sizeof(*h)) return 0;                // Too small, headers dont fit
  if (mg_ntohs(h->num_questions) > 1) return 0;  // Sanity
  num_answers = mg_ntohs(h->num_answers);
  if (num_answers > 10) {
    MG_DEBUG(("Got %u answers, ignoring beyond 10th one", num_answers));
    num_answers = 10;  // Sanity cap
  }
  dm->txnid = mg_ntohs(h->txnid);
  is_response = mg_ntohs(h->flags) & 0x8000;

  for (i = 0; i < mg_ntohs(h->num_questions); i++) {
    if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false;
    // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass));
    mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
    ofs += n;
  }

  if (!is_response) {
    // For queries, there is no need to parse the answers. In this way,
    // we also ensure the domain name (dm->name) is parsed from
    // the question field.
    return true;
  }

  for (i = 0; i < num_answers; i++) {
    if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false;
    // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass,
    // dm->name));
    mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
    ofs += n;

    if (rr.alen == 4 && rr.atype == MG_DNS_RTYPE_A && rr.aclass == 1) {
      dm->addr.is_ip6 = false;
      memcpy(&dm->addr.addr.ip, &buf[ofs - 4], 4);
      dm->resolved = true;
      break;  // Return success
    } else if (rr.alen == 16 && rr.atype == MG_DNS_RTYPE_AAAA &&
               rr.aclass == 1) {
      dm->addr.is_ip6 = true;
      memcpy(&dm->addr.addr.ip, &buf[ofs - 16], 16);
      dm->resolved = true;
      break;  // Return success
    }
  }
  return true;
}

static void dns_cb(struct mg_connection *c, int ev, void *ev_data) {
  struct dns_data *d, *tmp;
  struct dns_data **head = (struct dns_data **) &c->mgr->active_dns_requests;
  if (ev == MG_EV_POLL) {
    uint64_t now = *(uint64_t *) ev_data;
    for (d = *head; d != NULL; d = tmp) {
      tmp = d->next;
      // MG_DEBUG(("%lu %lu dns poll", d->expire, now));
      if (now > d->expire) mg_error(d->c, "DNS timeout");  // will remove entry
    }
  } else if (ev == MG_EV_READ) {
    struct mg_dns_message dm;
    int resolved = 0;
    if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
      MG_ERROR(("Unexpected DNS response:"));
      mg_hexdump(c->recv.buf, c->recv.len);
    } else {
      // MG_VERBOSE(("%s %d", dm.name, dm.resolved));
      for (d = *head; d != NULL; d = tmp) {
        tmp = d->next;
        // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
        if (dm.txnid != d->txnid) continue;
        if (d->c->is_resolving) {
          if (dm.resolved) {
            dm.addr.port = d->c->rem.port;  // Save port
            d->c->rem = dm.addr;            // Copy resolved address
            MG_DEBUG(
                ("%lu %s is %M", d->c->id, dm.name, mg_print_ip, &d->c->rem));
            mg_connect_resolved(d->c);
#if MG_ENABLE_IPV6
          } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' &&
                     c->mgr->use_dns6 == false) {
            struct mg_str x = mg_str(dm.name);
            sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
#endif
          } else {
            mg_error(d->c, "%s DNS lookup failed", dm.name);
          }
        } else {
          MG_ERROR(("%lu already resolved", d->c->id));
        }
        dns_free(head, d);
        resolved = 1;
      }
    }
    if (!resolved) MG_ERROR(("stray DNS reply"));
    c->recv.len = 0;
  } else if (ev == MG_EV_CLOSE) {
    for (d = *head; d != NULL; d = tmp) {
      tmp = d->next;
      mg_error(d->c, "DNS error");  // will remove entry
    }
  }
}

static bool dns_send(struct mg_connection *c, const struct mg_str *name,
                     unsigned int rtype, uint16_t txnid, uint16_t flags) {
  struct { // RFC-1035 4.1.2
    struct mg_dns_header header;
    uint8_t data[256];
  } pkt;
  size_t i, n;
  memset(&pkt, 0, sizeof(pkt));
  pkt.header.txnid = mg_htons(txnid);
  pkt.header.flags = mg_htons(flags);
  pkt.header.num_questions = mg_htons(1);
  for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
    if (name->buf[i] == '.' || i >= name->len) {
      pkt.data[n] = (uint8_t) (i - n);
      memcpy(&pkt.data[n + 1], name->buf + n, i - n);
      n = i + 1;
    }
    if (i >= name->len) break;
  }
  memset(&pkt.data[n], 0, 5);         // nul, QTYPE, QCLASS
  pkt.data[n + 2] = (uint8_t) rtype;  // QTYPE = rtype, only 0-255 supported
  pkt.data[n + 4] = 1;                // QCLASS = IN
  n += 5;
  return mg_send(c, &pkt, sizeof(pkt.header) + n);
}

static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
                        uint16_t txnid, bool ipv6) {
  return dns_send(c, name, ipv6 ? MG_DNS_RTYPE_AAAA : MG_DNS_RTYPE_A, txnid,
                  0x100); // RD
}

bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc);
bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc) {
  if (dnsc->url == NULL) {
    mg_error(0, "DNS server URL is NULL. Call mg_mgr_init()");
    return false;
  }
  if (dnsc->c == NULL) {
    dnsc->c = mg_connect(mgr, dnsc->url, NULL, NULL);
    if (dnsc->c == NULL) return false;
    dnsc->c->pfn = dns_cb;
  }
  return true;
}

static void sendnsreq(struct mg_connection *c, struct mg_str *name, int ms,
                      struct mg_dns *dnsc, bool ipv6) {
  struct dns_data *d = NULL;
  if (!mg_dnsc_init(c->mgr, dnsc)) {
    mg_error(c, "resolver");
  } else if ((d = (struct dns_data *) mg_calloc(1, sizeof(*d))) == NULL) {
    mg_error(c, "resolve OOM");
  } else {
    struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests;
    uint16_t id;
    mg_random(&id, sizeof(uint16_t));
    if (reqs != NULL) // no seq, no collision for upto 256 in-flight requests
      id = (uint16_t) (reqs->txnid + (id & 0xFF) + 1);
    d->txnid = id;
    d->next = reqs;
    c->mgr->active_dns_requests = d;
    d->expire = mg_millis() + (uint64_t) ms;
    d->c = c;
    c->is_resolving = 1;
    MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len,
                name->buf, dnsc->url, d->txnid));
    if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) {
      mg_error(dnsc->c, "DNS send");
    }
  }
}

void mg_resolve(struct mg_connection *c, const char *url) {
  struct mg_str host = mg_url_host(url);
  c->rem.port = mg_htons(mg_url_port(url));
  if (mg_aton(host, &c->rem)) {
    // host is an IP address, do not fire name resolution
    mg_connect_resolved(c);
  } else if (host.len > 6 &&
             strncmp(".local", &host.buf[host.len - 6], 6) == 0) {
    // this is a request for a .local name (mDNS)
    sendmdnsreq(c, &host, 500, c->mgr->mdns, c->mgr->use_dns6);  // 500ms tmout
  } else {
    // host is not an IP nor a .local, send DNS resolution request
    struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4;
    sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
  }
}

// Response header length is 10 bytes
static const uint8_t mdns_answer[] = {
    0, 1,          // 2 bytes - record type, A
    0, 1,          // 2 bytes - address class, INET
    0, 0, 0, 120,  // 4 bytes - TTL
    0, 4           // 2 bytes - address length
};

// A name length is name->len + '.local' + 2 = name->len + 8
static uint8_t *build_name(struct mg_str *name, uint8_t *p) {
  *p++ = (uint8_t) name->len;  // label 1
  memcpy(p, name->buf, name->len), p += name->len;
  *p++ = 5;  // label 2
  memcpy(p, "local", 5), p += 5;
  *p++ = 0;  // no more labels
  return p;
}

void mg_getlocaddr(struct mg_connection *, struct mg_addr *, struct mg_addr *);

// An A record length is 10 + 4 = 14 bytes
static uint8_t *build_a_record(struct mg_connection *c, uint8_t *p,
                               struct mg_addr *addr) {
  memcpy(p, mdns_answer, sizeof(mdns_answer)), p += sizeof(mdns_answer);
  if (addr != NULL && !addr->is_ip6) {
    memcpy(p, &addr->addr.ip4, 4), p += 4;
  } else {
#if MG_ENABLE_TCPIP
    memcpy(p, &c->mgr->ifp->ip, 4), p += 4;
#else
    struct mg_addr loc, to;
    memset(&loc, 0, sizeof(loc));
    to.is_ip6 = false;
    to.port = mg_htons(5353);
    to.addr.ip4 = MG_IPV4(224, 0, 0, 51);
    mg_getlocaddr(c, &to, &loc);
    memcpy(p, &loc.addr.ip4, 4), p += 4;
#endif
  }
  return p;
}

// A srv name length is r->srvcproto.len + '.local' + 2 = r->srvcproto.len + 8
static uint8_t *build_srv_name(uint8_t *p, struct mg_dnssd_record *r) {
  *p++ = (uint8_t) r->srvcproto.len - 5;  // label 1, up to '._tcp'
  memcpy(p, r->srvcproto.buf, r->srvcproto.len), p += r->srvcproto.len;
  p[-5] = 4;  // label 2, '_tcp', overwrite '.'
  *p++ = 5;   // label 3
  memcpy(p, "local", 5), p += 5;
  *p++ = 0;  // no more labels
  return p;
}

#if 0
// TODO(): for listing
static uint8_t *build_mysrv_name(struct mg_str *name, uint8_t *p,
                                 struct mg_dnssd_record *r) {
  *p++ = name->len;  // label 1
  memcpy(p, name->buf, name->len), p += name->len;
  return build_srv_name(p, r);
}
#endif

// A PTR record length is 10 + name->len + 3 = name->len + 13
static uint8_t *build_ptr_record(struct mg_str *name, uint8_t *p, uint16_t o) {
  uint16_t offset = mg_htons(o);
  memcpy(p, mdns_answer, sizeof(mdns_answer));
  p[1] = MG_DNS_RTYPE_PTR;  // overwrite record type
  p += sizeof(mdns_answer);
  p[-1] = (uint8_t) name->len +
          3;  // overwrite response length, label length + label + offset
  *p++ = (uint8_t) name->len;                       // response: label 1
  memcpy(p, name->buf, name->len), p += name->len;  // copy label
  memcpy(p, &offset, 2);
  *p |= 0xC0, p += 2;
  return p;
}

// An SRV record length is 10 + name->len + 9 = name->len + 19
static uint8_t *build_srv_record(struct mg_str *name, uint8_t *p,
                                 struct mg_dnssd_record *r, uint16_t o) {
  uint16_t port = mg_htons(r->port);
  uint16_t offset = mg_htons(o);
  memcpy(p, mdns_answer, sizeof(mdns_answer));
  p[1] = MG_DNS_RTYPE_SRV;  // overwrite record type
  p += sizeof(mdns_answer);
  p[-1] = (uint8_t) name->len + 9;  // overwrite response length (4+2+1+2)
  *p++ = 0;                         // priority
  *p++ = 0;
  *p++ = 0;  // weight
  *p++ = 0;
  memcpy(p, &port, 2), p += 2;  // port
  *p++ = (uint8_t) name->len;   // label 1
  memcpy(p, name->buf, name->len), p += name->len;
  memcpy(p, &offset, 2);
  *p |= 0xC0, p += 2;
  return p;
}

// A TXT record length is r->txt.len (txt contents) + 10
static uint8_t *build_txt_record(uint8_t *p, struct mg_dnssd_record *r) {
  uint16_t len = mg_htons((uint16_t) r->txt.len);
  memcpy(p, mdns_answer, sizeof(mdns_answer));
  p[1] = MG_DNS_RTYPE_TXT;  // overwrite record type
  p += sizeof(mdns_answer);
  memcpy(p - 2, &len, 2);  // overwrite response length
  memcpy(p, r->txt.buf, r->txt.len), p += r->txt.len;  // copy record verbatim
  return p;
}

// Each additional record has a 2-byte field pointing to the name label

// RFC-6762 16: case-insensitivity --> RFC-1034, 1035

static void handle_mdns_query(struct mg_connection *c) {
  struct mg_dns_header *qh = (struct mg_dns_header *) c->recv.buf;
  struct mg_dns_rr rr;
  size_t n;
  // Parse first question, offset 12 is header size
  n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr);
  MG_VERBOSE(("mDNS request parsed, result=%d", (int) n));
  if (n > 0) {
    // RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0
    uint8_t buf[sizeof(struct mg_dns_header) + 256 + sizeof(mdns_answer) + 4];
    struct mg_dns_header *h = (struct mg_dns_header *) buf;
    uint8_t *p = &buf[sizeof(*h)];
    char name[256];
    uint8_t name_len;
    // uint16_t q = mg_ntohs(qh->num_questions);
    struct mg_str defname = mg_str((const char *) c->fn_data);
    struct mg_str *respname;
    struct mg_mdns_req req;
    memset(&req, 0, sizeof(req));
    req.is_unicast = (rr.aclass & MG_BIT(15)) != 0;  // QU
    rr.aclass &= (uint16_t) ~MG_BIT(15);  // remove "QU" (unicast response)
    qh->num_questions = mg_htons(1);      // parser sanity
    mg_dns_parse_name(c->recv.buf, c->recv.len, 12, name, sizeof(name));
    name_len = (uint8_t) strlen(name);  // verify it ends in .local
    if (name_len <= 6 || strcmp(".local", &name[name_len - 6]) != 0 ||
        (rr.aclass != 1 && rr.aclass != 0xff))
      return;
    name[name_len -= 6] = '\0';  // remove .local
    MG_VERBOSE(("RR %u %u %s", (unsigned int) rr.atype,
                (unsigned int) rr.aclass, name));
    if (rr.atype == MG_DNS_RTYPE_A) {
      // if we have a name to match, go; otherwise users will match and fill
      // req.r.name and set req.is_resp
      if (c->fn_data != NULL && mg_casecmp((char *) c->fn_data, name) != 0)
        return;
      req.is_resp = (c->fn_data != NULL);
      req.reqname = mg_str_n(name, name_len);
    } else  // users have to match the request to something in their db, then
            // fill req.r and set req.is_resp
      if (rr.atype == MG_DNS_RTYPE_PTR) {
        if (strcmp("_services._dns-sd._udp", name) == 0) req.is_listing = true;
        MG_DEBUG(
            ("PTR request for %s", req.is_listing ? "services listing" : name));
        req.reqname = mg_str_n(name, name_len);
      } else if (rr.atype == MG_DNS_RTYPE_SRV || rr.atype == MG_DNS_RTYPE_TXT) {
        MG_DEBUG(("%s request for %s",
                  rr.atype == MG_DNS_RTYPE_SRV ? "SRV" : "TXT", name));
        // if possible, check it starts with our name, users will check it ends
        // in a service name they handle
        if (c->fn_data != NULL) {
          if (mg_strcasecmp(defname, mg_str_n(name, defname.len)) != 0 ||
              name[defname.len] != '.')
            return;
          req.reqname =
              mg_str_n(name + defname.len + 1, name_len - defname.len - 1);
          MG_DEBUG(
              ("That's us, handing %.*s", req.reqname.len, req.reqname.buf));
        } else {
          req.reqname = mg_str_n(name, name_len);
        }
      } else {  // unhandled record
        return;
      }
    req.rr = &rr;
    mg_call(c, MG_EV_MDNS_REQ, &req);
    if (!req.is_resp) return;
    respname = req.respname.buf != NULL ? &req.respname : &defname;

    memset(h, 0, sizeof(*h));                   // clear header
    h->txnid = req.is_unicast ? qh->txnid : 0;  // RFC-6762 18.1
    h->num_answers = mg_htons(1);  // RFC-6762 6: 0 questions, 1 Answer
    h->flags = mg_htons(0x8400);   // Authoritative response
    if (req.is_listing) {
      // TODO(): RFC-6762 6: each responder SHOULD delay its response by a
      // random amount of time selected with uniform random distribution in the
      // range 20-120 ms.
      // TODO():
      return;
    } else if (rr.atype == MG_DNS_RTYPE_PTR) {  // serve PTR + SRV + TXT + A
      // TODO(): RFC-6762 6: each responder SHOULD delay its response by a
      // random amount of time selected with uniform random distribution in the
      // range 20-120 ms. Response to PTR is local_name._myservice._tcp.local
      uint8_t *o = p, *aux;
      uint16_t offset;
      if (respname->buf == NULL || respname->len == 0) return;
      if ((sizeof(*h) + req.r->srvcproto.len + 8 + respname->len + 13 + 2 +
           respname->len + 19 + 2 + req.r->txt.len + 10 + 2 + 14) >
          sizeof(buf))  // srv name + PTR + 2 + SRV + 2 + TXT + 2 + A
        return;
      h->num_other_prs = mg_htons(3);  // 3 additional records
      p = build_srv_name(p, req.r);
      aux = build_ptr_record(respname, p, (uint16_t) (o - buf));
      o = p + sizeof(mdns_answer);  // point to PTR response (full srvc name)
      offset = mg_htons((uint16_t) (o - buf));
      o = p - 7;  // point to '.local' label (\x05local\x00)
      p = aux;
      memcpy(p, &offset, 2);  // point to full srvc name, in record
      *p |= 0xC0, p += 2;
      aux = p;
      p = build_srv_record(respname, p, req.r, (uint16_t) (o - buf));
      o = aux + sizeof(mdns_answer) + 6;  // point to target in SRV
      memcpy(p, &offset, 2);              // point to full srvc name, in record
      *p |= 0xC0, p += 2;
      p = build_txt_record(p, req.r);
      offset = mg_htons((uint16_t) (o - buf));
      memcpy(p, &offset, 2);  // point to target name, in record
      *p |= 0xC0, p += 2;
      p = build_a_record(c, p, req.addr);
    } else if (rr.atype == MG_DNS_RTYPE_TXT) {
      if ((sizeof(*h) + req.r->srvcproto.len + 8 + req.r->txt.len + 10) >
          sizeof(buf))  // srv name + TXT
        return;
      p = build_srv_name(p, req.r);
      p = build_txt_record(p, req.r);
    } else if (rr.atype == MG_DNS_RTYPE_SRV) {  // serve SRV + A
      uint8_t *o, *aux;
      uint16_t offset;
      if (respname->buf == NULL || respname->len == 0) return;
      if ((sizeof(*h) + req.r->srvcproto.len + 8 + respname->len + 19 + 2 +
           14) > sizeof(buf))  // srv name + SRV + 2 + A
        return;
      h->num_other_prs = mg_htons(1);  // 1 additional record
      p = build_srv_name(p, req.r);
      o = p - 7;  // point to '.local' label (\x05local\x00)
      aux = p;
      p = build_srv_record(respname, p, req.r, (uint16_t) (o - buf));
      o = aux + sizeof(mdns_answer) + 6;  // point to target in SRV
      offset = mg_htons((uint16_t) (o - buf));
      memcpy(p, &offset, 2);  // point to target name, in record
      *p |= 0xC0, p += 2;
      p = build_a_record(c, p, req.addr);
    } else {  // A requested
      // RFC-6762 6: 0 Auth, 0 Additional RRs
      if (respname->buf == NULL || respname->len == 0) return;
      if ((sizeof(*h) + respname->len + 8 + 14) > sizeof(buf))  // name + A
        return;
      p = build_name(respname, p);
      p = build_a_record(c, p, req.addr);
    }
    if (!req.is_unicast) mg_multicast_restore(c, (uint8_t *) &c->loc);
    mg_send(c, buf, (size_t) (p - buf));  // And send it!
    MG_DEBUG(("%M > %M", mg_print_ip_port, &c->loc, mg_print_ip_port, &c->rem));
    MG_DEBUG(("mDNS %s response sent", req.is_unicast ? "unicast" : "mcast"));
  }
}


static size_t srvtoname(char *svc) {
  char *dot = strchr(svc, '.');
  if (dot == NULL) return 0;
  *dot = '\0';
  return (size_t) (dot - svc);
}

#define MG_MAX_MDNS_RECORDS 8
// Condense one useful mDNS/DNS-SD chain from a response datagram into a single
// MG_EV_MDNS_RESP event. A/AAAA as the first supported record are terminal
// address answers (AAAA currently ignored). For PTR/SRV/TXT, subsequent
// records are attached only when their label matches the service instance, or
// the SRV target for address records.
// Some devices send SRV/TXT as extra Answer RRs instead of Additional RRs, so
// both sections are parsed as one chain.
static void handle_mdns_response(struct mg_connection *c) {
  struct mg_dns_header *rh = (struct mg_dns_header *) c->recv.buf;
  struct mg_dns_rr rr, rr_[5]; // A, PTR, SRV, TXT, AAAA, in that order
  struct mg_mdns_resp resp;
  size_t n, i, num, answers, roff_[5], roff = 12;  // offset 12 is header size
  // RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0
  char name[256], srvcproto[256], instance[256], host[256];
  uint16_t atype = 0;
  answers = mg_ntohs(rh->num_answers);
  num = answers + mg_ntohs(rh->num_other_prs);
  if (answers == 0) return;
  MG_VERBOSE(("mDNS response: %u answer, %u additional", (unsigned) answers,
              (unsigned) (num - answers)));
  if (num > MG_MAX_MDNS_RECORDS) {
    num = MG_MAX_MDNS_RECORDS;
    MG_DEBUG(("ignoring > %u records", MG_MAX_MDNS_RECORDS));
  }
  memset(roff_, 0, sizeof(roff_));
  memset(&rr_, 0, sizeof(rr_));
  memset(&resp, 0, sizeof(resp));
  instance[0] = host[0] = '\0';
  for (i = 0; i < num; i++) { // First Answer RR is primary; the rest must match it
    n = mg_dns_parse_rr(c->recv.buf, c->recv.len, roff, false, &rr);
    MG_VERBOSE(("mDNS record parsed, result=%u", n));
    if (n == 0) return;
    if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff, name, sizeof(name)) == 0) return;
    MG_VERBOSE(("RR %u %u %s", (unsigned int) rr.atype, (unsigned int) rr.aclass, name));
    if (rr.alen == 4 && rr.atype == MG_DNS_RTYPE_A && (rr.aclass & 0x7FFF) == 1 &&
        (i == 0 || (host[0] != '\0' && strcmp(name, host) == 0) ||
         (host[0] == '\0' && instance[0] != '\0' && strcmp(name, instance) == 0))) {
      if (rr_[0].atype != 0) break;
      if (i == 0) atype = rr.atype;
      resp.name = mg_str(name);
      resp.addr.is_ip6 = false;
      memcpy(resp.addr.addr.ip, c->recv.buf + roff + n - 4, 4);
      MG_VERBOSE(("A record"));
      roff_[0] = roff, rr_[0] = rr;
      if (atype == MG_DNS_RTYPE_A) break;
    } else if (rr.alen == 16 && rr.atype == MG_DNS_RTYPE_AAAA && (rr.aclass & 0x7FFF) == 1 &&
               (i == 0 || (host[0] != '\0' && strcmp(name, host) == 0) ||
                (host[0] == '\0' && instance[0] != '\0' && strcmp(name, instance) == 0))) {
# if 0
      if (rr_[4].atype != 0) break;
      resp.addr.is_ip6 = true;
      if (i == 0) atype = rr.atype;
      resp.name = mg_str(name);
      memcpy(resp.addr.addr.ip, c->recv.buf + roff + n - 16, 16);
      MG_VERBOSE(("AAAA record"));
      roff_[4] = roff, rr_[4] = rr;
      if (atype == MG_DNS_RTYPE_AAAA) break;
#else
      MG_VERBOSE(("ignored AAAA record"));
#endif
    } else if (rr.atype == MG_DNS_RTYPE_PTR && (rr.aclass & 0x7FFF) == 1 && i == 0) {
      if (rr_[1].atype != 0) break;
      atype = rr.atype;
      if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff + rr.nlen + 10,
                            instance, sizeof(instance)) == 0) return;
      MG_VERBOSE(("PTR record"));
      roff_[1] = roff, rr_[1] = rr;
    } else if (rr.atype == MG_DNS_RTYPE_SRV && rr.alen >= 6 && (rr.aclass & 0x7FFF) == 1 &&
               (i == 0 || (instance[0] != '\0' && strcmp(name, instance) == 0))) {
      if (rr_[2].atype != 0) break;
      if (i == 0) atype = rr.atype;
      resp.sd.port = MG_LOAD_BE16(c->recv.buf + roff + rr.nlen + 14);
      resp.addr.port = mg_htons(resp.sd.port);
      if (instance[0] == '\0') mg_snprintf(instance, sizeof(instance), "%s", name);
      if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff + rr.nlen + 16,
                            host, sizeof(host)) == 0) return;
      MG_VERBOSE(("SRV record"));
      roff_[2] = roff, rr_[2] = rr;
    } else if (rr.atype == MG_DNS_RTYPE_TXT && (rr.aclass & 0x7FFF) == 1 &&
               (i == 0 || (instance[0] != '\0' && strcmp(name, instance) == 0))) {
      if (rr_[3].atype != 0) break;
      if (i == 0) atype = rr.atype;
      if (instance[0] == '\0') mg_snprintf(instance, sizeof(instance), "%s", name);
      resp.sd.txt = mg_str_n((char *) c->recv.buf + roff + rr.nlen + 10, rr.alen);
      MG_VERBOSE(("TXT record"));
      roff_[3] = roff, rr_[3] = rr;
    }
    roff += n;
    if (i == 0 && atype == 0) return;
  }
  if (atype == MG_DNS_RTYPE_A) {
    resp.rr = &rr_[0];
    MG_DEBUG(("A response from %s = %M", name, mg_print_ip, &resp.addr));
#if 0
  } else if (atype == MG_DNS_RTYPE_AAAA) {
    resp.rr = &rr_[4];
    MG_DEBUG(("AAAA response from %s = %M", name, mg_print_ip6, resp.addr.addr.ip));
#endif
  } else if (atype == MG_DNS_RTYPE_PTR) {
    size_t len;
    resp.rr = &rr_[1];
    if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff_[1], srvcproto,
                          sizeof(srvcproto)) == 0) return;
    len = strlen(srvcproto);
    if (len <= 6) return;
    resp.sd.srvcproto = mg_str_n(srvcproto, len - 6);  // remove .local
    if (host[0] != '\0') {
      resp.name = mg_str(host);
    } else if (rr_[0].atype == 0) {
      mg_snprintf(name, sizeof(name), "%s", instance);
      resp.name = mg_str_n(name, srvtoname(name));
    } else {
      if (mg_dns_parse_name(c->recv.buf, c->recv.len, roff_[0], name,
                            sizeof(name)) == 0) return;
      resp.name = mg_str(name);
    }
    MG_DEBUG(("PTR response for %s from %s", srvcproto, name));
  } else if (atype == MG_DNS_RTYPE_SRV || atype == MG_DNS_RTYPE_TXT) {
    size_t len, name_len;
    resp.rr = atype == MG_DNS_RTYPE_SRV ? &rr_[2] : &rr_[3];
    if (mg_dns_parse_name(c->recv.buf, c->recv.len,
                          atype == MG_DNS_RTYPE_SRV ? roff_[2] : roff_[3],
                          srvcproto, sizeof(srvcproto)) == 0)
      return;
    len = strlen(srvcproto);
    name_len = srvtoname(srvcproto);
    if (name_len == 0 || len <= name_len + 7) return;  // remove .local
    resp.sd.srvcproto = mg_str_n(srvcproto + name_len + 1, len - name_len - 7);
    resp.name = mg_str_n(srvcproto, name_len);
    MG_DEBUG(("%s response for %s from %s", atype == MG_DNS_RTYPE_SRV ? "SRV" : "TXT", srvcproto, name));
  } else {
    return;
  }
  mg_call(c, MG_EV_MDNS_RESP, &resp);
}

static void handle_mdns_record(struct mg_connection *c) {
  struct mg_dns_header *h = (struct mg_dns_header *) c->recv.buf;
  if (c->recv.len <= 12) return;
  if ((h->flags & mg_htons(0xF800)) == 0) {
    // flags -> !resp, opcode=0 => query; ignore other opcodes
    handle_mdns_query(c);
  } else if ((h->flags & mg_htons(0xF800)) == mg_htons(0x8000)) {
    // flags -> resp, opcode=0 => response; ignore other opcodes
    handle_mdns_response(c);
  }
}

static void mdns_cb(struct mg_connection *c, int ev, void *ev_data) {
  struct mdns_data *d, *tmp;
  struct mdns_data **head = (struct mdns_data **) &c->mgr->active_mdns_requests;
  // mDNS resolver
  if (ev == MG_EV_POLL) {
    uint64_t now = *(uint64_t *) ev_data;
    for (d = *head; d != NULL; d = tmp) {
      tmp = d->next;
      // MG_DEBUG(("%lu %lu mdns poll", d->expire, now));
      if (now > d->expire) mg_error(d->c, "mDNS timeout");  // will remove entry
    }
  } else if (ev == MG_EV_CLOSE) {
    for (d = *head; d != NULL; d = tmp) {
      tmp = d->next;
      mg_error(d->c, "mDNS listener error");  // this will remove entry
    }
  } else if (ev == MG_EV_MDNS_RESP) {
    struct mg_mdns_resp *resp = (struct mg_mdns_resp *) ev_data;
    if (resp->rr->atype == MG_DNS_RTYPE_A) {
      for (d = *head; d != NULL; d = tmp) {
        tmp = d->next;
        if (mg_strcasecmp(d->name, resp->name) != 0) continue;
        if (d->c->is_resolving) {
          resp->addr.port = d->c->rem.port;  // Save port
          d->c->rem = resp->addr;            // Copy resolved address
          MG_DEBUG(("%lu %.*s is %M", d->c->id, resp->name.len, resp->name.buf,
                    mg_print_ip, &d->c->rem));
          mg_connect_resolved(d->c);
        } else {
          // this should not happen, unless above does not clear c->is_resolving
          MG_ERROR(("%lu already resolved", d->c->id));
        }
        mdns_free(head, d);
      }
    }
  } else if (ev == MG_EV_READ) {
    // generic mDNS[-SD] handling
    handle_mdns_record(c);  // this will call us back with MG_EV_MDNS_RESP
    mg_iobuf_del(&c->recv, 0, c->recv.len);
  }

  (void) ev_data;
}

void mg_multicast_add(struct mg_connection *c, char *ip);
struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, mg_event_handler_t fn,
                                     void *fn_data) {
  struct mg_connection *c =
      mg_listen(mgr, "udp://224.0.0.251:5353", fn, fn_data);
  if (c == NULL) return NULL;
  c->mgr->mdns = c;  // Add mDNS entry to enable resolver to use it
  c->pfn = mdns_cb, c->pfn_data = fn_data;
  mg_multicast_add(c, (char *) "224.0.0.251");
  return c;
}

static bool mdns_query(struct mg_connection *c, struct mg_str *name,
                       unsigned int rtype) {
  mg_multicast_restore(c, (uint8_t *) &c->loc);
  return dns_send(c, name, rtype, 0, 0);  // RFC-6762 18.1 id = 0, 18.6 RD = 0
}

bool mg_mdns_query(struct mg_connection *c, const char *name,
                   unsigned int rtype) {
  struct mg_str name_;
  name_.buf = (char *) name, name_.len = strlen(name);
  return mdns_query(c, &name_, rtype);
}

static void sendmdnsreq(struct mg_connection *c, struct mg_str *name, int ms,
                        struct mg_connection *mdnsc, bool ipv6) {
  struct mdns_data *d = NULL;
  if (mdnsc == NULL) {
    mg_error(c, "no mDNS listener, see mg_mdns_listen()");
  } else if ((d = (struct mdns_data *) mg_calloc(1, sizeof(*d))) == NULL) {
    mg_error(c, "resolve OOM");
  } else {
    struct mdns_data *reqs = (struct mdns_data *) c->mgr->active_mdns_requests;
    d->next = reqs;
    c->mgr->active_mdns_requests = d;
    d->expire = mg_millis() + (uint64_t) ms;
    d->name = mg_strdup(*name);
    d->c = c;
    c->is_resolving = 1;
    MG_VERBOSE(
        ("%lu resolving %.*s via mDNS", c->id, (int) name->len, name->buf));
    if (!mdns_query(mdnsc, name, MG_DNS_RTYPE_A)) {
      mg_error(c, "mDNS send");  // will remove newly created entry
    }
  }
  (void) ipv6;
}
