//---------------------------------------------------------------------------
#include <vcl.h>
#include <tchar.h>
#pragma hdrstop

/*

  mdnssd-min

  mdnssd-min is a minimal DNS-SD and mDNS client that takes a service type
  as its argument and returns the IPv4 and/or IPv6 addreses and port numbers
  running a service of the type.

  License: GPLv3
  Author: juul@sudomesh.org
  Copyright 2013-2014 Marc Juul Christoffersen.

  modifications by: philippe44@outlook.com

  References:

  DNS RFC: http://tools.ietf.org/html/rfc1035
    Section 4.1, 3.2.2 and 3.2.4

  DNS RFC: http://tools.ietf.org/html/rfc1034
    Section 3.7.1

  DNS Security Extensions RFC: http://tools.ietf.org/html/rfc2535
    Section 6.1

  mDNS RFC: http://tools.ietf.org/html/rfc6762
    Section 18.

  DNS-SD RFC: http://tools.ietf.org/html/rfc6763

  DNS SRV RFC: http://tools.ietf.org/html/rfc2782
        
*/
#include "Train_Control.h"
#include "Comm_Thread.h"
//#include "Setup_Control.h"
#include "Debug_Controls.h"

#if __BORLANDC__ >= 0x720
// Defined for CB10.1 C++ programs that use the VCL.
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>			// MCD
#include <io.h>
//typedef unsigned int socklen_t;
#include <stddef.h>	// MCD
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <winsock2.h>	// MCD
#include <ws2ipdef.h>	// MCD
#include <process.h>
#else
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned int socklen_t;
#include <stdio.h>
#endif

typedef uint32_t in_addr_t;


#include "mDNSThread.h"
                     
#include <assert.h>

// Print line
static String Line;
// Handle for sending messages to the caller from functions that are not class member functions

static HWND MyParent;


extern TCommThread * ptrTIUCommThread;				// pointer to TIU communications thread


#define DEBUGX 8    // debug level for massive debug output (requires ptrTIUCommThread != NULL)


void winsock_init(void) {
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2, 2);
	int WSerr = WSAStartup(wVersionRequested, &wsaData);
	if (WSerr != 0) {
		ptrTIUCommThread->PrintItem("mDNSThread : Failed to initialize WinSock", clBlack);
		return;
	}
}
/*---------------------------------------------------------------------------*/

void winsock_close(void) {
	WSACleanup();
}
/*---------------------------------------------------------------------------*/

#define MAX_INTERFACES 256

#define DEFAULT_INTERFACE 1

static in_addr_t get_localhost(char **name)
{
	char buf[256];
	struct hostent *h = NULL;
	struct sockaddr_in LocalAddr;

	memset(&LocalAddr, 0, sizeof(LocalAddr));

	gethostname(buf, 256);
	h = gethostbyname(buf);

	if (name) *name = strdup(buf);

	if (h != NULL) {
		memcpy(&LocalAddr.sin_addr, h->h_addr_list[0], 4);
		return LocalAddr.sin_addr.s_addr;
	}
	else return INADDR_ANY;
}
//---------------------------------------------------------------------------

#pragma package(smart_init)
//---------------------------------------------------------------------------
//   Important: Methods and properties of objects in VCL can only be
//   used in a method called using Synchronize, for example:
//
//      Synchronize(UpdateCaption);
//
//   where UpdateCaption could look like:
//
//      void __fastcall TMultiDNS::UpdateCaption()
//      {
//        Form1->Caption = "Updated in a thread";
//      }
//---------------------------------------------------------------------------
__fastcall TMultiDNS::TMultiDNS(bool CreateSuspended)
	: TThread(CreateSuspended)
{
assert(false);		// never called since the overloaded constructor below is used
}
//---------------------------------------------------------------------------
__fastcall TMultiDNS::TMultiDNS(bool CreateSuspended, TForm * Caller, HWND Parent, AnsiString Host, TStrings * Items)
	: TThread(CreateSuspended)
{
MyCaller = Caller;
MyParent = Parent;
MyItems = Items;

host.s_addr = inet_addr(Host.c_str());	// the interface to use
//
// termination
OnTerminate = &RunOnTerminate;
// FreeOnTerminate is not set so caller must delete thread
FreeOnTerminate = false;
// ReturnValue is accessable using WaitFor()
ReturnValue = -1;
// Next call is ok since the constructor runs in the context of the calling VCL code
if (DEBUGGINGX(1) && DB) DB->Debug_Print("mDNSThread Started", clGreen);
}
//---------------------------------------------------------------------------

void __fastcall TMultiDNS::Execute()
{
// Calls to Sleep() are ok in this thread
// NO VCL Calls in this thread
//
// this does not seem to work in BCB4.0 and Thread view not available in CB10.1
// it does work on BCB6.0
// http://docwiki.embarcadero.com/RADStudio/Rio/en/Converting_an_Unnamed_Thread_to_a_Named_Thread
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = "mDNS_Thread";
info.dwThreadID = -1;
info.dwFlags = 0;
__try {
    RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD),
        (DWORD*)&info);
    }
__except (EXCEPTION_CONTINUE_EXECUTION) {
    }

  if (Terminated) return;						// in case we are terminated

  struct mDNShandle_s *handle = NULL;
  uint32_t timeout = 1000;          // Run time of query in milliseconds
  int count = 1;                    // Run one scan
  
  winsock_init();
  //if (host.s_addr == INADDR_ANY) host.s_addr = get_localhost(NULL);

  if (DEBUGGING) {
      Line = "mDNS Thread is using interface " + String(inet_ntoa(host));
      if (ptrTIUCommThread) ptrTIUCommThread->PrintItem(Line, clBlack);
      }
  // do the work here
  handle = init_mDNS(host);
  if (!handle) {
  	Line =  "cannot open socket";
	if (ptrTIUCommThread) ptrTIUCommThread->PrintItem(Line, clBlack);
    }

  while (count--) {
	query_mDNS(handle, query_arg, 0, timeout, (void*) handle);
	control_mDNS(handle, MDNS_RESET);
    }
  close_mDNS(handle);
  //
  winsock_close();
}

//---------------------------------------------------------------------------

void __fastcall TMultiDNS::RunOnTerminate(TObject * Sender)     // called Automatically using Synchronize()
{
if (DEBUGGINGX(1) && DB) DB->Debug_Print("mDNSThread Terminated", clGreen);
//
}
//---------------------------------------------------------------------------
void __fastcall TMultiDNS::ListIPItem()     // called using Synchronize()
{
//
// display a line in the List Box of the calling class
// do not allow duplicate lines
//
if (MyItems != NULL) {
    if (MyItems->IndexOf(Line) < 0) {
        MyItems->Add(Line);
        }
    }
}
//---------------------------------------------------------------------------

bool __fastcall TMultiDNS::PrintServices(mDNSservice_t *slist, void *cookie, bool *stop)
{
//
#define VERBOSE true
	mDNSservice_t *s;
    char discovered[512];
    #ifdef VERBOSE
    char attributes[512];
    #endif

    if (slist == NULL) {
    	Line = "--->";
		if (ptrTIUCommThread) ptrTIUCommThread->PrintItem(Line, clBlack);
    	Line = String((char *) cookie);
        if (ptrTIUCommThread) ptrTIUCommThread->PrintItem(Line, clBlack);
        return false;
        }

    #if VERBOSE
	for (s = slist; s; s = s->next) {
		char *host = strdup(inet_ntoa(s->host));
        if (DEBUGGING) {
            assert(host);	// check that malloc worked
            snprintf(discovered, sizeof discovered, "[%s] %s\t%05hu\t%-25s %s %us %s", host, inet_ntoa(s->addr), s->port,
                   s->hostname, s->name, s->since, s->expired ? "EXPIRED" : "ACTIVE");
            Line = String(discovered);
            if (ptrTIUCommThread) ptrTIUCommThread->PrintItem(Line, clBlack);
            // Display format
            //[192.168.1.114] 192.168.1.114	08890	Modem.local            Modem._RTCModem._udp.local 0s ACTIVE
            }

        snprintf(discovered, sizeof discovered, "%s   %5hu   %s", host, s->port,
			   s->hostname);
        Line = String(discovered);
        Synchronize(ListIPItem);
        // Display format
        //192.168.1.114	08890 RTCRadio.local

		free(host);

		if (DEBUGGING) {
            // display attributes
            int j;
            for (j = 0; j < s->attr_count; j++) {
              snprintf(attributes, sizeof attributes, "\t%s =  %s", s->attr[j].name, s->attr[j].value);
              Line = String(attributes);
              if (ptrTIUCommThread) ptrTIUCommThread->PrintItem(Line, clBlack);
              }
        	}
        }
    #else
	for (s = slist; s; s = s->next) {
		char *host = strdup(inet_ntoa(s->host));
        assert(host);	// check that malloc worked
        snprintf(discovered, sizeof discovered, "%s   %5hu   %s", host, s->port,
			   s->hostname);
        Line = String(discovered);
        Synchronize(ListIPItem);
        // Display format
        //192.168.1.114	08890 RTCRadio.local
		free(host);
        }
    #endif

	/* options to control loop
	control_mDNS((struct mDNShandle_s*) cookie, MDNS_RESET);
	control_mDNS((struct mDNShandle_s*) cookie, MDNS_SUSPEND);
	*stop = true;
	*/
	// we have not released the slist
	return false;
}
//---------------------------------------------------------------------------

static int debug(const char* format, ...) {
if (DEBUGGINGX(DEBUGX)) {
    va_list args;
    int ret;
    char debugline[512];
    va_start(args, format);
    ret = vsprintf(debugline, format, args);
    va_end(args);
    Line = String(debugline);
    if (ptrTIUCommThread) ptrTIUCommThread->PrintItem(Line, clRed);
    return ret;
	}
return 0;
}
/*---------------------------------------------------------------------------*/

static item_t *insert_item(item_t *item, item_t **list) {
  if (*list) item->next = *list;
  else item->next = NULL;

  *list = item;

  return item;
}
/*---------------------------------------------------------------------------*/

static item_t *insert_tail_item(item_t *item, item_t **list) {
  if (*list) {
	struct item_s *p = *list;
	while (p->next) p = p->next;
	item->next = p->next;
	p->next = item;
  } else {
	item->next = NULL;
	*list = item;
  }

  return item;
}
/*---------------------------------------------------------------------------*/

static item_t *insert_ordered_item(item_t *item, item_t **list, int (*compare)(void *a, void *b)) {
  if (*list) {
	struct item_s *p = *list;
	while (p->next && compare(p->next, item) <= 0) p = p->next;
	item->next = p->next;
	p->next = item;
  } else {
	item->next = NULL;
	*list = item;
  }

  return item;
}

/*---------------------------------------------------------------------------*/

static item_t *remove_item(item_t *item, item_t **list) {
  if (item != *list) {
	struct item_s *p = *list;
	while (p && p->next != item) p = p->next;
	if (p) p->next = item->next;
	item->next = NULL;
  } else *list = (*list)->next;

  return item;
}
/*---------------------------------------------------------------------------*/

static void clear_list(item_t *list, void (*free_func)(void *)) {
  if (!list) return;
  while (list) {
	struct item_s *next = list->next;
	if (free_func) (*free_func)(list);
	else free(list);
	list = next;
  }
}
/*---------------------------------------------------------------------------*/

static void free_a(alist_t* a) {
	if (a->name) free(a->name);
	free(a);
}
/*---------------------------------------------------------------------------*/

static void free_s(slist_t* s) {
	if (s->name) free(s->name);
	if (s->hostname) free(s->hostname);
	if (s->txt) free(s->txt);
	free(s);
}
/*---------------------------------------------------------------------------*/

static char* prepare_query_string(char* name) {
  int i;
  int count;
  int lastdot = 0;
  int len = strlen(name);
  char* result;

  result = (char *)malloc(len + 2);
  if(!result) {
	if (DEBUGGINGX(DEBUGX)) debug("failed to allocate memory for parsed hostname");
	return NULL;
  }

  count = 0;
  for(i=0; i < len+1; i++) {
    if((name[i] == '.') || (name[i] == '\0')) {
      result[lastdot] = (char) count;
      count = 0;
      lastdot = i+1;
      continue;
    }
    result[i+1] = name[i];
    count++;
  }
  result[len+1] = '\0';

  return result;
}
/*---------------------------------------------------------------------------*/
// expects host byte_order
// https://courses.cs.duke.edu//fall16/compsci356/DNS/DNS-primer.pdf

static mDNSFlags* mdns_parse_header_flags(uint16_t data) {
  mDNSFlags* flags = (mDNSFlags *) malloc(sizeof(mDNSFlags));

  if(!flags) {
	if (DEBUGGINGX(DEBUGX)) debug("could not allocate memory for parsing header flags");
	return NULL;
  }

  flags->rcode = data & 0xf;
  flags->cd = (data >> 4) & 1;
  flags->ad = (data >> 5) & 1;
  flags->zero = (data >> 6) & 1;
  flags->ra = (data >> 7) & 1;
  flags->rd = (data >> 8) & 1;
  flags->tc = (data >> 9) & 1;
  flags->aa = (data >> 10) & 1;
  flags->opcode = (data >> 11) & 0xf;	// MCD I believe the shift here should be 11 was 14  (error in original code)
  flags->qr = (data >> 15) & 1;

  return flags;
}
/*---------------------------------------------------------------------------*/
// outputs host byte order
// https://courses.cs.duke.edu//fall16/compsci356/DNS/DNS-primer.pdf

static uint16_t mdns_pack_header_flags(mDNSFlags flags) {
  uint16_t packed = 0;
  
  packed |= (flags.rcode & 0xfff0);
  packed |= (flags.cd & 0xfffe) << 4;
  packed |= (flags.ad & 0xfffe) << 5;
  packed |= (flags.zero & 0xfffe) << 6;
  packed |= (flags.ra & 0xfffe) << 7;
  packed |= (flags.rd & 0xfffe) << 8;
  packed |= (flags.tc & 0xfffe) << 9;
  packed |= (flags.aa & 0xfffe) << 10;
  packed |= (flags.opcode & 0xfff0) << 11;	// MCD I believe the shift here should be 11 was 14 (error in original code)
  packed |= (flags.qr & 0xfffe) << 15;

  return packed;
}
/*---------------------------------------------------------------------------*/

static char* mdns_pack_question(mDNSQuestion* q, size_t* size) {
  char* packed;
  size_t name_length;
  uint16_t qtype;
  uint16_t qclass;


  name_length = strlen(q->qname) + 1;
  if(name_length > DNS_MAX_HOSTNAME_LENGTH) {
	if (DEBUGGINGX(DEBUGX)) debug("domain name too long");
	return NULL;
  }

  if (DEBUGGINGX(DEBUGX)) debug("name length: %u", name_length);

  *size = name_length + 2 + 2;

  // 1 char for terminating \0, 2 for qtype and 2 for qclass
  packed = (char *) malloc(*size);
  if(!packed) {
	if (DEBUGGINGX(DEBUGX)) debug("could not allocate memory for DNS question");
	return NULL;
  }

  memcpy(packed, q->qname, name_length);

  // The top bit of the qclass field is repurposed by mDNS
  // to indicate that a unicast response is preferred
  // See RFC 6762 section 5.4
  if(q->prefer_unicast_response) {
    q->qclass |= 1 << 15;
  }

  qtype = htons(q->qtype);
  qclass = htons(q->qclass);

  memcpy(packed + name_length, &qtype, 2);
  memcpy(packed + name_length + 2, &qclass, 2);

  return packed;
}
/*---------------------------------------------------------------------------*/
// parse question section

static int mdns_parse_question(char* message, char* data, int size) {
  mDNSQuestion q;
  char* cur;
  int parsed = 0;

  cur = data;
  // check for invalid length
  q.qname = parse_rr_name(message, data, &parsed);
  free(q.qname);
  cur += parsed;
  if(parsed > size) {
	if (DEBUGGINGX(DEBUGX)) debug("qname is too long");
	return 0;
  }

  memcpy(&(q.qtype), cur, 2);
  q.qtype = ntohs(q.qtype);
  cur += 2;
  parsed += 2;
  if(parsed > size) {
	return 0;
  }

  memcpy(&(q.qclass), cur, 2);
  q.qclass = ntohs(q.qclass);
  cur += 2;
  parsed += 2;
  if(parsed > size) {
	return 0;
  }

  return parsed;
}
/*---------------------------------------------------------------------------*/

static void mdns_message_print(mDNSMessage* msg) {

  if (DEBUGGINGX(9)) {
    mDNSFlags* flags = mdns_parse_header_flags(msg->flags);
    assert(flags);	// make sure constructor worked
  	debug("ID: %u", msg->id);
    debug("Flags: ");
    debug("      QR: %u", flags->qr);
    debug("  OPCODE: %u", flags->opcode);
    debug("      AA: %u", flags->aa);
    debug("      TC: %u", flags->tc);
    debug("      RD: %u", flags->rd);
    debug("      RA: %u", flags->ra);
    debug("       Z: %u", flags->zero);
    debug("      AD: %u", flags->ad);
    debug("      CD: %u", flags->cd);
    debug("   RCODE: %u", flags->rcode);
    debug("QDCOUNT: %u", msg->qd_count);
    debug("ANCOUNT: %u", msg->an_count);
    debug("NSCOUNT: %u", msg->ns_count);
    debug("ARCOUNT: %u", msg->ar_count);
    debug("Resource records:");
    free(flags);
	}
}
/*---------------------------------------------------------------------------*/
// parse A resource record

static int mdns_parse_rr_a(char* data, struct in_addr *addr) {
  addr->s_addr = INADDR_ANY;
  // ignore local link responses
  if ((data[0] == '\xa9') && (data[1] == '\xfe')) return 4;

  memcpy(&(addr->s_addr), data, 4);

  if (DEBUGGINGX(DEBUGX)) debug("        A: %s", inet_ntoa(*addr));

  return 4;
}
/*---------------------------------------------------------------------------*/
// parse PTR resource record

static int mdns_parse_rr_ptr(char* message, char* data, char **name) {
  int parsed = 0;

  *name = parse_rr_name(message, data, &parsed);

  if (DEBUGGINGX(DEBUGX)) debug("        PTR: %s", *name);

  return parsed;
}
/*---------------------------------------------------------------------------*/
// parse SRV resource record

static int mdns_parse_rr_srv(char* message, char* data, char **hostname, unsigned short *port) {
  uint16_t priority;
  uint16_t weight;
  int parsed = 0;

  // currently we do nothing with the priority and weight (comment from original code)
  memcpy(&priority, data, 2);
  priority = ntohs(priority);
  data += 2;
  parsed += 2;

  memcpy(&weight, data, 2);
  weight = ntohs(weight);
  data += 2;
  parsed += 2;

  memcpy(port, data, 2);
  *port = ntohs(*port);
  data += 2;
  parsed += 2;

  *hostname = parse_rr_name(message, data, &parsed);

  if (DEBUGGINGX(DEBUGX)) debug("        SRV target: %s", *hostname);
  if (DEBUGGINGX(DEBUGX)) debug("        SRV port: %u", *port);

  return parsed;
}
/*---------------------------------------------------------------------------*/
// parse TXT resource record

static void mdns_parse_rr_txt(char* message, mDNSResourceRecord* rr, char**txt, int *length) {
  if ((*txt = (char *) malloc(rr->rdata_length)) != NULL) {
	memcpy(*txt, rr->rdata, rr->rdata_length);
	*length = rr->rdata_length;
    }
}
/*---------------------------------------------------------------------------*/
// get name compression offset

static uint16_t get_offset(char* data) {
  uint16_t offset;

  memcpy(&offset, data, 2);
  offset = ntohs(offset);

  if((offset >> 14) & 3) {
    // this means that the name is a reference to
    // a string instead of a string
    offset &= 0x3fff; // change two most significant bits to 0
    return offset;
    }
  return 0;
}
/*---------------------------------------------------------------------------*/
// parse a domain name
// of the type included in resource records

static char* parse_rr_name(char* message, char* name, int* parsed) {

  int dereference_count = 0;
  uint16_t offset;
  int label_len;
  char* out;
  int out_i = 0;
  int i = 0;
  int did_jump = 0;
  int pars = 0;

  out = (char *) malloc(MAX_RR_NAME_SIZE);
  if(!out) {
	if (DEBUGGINGX(DEBUGX)) debug("could not allocate memory for resource record name");
	return NULL;
  }

  while(1) {
    offset = get_offset(name);
    if(offset) {
      if(!did_jump) {
        pars += 2; // parsed two bytes before jump
      }
      did_jump = 1;
      name = message + offset;
      dereference_count++;
      if(dereference_count >= MAX_DEREFERENCE_COUNT) {
        // don't allow messages to crash this app
        free(out);
        return NULL;
      }
      continue;
    }
    // insert a dot between labels
    if(out_i > 0) {
      out[out_i++] = '.';

      if(out_i+1 >= MAX_RR_NAME_SIZE) {
        free(out);
        return NULL;
      }
    }
    // it wasn't an offset, so it must be a string length
    label_len = (int) name[0];
    name++;
    if(!did_jump) {
      pars++;
    }
    for(i=0; i < label_len; i++) {
      out[out_i++] = name[i];
      if(out_i+1 >= MAX_RR_NAME_SIZE) {
        free(out);
        return NULL;
      }
      if(!did_jump) {
        pars++;
      }
    }
    name += label_len;
    if(name[0] == '\0') {
      out[out_i] = '\0';
      if(!did_jump && label_len) {
        pars++;
      }
      *parsed += pars;
      return out;
    }
  }
}
/*---------------------------------------------------------------------------*/
static void free_resource_record(mDNSResourceRecord* rr) {
  if(rr->name) {
    free(rr->name);
	rr->name = NULL;
  }
}
/*---------------------------------------------------------------------------*/
// parse a resource record
// the answer, authority and additional sections all use the resource record format

static int mdns_parse_rr(struct in_addr host, struct context_s *context, char* message, char* rrdata, int size, int is_answer) {
  mDNSResourceRecord rr;
  int parsed = 0;
  char* cur = rrdata;

  rr.name = NULL;

  rr.name = parse_rr_name(message, rrdata, &parsed);
  if(!rr.name) {
	// are calling functions dealing with this correctly? (comment from original code)
    free_resource_record(&rr);
    if (DEBUGGINGX(DEBUGX)) debug("parsing resource record name failed");
    return 0;
  }

  cur += parsed;

  // +10 because type, class, ttl and rdata_lenth
  // take up total 10 bytes
  if(parsed+10 > size) {
    free_resource_record(&rr);
    return 0;
  }

  if (DEBUGGINGX(DEBUGX)) debug("      Resource Record Name: %s", rr.name);

  memcpy(&(rr.rectype), cur, 2);
  rr.rectype = ntohs(rr.rectype);
  cur += 2;
  parsed += 2;

  if (DEBUGGINGX(DEBUGX)) debug("      Resource Record Type: %u", rr.rectype);

  memcpy(&(rr.recclass), cur, 2);
  rr.recclass = ntohs(rr.recclass);
  cur += 2;
  parsed += 2;

  memcpy(&(rr.ttl), cur, 4);
  rr.ttl = ntohl(rr.ttl);
  cur += 4;
  parsed += 4;

  if (DEBUGGINGX(DEBUGX)) debug("      ttl: %u", rr.ttl);

  memcpy(&(rr.rdata_length), cur, 2);
  rr.rdata_length = ntohs(rr.rdata_length);
  cur += 2;
  parsed += 2;

  if(parsed > size) {
    free_resource_record(&rr);
    return 0;
  }

  rr.rdata = cur;
  parsed += rr.rdata_length;

  if (is_answer) {
    if (rr.rectype == DNS_RR_TYPE_A) store_a(context, &rr);
    else store_other(host, context, message, &rr);
  }

  free_resource_record(&rr);

  if (DEBUGGINGX(DEBUGX)) debug("    ------------------------------");

  return parsed;
}
/*---------------------------------------------------------------------------*/
// this only parses the header so far (comment from original code)

static int mdns_parse_message_net(struct in_addr host, struct context_s *context, char* data, int size, mDNSMessage* msg) {

  int parsed = 0;
  int i;

  if(size < DNS_HEADER_SIZE) {
    return 0;
  }

  memcpy(msg, data, DNS_HEADER_SIZE);
  msg->id = ntohs(msg->id);
  msg->flags = ntohs(msg->flags);
  msg->qd_count = ntohs(msg->qd_count);
  msg->an_count = ntohs(msg->an_count);
  msg->ns_count = ntohs(msg->ns_count);
  msg->ar_count = ntohs(msg->ar_count);
  parsed += DNS_HEADER_SIZE;

  if (DEBUGGINGX(DEBUGX)) mdns_message_print(msg);

  if (DEBUGGINGX(DEBUGX)) debug("  Question records [%u] (not shown)", msg->qd_count);
  for(i=0; i < msg->qd_count; i++) {
    parsed += mdns_parse_question(data, data+parsed, size-parsed);
  }

  if (DEBUGGINGX(DEBUGX)) debug("  Answer records [%u]", msg->an_count);
  for(i=0; i < msg->an_count; i++) {
    //if (DEBUGGINGX(DEBUGX)) debug("    Answer record %u of %u", i+1, msg->an_count);
    parsed += mdns_parse_rr(host, context, data, data+parsed, size-parsed, 1);
  }

  if (DEBUGGINGX(DEBUGX)) debug("  Nameserver records [%u] (not shown)", msg->ns_count);
  for(i=0; i < msg->ns_count; i++) {
    parsed += mdns_parse_rr(host, context, data, data+parsed, size-parsed, 0);
  }

  if (DEBUGGINGX(DEBUGX)) debug("  Additional records [%u] (not shown)", msg->ns_count);
  for(i=0; i < msg->ar_count; i++) {
    parsed += mdns_parse_rr(host, context, data, data+parsed, size-parsed, 1);
  }

  return parsed;
}
/*---------------------------------------------------------------------------*/

static mDNSMessage* mdns_build_query_message(char* query_str, uint16_t query_type) {
  mDNSMessage* msg;
  mDNSQuestion question;
  mDNSFlags flags;

  msg = (mDNSMessageStruct *) malloc(sizeof(mDNSMessage));

  if(!msg) {
	if (DEBUGGINGX(DEBUGX)) debug("failed to allocate memory for mDNS message");
	return NULL;
  }

  flags.qr = 0; // this is a query
  flags.opcode = 0; // opcode must be 0 for multicast
  flags.aa = 0; // must be 0 for queries
  flags.tc = 0; // no (more) known-answer records coming
  flags.rd = 0; // must be 0 for multicast
  flags.ra = 0; // must be 0 for multicast
  flags.zero = 0; // must be zero
  flags.ad = 0; // must be zero for multicast
  flags.cd = 0; // must be zero for multicast
  flags.rcode = 0;

  msg->id = 0; // should be 0 for multicast query messages
  msg->flags = htons(mdns_pack_header_flags(flags));
  msg->qd_count = htons(1); // one question
  msg->an_count = msg->ns_count = msg->ar_count = 0;	// MCD This line was omitted from the other implementation
  														// of mDNS SD. The coder who ported that code to this
  														// version must have caught the omission and added this line
  question.qname = query_str;

  if(!question.qname) {
    return NULL;
  }

  question.prefer_unicast_response = 0;
  question.qtype = query_type;
  question.qclass = 1; // class for the internet (RFC 1035 section 3.2.4)

  if ((msg->data = mdns_pack_question(&question, &(msg->data_size))) == NULL) {
	  free(msg);
	  return NULL;
  }

  return msg;
}
/*---------------------------------------------------------------------------*/

static char* mdns_pack_message(mDNSMessage* msg, size_t* pack_length) {
  char* pack;

  *pack_length = DNS_HEADER_SIZE + msg->data_size;
  if(*pack_length > DNS_MESSAGE_MAX_SIZE) {
    if (DEBUGGINGX(DEBUGX)) debug("mDNS message too large");
	return NULL;
  }

  pack = (char *) malloc(*pack_length);
  if(!pack) {
    if (DEBUGGINGX(DEBUGX)) debug("failed to allocate data for packed mDNS message");
	return NULL;
  }

  memcpy(pack, msg, DNS_HEADER_SIZE);
  memcpy(pack + DNS_HEADER_SIZE, msg->data, msg->data_size);

  return pack;
}
/*---------------------------------------------------------------------------*/
// parse TXT resource record

static void mdns_parse_txt(char *txt, int txt_length, mDNSservice_t *s) {
	int len = 0, count = 0;
	char *p;
	int i;

	if (!txt) return;

	while (len + count < txt_length) {
		len += * ((char*) txt + len + count);
		count++;
	}

	s->attr_count = count;
	s->attr = (txt_attr_s *)malloc(count * sizeof(txt_attr_t));

	p = txt;
	for (i = 0; i < count; i++) {
		char *value = (char *) memchr(p + 1, '=', *p);
		if (value) {
			len = *p - (value - (p + 1)) - 1;
			s->attr[i].value = (char *) malloc(len + 1);
			memcpy(s->attr[i].value, value + 1, len);
			s->attr[i].value[len] = '\0';
			len = (value - (p + 1));
			s->attr[i].name = (char *) malloc(len + 1);
			memcpy(s->attr[i].name, p + 1, len);
			s->attr[i].name[len] = '\0';
		}
		else {
			len = *p;
			s->attr[i].name = (char *) malloc(len + 1);
			memcpy(s->attr[i].name, p + 1, len);
			s->attr[i].name[len] = '\0';
			s->attr[i].value = NULL;
		}
		p += *p + 1;
	}
}
/*---------------------------------------------------------------------------*/

static int send_query(int sock, char* query_arg, uint16_t query_type) {

  mDNSMessage* msg;
  byte* data;		// MCD was char (in original code)
  size_t data_size;
  int res;
  struct sockaddr_in addr;
  socklen_t addrlen;
  char* query_str;

  if ((query_str = prepare_query_string(query_arg)) == NULL) return -1;

  addr.sin_family = AF_INET;
  addr.sin_port = htons(MDNS_PORT);
  addr.sin_addr.s_addr = inet_addr(MDNS_MULTICAST_ADDRESS);
  addrlen = sizeof(addr);

  // build and pack the query message
  msg = mdns_build_query_message(query_str, query_type);
  free(query_str);
  if (!msg) return -1;

  data = mdns_pack_message(msg, &data_size);
  free(msg->data);
  free(msg);
  if (!data) return -1;

	// Display the packet in hex/ASCII
  if (DEBUGGINGX(DEBUGX)) {
    Line = "";
	for (size_t i = 0; i < data_size ; i++) {
    	char shortline[10];
		if (data[i] >= 32 && data[i] <=126) {
        	snprintf(shortline, sizeof shortline, "%c", data[i]);
			} else {
			snprintf(shortline, sizeof shortline, "\\%X", data[i]);
			}
        Line = Line + String(shortline);
		}
    if (ptrTIUCommThread) ptrTIUCommThread->PrintItem(Line, clGreen);
	if (DEBUGGINGX(DEBUGX)) debug("");
    }
  // send query message
  res = sendto(sock, data, data_size, 0, (struct sockaddr *) &addr, addrlen);
  free(data);

  return res;
}
/*---------------------------------------------------------------------------*/
/*
  An answer is complete if it has all of:
	* A hostname (from a SRV record)
	* A port (from a SRV record)
	* An IP address (from an A record)
 */
 static int is_complete(slist_t *s) {
  if (s->addr.s_addr && s->hostname && s->port && s->txt) return 1;
  else return 0;
}
/*---------------------------------------------------------------------------*/

static void store_a(struct context_s *context, mDNSResourceRecord* rr) {
  alist_t *b;
  struct in_addr addr;

  mdns_parse_rr_a((char *) rr->rdata, &addr);

  for (b = context->alist; b; b = b->next) {

	if (!strcmp(b->name, rr->name)) {
		if (addr.s_addr) b->addr = addr;
		b->eol = (GetTickCount() / 1000.0) + rr->ttl;
		return;
	}
  }

  b = (alist_s *) malloc(sizeof(alist_t));
  b->addr = addr;
  b->name = strdup(rr->name);
  b->eol = (GetTickCount() / 1000.0) + rr->ttl;

  insert_item((item_t*) b, (item_t**) &context->alist);
}
/*---------------------------------------------------------------------------*/

static slist_t *create_s(struct in_addr host, char *name, slist_t **list) {
  slist_t *s = (slist_t *) calloc(1, sizeof(slist_t));
  s->name = strdup(name);
  s->host = host;
  insert_item((item_t*) s, (item_t**) list);
  return s;
}
/*---------------------------------------------------------------------------*/

static void store_other(struct in_addr host, struct context_s *context, char *message, mDNSResourceRecord* rr) {
  slist_t *b = NULL;
  char *name = NULL;
  uint32_t now, ttl;

  // for a PTR, the rr name must match exactly the query, for others it shall
  // at least contain it, otherwise it(s not for us
  if ((rr->rectype == DNS_RR_TYPE_PTR && strcmp(rr->name, context->query)) ||
	  !strstr((char *) rr->name, context->query)) return;

  now = (GetTickCount() / 1000.0);
  ttl = (context->ttl && context->ttl < rr->ttl) ? context->ttl : rr->ttl;

  // the queuing tool is head insertion, so this reverts the time or arrival
  // entry with ttl = 0 are not created, deletion must apply to an existing one
  switch (rr->rectype) {

	// PTR: get service name
	case DNS_RR_TYPE_PTR: {
	  mdns_parse_rr_ptr(message, (char *) rr->rdata, &name);

	  // can't factorize the "for/switch" as name is update above
	  for (b = context->slist; b && (strcmp(b->name, name) || b->host.s_addr != host.s_addr); b = b->next);
	  if (!b && rr->ttl) b = create_s(host, name, &context->slist);

	  if (b) b->eol[0] = now + ttl;

	  free(name);
	  break;
	}

	// SRV: service descriptor ==> get hostname & port
	case DNS_RR_TYPE_SRV: {
	  unsigned short port;
	  char *hostname = NULL;

	  mdns_parse_rr_srv(message, (char *) rr->rdata, &hostname, &port);

	  for (b = context->slist; b && (strcmp(b->name, rr->name) || b->host.s_addr != host.s_addr); b = b->next);
	  if (!b && rr->ttl) b = create_s(host, rr->name, &context->slist);

	  if (b) {
		// update port
		if (port && b->port != port) {
		  b->port = port;
		  b->status = MDNS_UPDATED;
		}
		// update hostname
		if (!b->hostname || strcmp(b->hostname, hostname)) {
		  NFREE(b->hostname);
		  b->status = MDNS_UPDATED;
		  b->hostname = strdup(hostname);
		}
		b->eol[1] = now + ttl;
	  }

	  free(hostname);
	  break;
	}

	// TXT: get txt content
	case DNS_RR_TYPE_TXT: {
	  char *txt = NULL;
	  int length = 0;

	  mdns_parse_rr_txt(message, rr, &txt, &length);

	  for (b = context->slist; b && (strcmp(b->name, rr->name) || b->host.s_addr != host.s_addr); b = b->next);
	  if (!b && rr->ttl) b = create_s(host, rr->name, &context->slist);

	  if (b) {
		// update txt
		if (!b->txt || memcmp(b->txt, txt, length)) {
		  NFREE(b->txt);
		  b->txt = (char *) malloc(length);
		  b->txt_length = length;
		  memcpy(b->txt, txt, length);
		  b->status = MDNS_UPDATED;
		}
		b->eol[2] = now + ttl;
	  }

	  free(txt);
	  break;
	}
  }

  // set when was it last seen, except for deletion
  if (b && rr->ttl) b->seen = now;
}
/*---------------------------------------------------------------------------*/

static mDNSservice_t *build_update(struct context_s *context, bool build) {
  mDNSservice_t *services = NULL;
  uint32_t now = (GetTickCount() / 1000.0);
  alist_t *a;
  slist_t *s;

  // cleanup the alist
  a = context->alist;
  while (a) {
	alist_t *next = a->next;
	if (a->eol - now > 0x7fffffff) {
		remove_item((item_t*) a, (item_t**) &context->alist);
		free_a(a);
	}
	a = next;
  }

  s = context->slist;
  // order of the slist is reverse time of arrival so the order of the services,
  // as it uses the same queueing tool, will revert that back ... or so I think
  while (s) {
	slist_t *next = s->next;
	bool ptr_expired, srv_expired, txt_expired, a_expired;

	// got a complete answer, search for A
	a_expired = true;
	if (s->hostname && s->port && s->txt) {
		for (a = context->alist; a; a = a->next) {
			if (strcmp(s->hostname, a->name)) continue;
			if (s->addr.s_addr != a->addr.s_addr) {
				s->addr.s_addr = a->addr.s_addr;
				s->status = MDNS_UPDATED;
			}
			a_expired = false;
			break;
		}
	}

	ptr_expired = s->eol[0] - now > 0x7fffffff;
	srv_expired = s->eol[1] - now > 0x7fffffff;
	txt_expired = s->eol[2] - now > 0x7fffffff;

	// a service has expired - must be done before the below check to make sure
	// that the expiry is after in the queue
	if (ptr_expired || srv_expired || txt_expired || a_expired) {
		// set IP & port to zero so that caller knows, but txt is needed
		if (build && is_complete(s)) {
			mDNSservice_t *p = (mDNSservice_t *) malloc(sizeof(mDNSservice_t));
			p->host = s->host;
			p->name = strdup(s->name);
			p->hostname = strdup(s->hostname);
			p->addr = s->addr;
			p->port = s->port;
			p->since = now - s->seen;
			p->expired = true;
			mdns_parse_txt(s->txt, s->txt_length, p);
			insert_item((item_t*) p, (item_t**) &services);
		}
	}

	// a service has been updated, but it might have expired just after - so we
	// will have both creation & destruction in the response with correct order
	if (build && is_complete(s) && s->status != MDNS_CURRENT) {
		mDNSservice_t *p = (mDNSservice_t *) malloc(sizeof(mDNSservice_t));
		p->host = s->host;
		p->name = strdup(s->name);
		p->hostname = strdup(s->hostname);
		p->addr = s->addr;
		p->port = s->port;
		p->since = now - s->seen;
		p->expired = false;
		mdns_parse_txt(s->txt, s->txt_length, p);
		insert_item((item_t*)p, (item_t**) &services);
		s->status = MDNS_CURRENT;
	}

	if (ptr_expired && srv_expired && txt_expired) {
		// all RRs for service are expired.
		// now we can remove the service
		remove_item((item_t*) s, (item_t**) &context->slist);
		free_s(s);
	} else {
		// Mark expired RRs invalid
		if (ptr_expired && s->eol[0]) {
			s->eol[0] = 0;
			s->status = MDNS_EXPIRED;
		}
		if (srv_expired && s->eol[1]) {
			s->eol[1] = 0;
			NFREE(s->hostname);
			s->port = 0;
			s->hostname = NULL;
			s->status = MDNS_EXPIRED; /* needed? */
		}
		if (txt_expired && s->eol[2]) {
			s->eol[2] = 0;
			NFREE(s->txt);
			s->txt_length = 0;
			s->txt = NULL;
			s->status = MDNS_EXPIRED; /* needed? */
		}
		if (a_expired) {
			s->addr.s_addr = 0;
			s->status = MDNS_EXPIRED;
		}
	}

	s = next;
  }

  return services;
}
/*---------------------------------------------------------------------------*/

struct mDNShandle_s * __fastcall TMultiDNS::init_mDNS(struct in_addr host) {
  int sock;
  int res;
  struct ip_mreq mreq;
  struct sockaddr_in addr;
  socklen_t addrlen;
  int enable = 1;
  char param;
  mDNShandle_t *handle;

  if (DEBUGGINGX(DEBUGX)) debug("Opening socket");
  sock = socket(AF_INET, SOCK_DGRAM, 0);
  if(sock < 0) {
	if (DEBUGGINGX(DEBUGX)) debug("error opening socket");
	return NULL;
  }

  param = 32;
  if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (char *) &param, sizeof(param)) < 0) {
	if (DEBUGGINGX(DEBUGX)) debug("setsocket error setting multicast TTL");
	return NULL;
  }

  if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(enable)) < 0) {
	if (DEBUGGINGX(DEBUGX)) debug("setsocket error setting reuse addr sockopt" );
	return NULL;
  }

  param = 1;
  if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &param, sizeof(param)) < 0) {
	if (DEBUGGINGX(DEBUGX)) debug("setsocket error setting multicast_loop");
	return NULL;
  }

#if defined(NOTUSED)
  enable = sizeof(enable);
  if (!getsockopt(sock, SOL_SOCKET, SO_REUSEPORT,(void*) &enable, (void*) &enable)) {
	enable = 1;
	if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,(void*) &enable, sizeof(enable)) < 0) {
	  if (DEBUGGINGX(DEBUGX)) debug("error setting reuseport");
	}
  }
#endif

  memset(&addr, 0, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_port = htons(MDNS_PORT);
  addr.sin_addr.s_addr = INADDR_ANY;
  addrlen = sizeof(addr);

  if (DEBUGGINGX(DEBUGX)) debug("Binding socket port : %d IP: INADDR_ANY", MDNS_PORT);	// MCD added debug print

  res = bind(sock, (struct sockaddr *) &addr, addrlen);
  if(res < 0) {
    if (DEBUGGINGX(DEBUGX)) debug("Socket Binding Error = %d", WSAGetLastError());	// MCD added debug print
    return NULL;
    }

  memset(&mreq, 0, sizeof(mreq));
  mreq.imr_multiaddr.s_addr = inet_addr(MDNS_MULTICAST_ADDRESS);
  mreq.imr_interface.s_addr = host.s_addr;

  if (setsockopt (sock, IPPROTO_IP, IP_MULTICAST_IF, (char *) &mreq.imr_interface.s_addr, sizeof(mreq.imr_interface.s_addr)) < 0)  {
	if (DEBUGGINGX(DEBUGX)) debug("Setsocket multicast Error = %d", WSAGetLastError());	// MCD added debug print
	return NULL;
  }

  if (DEBUGGINGX(DEBUGX)) debug("Setting socket options for multicast");
  if(setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, sizeof(mreq)) < 0) {
	if (DEBUGGINGX(DEBUGX)) debug("Setsocket Add Membership Error = %d", WSAGetLastError());	// MCD added debug print
	return NULL;
  }

  handle = (struct mDNShandle_s *) calloc(1, sizeof(mDNShandle_s));
  handle->sock = sock;
  handle->state = MDNS_IDLE;
  handle->last = 0;

  return handle;
}
/*---------------------------------------------------------------------------*/

void __fastcall TMultiDNS::control_mDNS(struct mDNShandle_s *handle, mDNScontrol_e request) {
	if (!handle) return;
	// reset useless when stopped and is taken care by the query if running
	if (handle->state == MDNS_RUNNING) handle->control = request;
	else if (request == MDNS_RESET) clear_context(&handle->context);
}
/*---------------------------------------------------------------------------*/

void __fastcall TMultiDNS::close_mDNS(struct mDNShandle_s *handle) {
	if (!handle) return;
	// query is not running, clear here, otherwise the query will self-clear
	if (handle->state == MDNS_IDLE) {
		clear_context(&handle->context);
		closesocket(handle->sock);
		handle->sock = -1;
		free(handle);
	} else handle->state = MDNS_IDLE;
}

/*---------------------------------------------------------------------------*/

static void clear_context(struct context_s *context) {
  clear_list((item_s *) context->alist, (void (*)(void*)) &free_a);
  clear_list((item_s *) context->slist, (void (*)(void*)) &free_s);
  context->slist = NULL;
  context->alist = NULL;
}

/*---------------------------------------------------------------------------*/
static void free_item_mDNS(mDNSservice_t* slist) {
	int i;

	free(slist->name);
	free(slist->hostname);

	for (i = 0; i < slist->attr_count; i++) {
		if (slist->attr[i].name) free(slist->attr[i].name);
		if (slist->attr[i].value) free(slist->attr[i].value);
	}

	free(slist->attr);

	free(slist);
}


/*---------------------------------------------------------------------------*/
void __fastcall TMultiDNS::free_list_mDNS(mDNSservice_t* slist) {
  clear_list((item_s *) slist, (void(*)(void*)) &free_item_mDNS);
}


/*---------------------------------------------------------------------------*/
mDNSservice_t* __fastcall TMultiDNS::get_list_mDNS(struct mDNShandle_s *handle) {
  slist_t *s;
  mDNSservice_t *services = NULL, *p;

  for (s = handle->context.slist; s; s = s->next) {
	if (is_complete(s)) {
		p = (mDNSservice_t *) malloc(sizeof(mDNSservice_t));
		p->name = strdup(s->name);
		p->hostname = strdup(s->hostname);
		p->addr = s->addr;
		p->port = s->port;
		p->expired = false;
		mdns_parse_txt(s->txt, s->txt_length, p);
		insert_item((item_t*) s, (item_t**) &services);
	}
  }

  return services;
}
/*---------------------------------------------------------------------------*/
#define USEGROKVERSION true
/*---------------------------------------------------------------------------*/
#if ! USEGROKVERSION
bool __fastcall TMultiDNS::query_mDNS(struct mDNShandle_s * handle, AnsiString query, int ttl, uint32_t runtime, void *cookie) {
//
// Query runtime in milliseconds
//
  struct sockaddr_in addr;
  socklen_t addrlen;
  int res, parsed;
  byte* recvdata;		// MCD was char (in original code)
  fd_set active_fd_set, read_fd_set, except_fd_set;
  mDNSservice_t *slist;
  uint32_t now, starttime;
  bool stop = false, rc = true;

  if (!handle || handle->sock < 0) return false;

  if(query[1] != '_') { // index one is first character in AnsiString
	if (DEBUGGINGX(DEBUGX)) debug("only service queries currently supported");
	return false;
  }

  starttime = GetTickCount();
  uint32_t endtime = starttime + runtime;

  addr.sin_family = AF_INET;
  addr.sin_port = htons(MDNS_PORT);
  addr.sin_addr.s_addr = inet_addr(MDNS_MULTICAST_ADDRESS);
  addrlen = sizeof(addr);

  recvdata = (unsigned char *) malloc(DNS_BUFFER_SIZE);
  if (!recvdata) return false;

  FD_ZERO(&active_fd_set);
  FD_SET(handle->sock, &active_fd_set);

  if (DEBUGGINGX(DEBUGX)) debug("Entering main query_mDNS loop - runtime (%d)", runtime);

  handle->context.query = query.c_str();
  handle->context.ttl = ttl;
  handle->state = MDNS_RUNNING;
  handle->last = GetTickCount();

  #define SELECTTIME 50     // milliseconds

  // this protects against a u32 rollover
  while (1) {
	struct timeval sel_time = {0, SELECTTIME*1000};

	now = GetTickCount();

	// re-launch a search regularly every SELECTTIME milliseconds
	if (now > handle->last + SELECTTIME) {
		send_query(handle->sock, handle->context.query, DNS_RR_TYPE_PTR);
		handle->last = now;
	}

	read_fd_set = active_fd_set;
	except_fd_set = active_fd_set;

	res = select(handle->sock + 1, &read_fd_set, NULL, &except_fd_set, &sel_time);

	// finishing or suspending query
	if (handle->state == MDNS_IDLE || handle->control == MDNS_SUSPEND || (now > endtime)) {
		if (DEBUGGINGX(DEBUGX)) debug("Query idle or suspended or runtime (%d) exceeded", endtime);
		break;
	}

	// just clear list
	if (handle->control == MDNS_RESET) {
	  clear_context(&handle->context);
	  handle->control = MDNS_NONE;
	  handle->last = now - 3600;    // milliseconds
	}

	if (res < 0) {
	  rc = false;
	  if (DEBUGGINGX(DEBUGX)) debug("Select error");
	  break;
	}

	if (res == 0) continue;

	if(FD_ISSET(handle->sock, &except_fd_set)) {
	  rc = false;
	  if (DEBUGGINGX(DEBUGX)) debug("exception on socket");
	  break;
	}

	// DNS messages should arrive as single packets
	// so we don't need to worry about partial receives
	if (DEBUGGINGX(DEBUGX)) debug("Receiving data");
	res = recvfrom(handle->sock, recvdata, DNS_BUFFER_SIZE, 0, (struct sockaddr *) &addr, (int *)&addrlen);

	if (res < 0) {
	  rc = false;
	  if (DEBUGGINGX(DEBUGX)) debug("error receiving");
	  break;
	} else if (res == 0) {
	  rc = false;
	  if (DEBUGGINGX(DEBUGX)) debug("unknown error"); // for TCP means connection closed, but for UDP? (comment from original code)
	}

	if (DEBUGGINGX(DEBUGX)) debug("Received %u bytes from %s", res, inet_ntoa(addr.sin_addr));

	parsed = 0;
	if (DEBUGGINGX(DEBUGX)) debug("Attempting to parse received data");

	// Display the packet in hex/ASCII
    if (DEBUGGINGX(DEBUGX)) {
        Line = "";
        for (int i = 0; i < res ; i++) {
            char shortline[10];
            if (recvdata[i] >= 32 && recvdata[i] <=126) {
                snprintf(shortline, sizeof shortline, "%c", recvdata[i]);
                } else {
                snprintf(shortline, sizeof shortline, "\\%X", recvdata[i]);
                }
            Line = Line + String(shortline);
            }
		if (ptrTIUCommThread) ptrTIUCommThread->PrintItem(Line, clBlack);
        if (DEBUGGINGX(DEBUGX)) debug("");
        }

	// loop through received data
	do {
	  int resp;
	  mDNSMessage msg;

	  resp = mdns_parse_message_net(addr.sin_addr, &handle->context, recvdata+parsed, res, &msg);

	  // if nothing else is parsable, stop parsing
	  if (resp <= 0) break;

	  parsed += resp;
	  if (DEBUGGINGX(DEBUGX)) debug("--Parsed %u bytes of %u received bytes", parsed, res);
	} while(parsed < res); // while there is still something to parse

	if (DEBUGGINGX(DEBUGX)) debug("Finished parsing received data");

	slist = build_update(&handle->context, true /*callback != NULL*/);
    if (slist) {
    	PrintServices(slist, cookie, &stop);
		free_list_mDNS(slist);
        }

	if (stop) {
		if (DEBUGGINGX(DEBUGX)) debug("Stop Request Honored");
		break;
		}
  }

  free(recvdata);

  // this is request for stop, we have to clean by ourselves
  if (handle->state == MDNS_IDLE) {
	  clear_context(&handle->context);
	  closesocket(handle->sock);
	  handle->sock = -1;
	  free(handle);
  } else {
	  handle->control = MDNS_NONE;
	  handle->state = MDNS_IDLE;
  }
  return rc;
}
#endif
#if USEGROKVERSION
//---------------------------------------------------------------------------
/*
This modified query_mDNS was the 2nd version written by Grok.

https://grok.com/share/c2hhcmQtMg_6a724522-3108-436b-bb79-94f7cce1c1a7

https://grok.com/c/1591077f-a79d-486e-8dd2-c89a5802840d?rid=4cd55d59-d387-47e0-8a69-85a9c9eb7f25


I was never confident that the GROK code worked correctly or even it

was needed in the first place. I reverted back to the original code

*/

bool __fastcall TMultiDNS::query_mDNS(struct mDNShandle_s * handle, AnsiString query, int ttl, uint32_t runtime, void *cookie) {
//
// Query runtime in milliseconds
//
  struct sockaddr_in addr;
  socklen_t addrlen;
  int res;
  byte* recvdata;		// MCD was char (in original code)
  fd_set active_fd_set, read_fd_set, except_fd_set;
  bool stop = false, rc = true;

  if (!handle || handle->sock < 0) return false;

  if(query[1] != '_') { // index one is first character in AnsiString
	if (DEBUGGINGX(DEBUGX)) debug("only service queries currently supported");
	return false;
  }

//---------------------------------------------------------------------------

// main loop with proper timing + random jitter
//---------------------------------------------------------------------------

uint32_t starttime = GetTickCount();

addr.sin_family = AF_INET;
addr.sin_port = htons(MDNS_PORT);
addr.sin_addr.s_addr = inet_addr(MDNS_MULTICAST_ADDRESS);
addrlen = sizeof(addr);

recvdata = (unsigned char *) malloc(DNS_BUFFER_SIZE);
if (!recvdata) return false;

FD_ZERO(&active_fd_set);
FD_SET(handle->sock, &active_fd_set);

if (DEBUGGINGX(DEBUGX)) debug("Entering query_mDNS loop - runtime %u ms", runtime);

handle->context.query = query.c_str();
handle->context.ttl = ttl;
handle->state = MDNS_RUNNING;

// Force first query almost immediately
handle->last = GetTickCount() - 1000;

#define INITIAL_QUERIES      3
#define INITIAL_INTERVAL     250     // ms
#define NORMAL_INTERVAL      1000    // ms
#define SELECT_TIMEOUT_MS    50
#define INITIAL_JITTER_MS    50      // ±50 ms during burst
#define NORMAL_JITTER_PCT    10      // ±10% during normal phase

uint32_t query_count = 0;
uint32_t current_interval = INITIAL_INTERVAL;

while (1) {
    struct timeval sel_time = {0, SELECT_TIMEOUT_MS * 1000};
    uint32_t now = GetTickCount();

    // Early exit checks (rollover-safe)
    if (handle->state == MDNS_IDLE || 
        handle->control == MDNS_SUSPEND || 
        (now - starttime > runtime)) {
        if (DEBUGGINGX(DEBUGX)) debug("Query finished or runtime exceeded");
        break;
    }

    // Handle reset request
    if (handle->control == MDNS_RESET) {
        clear_context(&handle->context);
        handle->control = MDNS_NONE;
        handle->last = now - 1000;          // force quick re-send
        query_count = 0;
        current_interval = INITIAL_INTERVAL;
    }

    // === Send query when due ===
    uint32_t time_since_last = now - handle->last;
    if (time_since_last >= current_interval || query_count == 0) {
        send_query(handle->sock, handle->context.query, DNS_RR_TYPE_PTR);
        query_count++;

        if (DEBUGGINGX(DEBUGX)) debug("Sent mDNS query #%u", query_count);

        // Switch to slower interval after initial burst
        if (query_count >= INITIAL_QUERIES) {
            current_interval = NORMAL_INTERVAL;
        }

        // === Apply random jitter ===
        int jitter;
        if (query_count <= INITIAL_QUERIES) {
            jitter = (rand() % (2 * INITIAL_JITTER_MS + 1)) - INITIAL_JITTER_MS;
        } else {
            int jitter_range = (current_interval * NORMAL_JITTER_PCT) / 100;
            jitter = (rand() % (2 * jitter_range + 1)) - jitter_range;
        }

        // Use the jitter by shifting the base time
        // (prevents queries from bunching up)
        handle->last = now + jitter;

        // Optional safety: don't let next send be in the past
        if ((int)(handle->last - now) < 0)
            handle->last = now;
    }

    // Prepare fd sets
    read_fd_set = active_fd_set;
    except_fd_set = active_fd_set;

    res = select(handle->sock + 1, &read_fd_set, NULL, &except_fd_set, &sel_time);

    // Re-check exit conditions after select
    if (handle->state == MDNS_IDLE || 
        handle->control == MDNS_SUSPEND || 
        (GetTickCount() - starttime > runtime)) {
        if (DEBUGGINGX(DEBUGX)) debug("Exiting after select");
        break;
    }

    if (res < 0) {
        rc = false;
        if (DEBUGGINGX(DEBUGX)) debug("Select error");
        break;
    }
    if (res == 0) continue;

    if (FD_ISSET(handle->sock, &except_fd_set)) {
        rc = false;
        if (DEBUGGINGX(DEBUGX)) debug("Exception on socket");
        break;
    }

    // === Receive, parse, and callback (unchanged) ===
    if (DEBUGGINGX(DEBUGX)) debug("Receiving data");
    res = recvfrom(handle->sock, recvdata, DNS_BUFFER_SIZE, 0,
                   (struct sockaddr *)&addr, (int *)&addrlen);

    if (res < 0) {
        rc = false;
        if (DEBUGGINGX(DEBUGX)) debug("Error receiving");
        break;
    }
    if (res == 0) continue;

    if (DEBUGGINGX(DEBUGX)) debug("Received %u bytes from %s", res, inet_ntoa(addr.sin_addr));

    // Hex dump
    if (DEBUGGINGX(DEBUGX)) {
        AnsiString Line = "";
        for (int i = 0; i < res; i++) {
            char shortline[10];
            if (recvdata[i] >= 32 && recvdata[i] <= 126)
                snprintf(shortline, sizeof(shortline), "%c", recvdata[i]);
            else
                snprintf(shortline, sizeof(shortline), "\\%02X", (unsigned char)recvdata[i]);
            Line += shortline;
        }
        if (ptrTIUCommThread) ptrTIUCommThread->PrintItem(Line, clBlack);
        if (DEBUGGINGX(DEBUGX)) debug("");
    }

    // Parse
    int parsed = 0;
    do {
        int resp;
        mDNSMessage msg;
        resp = mdns_parse_message_net(addr.sin_addr, &handle->context,
                                      recvdata + parsed, res, &msg);
        if (resp <= 0) break;
        parsed += resp;
    } while (parsed < res);

    // Process results
    mDNSservice_t *slist = build_update(&handle->context, true);
    if (slist) {
        PrintServices(slist, cookie, &stop);
        free_list_mDNS(slist);
    }

    if (stop) {
        if (DEBUGGINGX(DEBUGX)) debug("Stop request honored");
        break;
    }
}

free(recvdata);

// Final cleanup
if (handle->state == MDNS_IDLE) {
    clear_context(&handle->context);
    closesocket(handle->sock);
    handle->sock = -1;
    free(handle);
} else {
    handle->control = MDNS_NONE;
    handle->state = MDNS_IDLE;
}

return rc;


}

#endif

//---------------------------------------------------------------------------


