/**
 * lwip MDNS resolver file.
 *
 * Created on: Jul 29, 2010
 * Author: Daniel Toma
 *

 * ported from uIP resolv.c Copyright (c) 2002-2003, Adam Dunkels.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**

 * This file implements a MDNS host name and PUCK service registration.

 *-----------------------------------------------------------------------------
 * Includes
 *----------------------------------------------------------------------------*/
#include "lwip/opt.h"
#if LWIP_MDNS /* don't build if not configured for use in lwipopts.h */
#include "lwip/mdns.h"
#include "lwip/udp.h"
#include "lwip/mem.h"
#include "lwip/igmp.h"
#include "osapi.h"
#include "os_type.h"
#include "user_interface.h"
#include "c_string.h"
#include "nodemcu_mdns.h"

#if 0
#define MDNS_DBG(...)  dbg_printf(...)
#else
#define MDNS_DBG(...)  do {} while (0)
#endif

#define min(a,b) ((a) < (b) ? (a) : (b))

#define MDNS_NAME_LENGTH     68 //68

#define DNS_RRTYPE_NSEC    47

static const char* service_name_with_suffix    = NULL;
#define DNS_SD_SERVICE       "_services._dns-sd._udp.local"
#define PUCK_SERVICE_LENGTH  30

#define PUCK_DATASHEET_SIZE   	96

#ifdef MEMLEAK_DEBUG
static const char mem_debug_file[] ICACHE_RODATA_ATTR = __FILE__;
#endif

/** DNS server IP address */
#ifndef DNS_MULTICAST_ADDRESS
#define DNS_MULTICAST_ADDRESS        ipaddr_addr("224.0.0.251") /* resolver1.opendns.com */
#endif

/** DNS server IP address */
#ifndef MDNS_LOCAL
#define MDNS_LOCAL                "local" /* resolver1.opendns.com */
#endif

/** DNS server port address */
#ifndef DNS_MDNS_PORT
#define DNS_MDNS_PORT           5353
#endif

/** DNS maximum number of retries when asking for a name, before "timeout". */
#ifndef DNS_MAX_RETRIES
#define DNS_MAX_RETRIES           4
#endif

/** DNS resource record max. TTL (one week as default) */
#ifndef DNS_MAX_TTL
#define DNS_MAX_TTL               604800
#endif

/* DNS protocol flags */
#define DNS_FLAG1_RESPONSE        0x84
#define DNS_FLAG1_OPCODE_STATUS   0x10
#define DNS_FLAG1_OPCODE_INVERSE  0x08
#define DNS_FLAG1_OPCODE_STANDARD 0x00
#define DNS_FLAG1_AUTHORATIVE     0x04
#define DNS_FLAG1_TRUNC           0x02
#define DNS_FLAG1_RD              0x01
#define DNS_FLAG2_RA              0x80
#define DNS_FLAG2_ERR_MASK        0x0f
#define DNS_FLAG2_ERR_NONE        0x00
#define DNS_FLAG2_ERR_NAME        0x03

/* DNS protocol states */
#define DNS_STATE_UNUSED          0
#define DNS_STATE_NEW             1
#define DNS_STATE_ASKING          2
#define DNS_STATE_DONE            3

/* MDNS registration type */
#define MDNS_HOSTNAME_REG         0
#define MDNS_SERVICE_REG          1

/* MDNS registration type */
#define MDNS_REG_ANSWER           1
#define MDNS_SD_ANSWER            2
#define MDNS_SERVICE_REG_ANSWER   3

/* MDNS registration time */
#define MDNS_HOST_TIME            120
#define MDNS_SERVICE_TIME         3600

/** MDNS name length with "." at the beginning and end of name*/
#ifndef MDNS_LENGTH_ADD
#define MDNS_LENGTH_ADD           2
#endif

#ifdef MDNS_MAX_NAME_LENGTH
#undef MDNS_MAX_NAME_LENGTH
#endif
#define MDNS_MAX_NAME_LENGTH      (256)

PACK_STRUCT_BEGIN
/** DNS message header */
struct mdns_hdr {
	PACK_STRUCT_FIELD(u16_t id);
	PACK_STRUCT_FIELD(u8_t flags1);
	PACK_STRUCT_FIELD(u8_t flags2);
	PACK_STRUCT_FIELD(u16_t numquestions);
	PACK_STRUCT_FIELD(u16_t numanswers);
	PACK_STRUCT_FIELD(u16_t numauthrr);
	PACK_STRUCT_FIELD(u16_t numextrarr);
}PACK_STRUCT_STRUCT;
PACK_STRUCT_END

#define SIZEOF_DNS_HDR 12

PACK_STRUCT_BEGIN
/** MDNS query message structure */
struct mdns_query {
	/* MDNS query record starts with either a domain name or a pointer
	 to a name already present somewhere in the packet. */PACK_STRUCT_FIELD(u16_t type);
	PACK_STRUCT_FIELD(u16_t class);
}PACK_STRUCT_STRUCT;
PACK_STRUCT_END

#define SIZEOF_DNS_QUERY 4

PACK_STRUCT_BEGIN
/** MDNS answer message structure */
struct mdns_answer {
	/* MDNS answer record starts with either a domain name or a pointer
	 to a name already present somewhere in the packet. */PACK_STRUCT_FIELD(u16_t type);
	PACK_STRUCT_FIELD(u16_t class);
	PACK_STRUCT_FIELD(u32_t ttl);
	PACK_STRUCT_FIELD(u16_t len);
}PACK_STRUCT_STRUCT;
PACK_STRUCT_END
#define SIZEOF_DNS_ANSWER 10

PACK_STRUCT_BEGIN
/** MDNS answer message structure */
struct mdns_a_rr {
	PACK_STRUCT_FIELD(u32_t src);
}PACK_STRUCT_STRUCT;
PACK_STRUCT_END

#define SIZEOF_MDNS_A_RR 4
PACK_STRUCT_BEGIN
/** MDNS service registration message structure */
struct mdns_service {
	PACK_STRUCT_FIELD(u16_t prior);
	PACK_STRUCT_FIELD(u16_t weight);
	PACK_STRUCT_FIELD(u16_t port);
}PACK_STRUCT_STRUCT;
PACK_STRUCT_END

#define SIZEOF_MDNS_SERVICE 6

static os_timer_t mdns_timer;
/* forward declarations */
static void mdns_recv(void *s, struct udp_pcb *pcb, struct pbuf *p,
		struct ip_addr *addr, u16_t port);

/*-----------------------------------------------------------------------------
 * Globales
 *----------------------------------------------------------------------------*/

/* MDNS variables */
//static char puck_datasheet[PUCK_DATASHEET_SIZE];
static struct udp_pcb *mdns_pcb = NULL;
static struct nodemcu_mdns_info * ms_info = NULL;
static struct ip_addr multicast_addr;
static uint8 register_flag = 0;
static uint8 mdns_flag = 0;
static u8_t *mdns_payload;

/**
 * Compare the "dotted" name "query" with the encoded name "response"
 * to make sure an answer from the DNS server matches the current mdns_table
 * entry (otherwise, answers might arrive late for hostname not on the list
 * any more).
 *
 * @param query hostname (not encoded) from the mdns_table
 * @param response encoded hostname in the DNS response
 * @return 0: names equal; 1: names differ
 */
static u8_t ICACHE_FLASH_ATTR
mdns_compare_name(unsigned char *query, unsigned char *response, unsigned char *pktbase) {
	unsigned char n;

	do {
		n = *response++;
		/** @see RFC 1035 - 4.1.4. Message compression */
		if ((n & 0xc0) == 0xc0) {
		  	n = ((n << 8) + *response) & 0x3fff;
			if (n < response - pktbase) {
			  response = pktbase + n;
			} else {
			  return 1;
			}
		} else {
			/* Not compressed name */
			while (n > 0) {
				char q = *query;
				if (q >= 'A' && q <= 'Z') {
				  q = q + 'a' - 'A';
				}
				char r = *response;
				if (r >= 'A' && r <= 'Z') {
				  r = r + 'a' - 'A';
				}
				if (q != r) {
					return 1;
				}
				++response;
				++query;
				--n;
			};
			++query;
		}
	} while (*response != 0);

	return 0;
}

static int 
mdns_namelen(u8_t *p, unsigned int maxlen) {
  u8_t *orig = p;

  while (*p && *p <= 63) {
    if (p - orig > maxlen) {
      return -1;
    }
    p += *p + 1;
  }

  if (*p >= 0xc0) {
    p += 2;	// advance over the two byte pointer
  } else {
    p++;	// advance over the final 0
  }

  if (p - orig > maxlen) {
    return -1;
  }

  return p - orig;
}

/* Copy an unencoded name into an encoded name */
static unsigned char *copy_and_encode_name(unsigned char *ptr, const char *name) {
  while (*name) {
    const char *p = name;
    while (*p != '.' && *p) {
      p++;
    }
    *ptr++ = p - name;
    memcpy(ptr, name, p - name);
    ptr += p - name;
    if (!*p) {
      break;
    }
    name = p + 1;
  }
  *ptr++ = 0;

  return ptr;
}

static err_t send_packet(struct pbuf *p, struct ip_addr *dst_addr, u16_t dst_port, u8_t *addr_ptr) {
  err_t err;
  /* send dns packet */
  struct netif *sta_netif = (struct netif *)eagle_lwip_getif(0x00);
  struct netif *ap_netif =  (struct netif *)eagle_lwip_getif(0x01);

  if (addr_ptr) {
    if (wifi_get_opmode() == 0x02) {
      if (!ap_netif) {
	return;
      }
      memcpy(addr_ptr, &ap_netif->ip_addr, sizeof(ap_netif->ip_addr));
    } else {
      if (!sta_netif) {
	return;
      }
      memcpy(addr_ptr, &sta_netif->ip_addr, sizeof(sta_netif->ip_addr));
    }
  }

  if (dst_addr) {
    err = udp_sendto(mdns_pcb, p, dst_addr, dst_port);
  } else {
    err = udp_sendto(mdns_pcb, p, &multicast_addr, DNS_MDNS_PORT);
    if(wifi_get_opmode() == 0x03 && wifi_get_broadcast_if() == 0x03 &&\
		    sta_netif != NULL && ap_netif != NULL) {
      if(netif_is_up(sta_netif) && netif_is_up(ap_netif)) {
	netif_set_default(sta_netif);
	if (addr_ptr) {
	  memcpy(addr_ptr, &ap_netif->ip_addr, sizeof(ap_netif->ip_addr));
	}
	err = udp_sendto(mdns_pcb, p, &multicast_addr, DNS_MDNS_PORT);
	netif_set_default(ap_netif);
      }
    }
  }

  /* free pbuf */
  pbuf_free(p);

  return err;
}
/**
 * Send a mDNS packet for the service type
 *
 * @param id transaction ID in the DNS query packet
 * @return ERR_OK if packet is sent; an err_t indicating the problem otherwise
 */
static err_t ICACHE_FLASH_ATTR
mdns_send_service_type(u16_t id, struct ip_addr *dst_addr, u16_t dst_port) {
	err_t err;
	struct mdns_hdr *hdr;
	struct mdns_answer ans;
	struct mdns_a_rr a_rr;
	struct mdns_service serv;
	struct pbuf *p ,*p_sta;
	char *query, *nptr;
	const char *pHostname;
	struct netif * sta_netif = NULL;
	struct netif * ap_netif = NULL;
	int max_ttl = dst_addr ? 10 : 7200;
	char tmpBuf[PUCK_DATASHEET_SIZE + PUCK_SERVICE_LENGTH];
	u8_t n;
	u16_t length = 0;
	/* if here, we have either a new query or a retry on a previous query to process */
	p = pbuf_alloc(PBUF_TRANSPORT,
			SIZEOF_DNS_HDR + MDNS_MAX_NAME_LENGTH * 2 + SIZEOF_DNS_QUERY, PBUF_RAM);
	if (p != NULL) {
		LWIP_ASSERT("pbuf must be in one piece", p->next == NULL);
		/* fill dns header */
		hdr = (struct mdns_hdr*) p->payload;
		os_memset(hdr, 0, SIZEOF_DNS_HDR);
		hdr->id = htons(id);
		hdr->flags1 = DNS_FLAG1_RESPONSE;

		pHostname = DNS_SD_SERVICE;
		hdr->numanswers = htons(1);
		query = (char*) hdr + SIZEOF_DNS_HDR;
		--pHostname;
		/* convert hostname into suitable query format. */
		do {
			++pHostname;
			nptr = query;
			++query;
			for (n = 0; *pHostname != '.' && *pHostname != 0; ++pHostname) {
				*query = *pHostname;
				++query;
				++n;
			}
			*nptr = n;
		} while (*pHostname != 0);
		*query++ = '\0';
		/* fill dns query */


		ans.type = htons(DNS_RRTYPE_PTR);
		ans.class = htons(DNS_RRCLASS_IN);
		ans.ttl = htonl(min(max_ttl, 3600));
		ans.len = htons(os_strlen(service_name_with_suffix) + 1 +1 );
		length = 0;

		MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);

		/* resize the query */
		query = query + SIZEOF_DNS_ANSWER;
		pHostname = service_name_with_suffix;
		--pHostname;

		/* convert hostname into suitable query format. */
		do {
			++pHostname;
			nptr = query;
			++query;
			for (n = 0; *pHostname != '.' && *pHostname != 0; ++pHostname) {
				*query = *pHostname;
				++query;
				++n;
			}
			*nptr = n;
		} while (*pHostname != 0);
		*query++ = '\0';

		/* resize pbuf to the exact dns query */
		pbuf_realloc(p, (query + length) - ((char*) (p->payload)));

		err = send_packet(p, dst_addr, dst_port, 0);

	} else {
		err = ERR_MEM;
	}

	return err;
}

/**
 * Send a mDNS service answer packet.
 *
 * @param name service name to query
 * @param id transaction ID in the DNS query packet
 * @return ERR_OK if packet is sent; an err_t indicating the problem otherwise
 */
static err_t ICACHE_FLASH_ATTR
mdns_send_service(struct nodemcu_mdns_info *info, u16_t id, struct ip_addr *dst_addr, u16_t dst_port) {
	err_t err;
	struct mdns_hdr *hdr;
	struct mdns_answer ans;
	struct mdns_service serv;
	struct mdns_a_rr a_rr;
	struct pbuf *p ,*p_sta;
	char *query, *nptr;
	char *query_end;
	const char *pHostname;
	const char *name = info->host_name;
	int max_ttl = dst_addr ? 10 : 7200;
	u8_t n;
	u8_t i = 0;
	u16_t length = 0;
	u8_t addr1 = 12, addr2 = 12;
	struct netif * sta_netif = NULL;
	struct netif * ap_netif = NULL;
	char tmpBuf[PUCK_DATASHEET_SIZE + PUCK_SERVICE_LENGTH];
	u16_t dns_class = dst_addr ? DNS_RRCLASS_IN : DNS_RRCLASS_FLUSH_IN;
	/* if here, we have either a new query or a retry on a previous query to process */
	p = pbuf_alloc(PBUF_TRANSPORT,
			SIZEOF_DNS_HDR + MDNS_MAX_NAME_LENGTH * 2 + SIZEOF_DNS_QUERY, PBUF_RAM);
	if (p != NULL) {
		LWIP_ASSERT("pbuf must be in one piece", p->next == NULL);
		/* fill dns header */
		hdr = (struct mdns_hdr*) p->payload;
		os_memset(hdr, 0, SIZEOF_DNS_HDR);
		hdr->id = htons(id);
		hdr->flags1 = DNS_FLAG1_RESPONSE;
		hdr->numanswers = htons(4);
		hdr->numextrarr = htons(1);
		query = (char*) hdr + SIZEOF_DNS_HDR;
		query_end = (char *) p->payload + p->tot_len;
		c_strlcpy(tmpBuf, service_name_with_suffix, sizeof(tmpBuf));

		pHostname = tmpBuf;
		--pHostname;

		/* convert hostname into suitable query format. */
		do {
			++pHostname;
			nptr = query;
			++query;
			++addr1;
			++addr2;
			for (n = 0; *pHostname != '.' && *pHostname != 0; ++pHostname) {
				*query = *pHostname;
				++query;
				++addr1;
				++addr2;
				++n;
			}
			*nptr = n;
		} while (*pHostname != 0);
		*query++ = '\0';
		length = sizeof(MDNS_LOCAL);
		addr1 -= length;
		length = os_strlen(service_name_with_suffix) + 1;
		addr2 -= length;

		ans.type = htons(DNS_RRTYPE_PTR);
		ans.class = htons(DNS_RRCLASS_IN);
		ans.ttl = htonl(min(max_ttl, 300));
		length = os_strlen(ms_info->host_desc) + MDNS_LENGTH_ADD + 1;
		ans.len = htons(length);
		length = 0;

		MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);
		/* resize the query */
		query = query + SIZEOF_DNS_ANSWER;

		int name_offset = query - (const char *) hdr;

		pHostname = ms_info->host_desc;
		--pHostname;
		/* convert hostname into suitable query format. */
		do {
			++pHostname;
			nptr = query;
			++query;
			for (n = 0; *pHostname != '.' && *pHostname != 0; ++pHostname) {
				*query = *pHostname;
				++query;
				++n;
			}
			*nptr = n;
		} while (*pHostname != 0);
		*query++ = DNS_OFFSET_FLAG;
		*query++ = DNS_DEFAULT_OFFSET;

		*query++ = 0xc0 + (name_offset >> 8);
		*query++ = name_offset & 0xff;

		/* fill the answer */
		ans.type = htons(DNS_RRTYPE_TXT);
		ans.class = htons(dns_class);
		ans.ttl = htonl(min(max_ttl, 300));
//		length = os_strlen(TXT_DATA) + MDNS_LENGTH_ADD + 1;
		const char *attributes[12];
		int attr_count = 0;
		for(i = 0; i < 10 && (info->txt_data[i] != NULL); i++) {
		  length += os_strlen(info->txt_data[i]);
		  length++;
		  attributes[attr_count++] = info->txt_data[i];
		}
		//MDNS_DBG("Found %d user attributes\n", i);
		static const char *defaults[] = { "platform=nodemcu", NULL };
		for(i = 0; defaults[i] != NULL; i++) {
		  // See if this is a duplicate
		  int j;
		  int len = strchr(defaults[i], '=') + 1 - defaults[i];
		  for (j = 0; j < attr_count; j++) {
		    if (strncmp(attributes[j], defaults[i], len) == 0) {
		      break;
		    }
		  }
		  if (j == attr_count) {
		    length += os_strlen(defaults[i]);
		    length++;
		    attributes[attr_count++] = defaults[i];
		  }
		}
		//MDNS_DBG("Found %d total attributes\n", attr_count);

		ans.len = htons(length);
		MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);
		query = query + SIZEOF_DNS_ANSWER;
		// Check enough space in the packet
	        const char *end_of_packet = query + length + 2 + SIZEOF_DNS_ANSWER + SIZEOF_MDNS_SERVICE +
		    os_strlen(ms_info->host_name) + 7 + 1 + 2 + SIZEOF_DNS_ANSWER + SIZEOF_MDNS_A_RR +
		    2 + SIZEOF_DNS_ANSWER + 5;

	        if (query_end <= end_of_packet) {
		  MDNS_DBG("Too much data to send\n");
		  pbuf_free(p);
		  return ERR_MEM;
		}
		//MDNS_DBG("Query=%x, query_end=%x, end_ofpacket=%x, length=%x\n", query, query_end, end_of_packet, length);

		i = 0;
		while(attributes[i] != NULL && i < attr_count) {
			pHostname = attributes[i];
			--pHostname;
			/* convert hostname into suitable query format. */
			do {
				++pHostname;
				nptr = query;
				++query;
				for (n = 0;  *pHostname != 0; ++pHostname) {
					*query = *pHostname;
					++query;
					++n;
				}
				*nptr = n;
			} while (*pHostname != 0);
			i++;
		}
//		*query++ = '\0';
		// Increment by length
		*query++ = 0xc0 + (name_offset >> 8);
		*query++ = name_offset & 0xff;

		// Increment by 2

		ans.type = htons(DNS_RRTYPE_SRV);
		ans.class = htons(dns_class);
		ans.ttl = htonl(min(max_ttl, 300));
		c_strlcpy(tmpBuf,ms_info->host_name, sizeof(tmpBuf));
		c_strlcat(tmpBuf, ".", sizeof(tmpBuf));
		c_strlcat(tmpBuf, MDNS_LOCAL, sizeof(tmpBuf));
		length = os_strlen(tmpBuf) + MDNS_LENGTH_ADD;
		ans.len = htons(SIZEOF_MDNS_SERVICE + length);
		length = 0;
		MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);

		/* resize the query */
		query = query + SIZEOF_DNS_ANSWER;

		serv.prior = htons(0);
		serv.weight = htons(0);
		serv.port = htons(ms_info->service_port);
		MEMCPY( query, &serv, SIZEOF_MDNS_SERVICE);
		/* resize the query */
		query = query + SIZEOF_MDNS_SERVICE;

		int hostname_offset = query - (const char *) hdr;

		pHostname = tmpBuf;
		--pHostname;
		do {
			++pHostname;
			nptr = query;
			++query;
			for (n = 0; *pHostname != '.' && *pHostname != 0; ++pHostname) {
				*query = *pHostname;
				++query;
				++n;
			}
			*nptr = n;
		} while (*pHostname != 0);
		*query++ = '\0';

		// increment by strlen(service_name) + 1 + 7 + sizeof_dns_answer + sizeof_mdns_service
		
		*query++ = 0xc0 + (hostname_offset >> 8);
		*query++ = hostname_offset & 0xff;

		ans.type = htons(DNS_RRTYPE_A);
		ans.class = htons(dns_class);
		ans.ttl = htonl(min(max_ttl, 300));
		ans.len = htons(DNS_IP_ADDR_LEN);

		MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);

		/* resize the query */
		query = query + SIZEOF_DNS_ANSWER;
		
		// increment by strlen(service_name) + 1 + 7 + sizeof_dns_answer 
		

		/* fill the payload of the mDNS message */
		/* set the local IP address */
		a_rr.src = 0;
		MEMCPY( query, &a_rr, SIZEOF_MDNS_A_RR);
		u8_t *addr_ptr = query + ((char *) &a_rr.src - (char *) &a_rr);
		/* resize the query */
		query = query + SIZEOF_MDNS_A_RR;

		// Append the NSEC record that says we only have an A record
		*query++ = 0xc0 + (hostname_offset >> 8);
		*query++ = hostname_offset & 0xff;

		ans.type = htons(DNS_RRTYPE_NSEC);
		ans.class = htons(dns_class);
		ans.ttl = htonl(min(max_ttl, 300));
		ans.len = htons(5);

		MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);
		query = query + SIZEOF_DNS_ANSWER;
		*query++ = 0xc0 + (hostname_offset >> 8);
		*query++ = hostname_offset & 0xff;
		*query++ = 0;
		*query++ = 1;
		*query++ = 0x40;


		//MDNS_DBG("Final ptr=%x\n", query);

		// increment by sizeof_mdns_a_rr

		/* set the name of the authority field.
		 * The same name as the Query using the offset address*/

		/* resize pbuf to the exact dns query */
		pbuf_realloc(p, (query) - ((char*) (p->payload)));

		err = send_packet(p, dst_addr, dst_port, addr_ptr);

		if (!dst_addr) {
		  // this is being sent multicast...
		  // so reset the timer
		  os_timer_disarm(&mdns_timer);
		  os_timer_arm(&mdns_timer, 1000 * 280, 1);
		}
	} else {
		MDNS_DBG("ERR_MEM \n");
		err = ERR_MEM;
	}

	return err;
}

static char *append_nsec_record(char *query, u32_t actual_rr, int max_ttl) {
  struct mdns_answer ans;

  ans.type = htons(DNS_RRTYPE_NSEC);
  ans.class = htons(DNS_RRCLASS_IN);
  ans.ttl = htonl(min(max_ttl, 300));
  ans.len = htons(9);

  MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);
  char *rr_len = query + ((char *) &ans.len - (char *) &ans) + 1;
  query = query + SIZEOF_DNS_ANSWER;
  *query++ = 0xc0;
  *query++ = sizeof(struct mdns_hdr);
  *query++ = 0;
  char *bm_len = query;
  *query++ = 5;
  char *abase = query;
  *query++ = 0;
  *query++ = 0;
  *query++ = 0;
  *query++ = 0;
  *query++ = 0;

  while (actual_rr > 0) {
    int v = actual_rr & 255;

    if (v < 5 * 8) {
      abase[v >> 3] |= 0x80 >> (v & 7);

      actual_rr = actual_rr >> 8;
    }
  }

  while (query[-1] == 0) {
    query--;
    (*bm_len)--;
    (*rr_len)--;
  }

  return query;
}

/**
 * This sends an empty response -- this is used when we doin't have an RR to send
 * but the name exists
 */

static void 
mdns_send_no_rr(struct mdns_hdr *req, const char *name, u32_t actual_rr, struct ip_addr *dst_addr, u16_t dst_port) {
  int max_ttl = dst_addr ? 10 : 7200;
  struct pbuf *p;
  p = pbuf_alloc(PBUF_TRANSPORT,
		  SIZEOF_DNS_HDR + MDNS_MAX_NAME_LENGTH * 2 + SIZEOF_DNS_QUERY, PBUF_RAM);
  if (p != NULL) {
    LWIP_ASSERT("pbuf must be in one piece", p->next == NULL);
    /* fill dns header */
    struct mdns_hdr *hdr = (struct mdns_hdr*) p->payload;
    os_memset(hdr, 0, SIZEOF_DNS_HDR);
    hdr->id = req->id;
    hdr->flags1 = DNS_FLAG1_RESPONSE;
    hdr->numextrarr = htons(1);
    char *query = (char*) hdr + SIZEOF_DNS_HDR;
    char *query_end = (char *) p->payload + p->tot_len;
    // Now copy over the dns name 
    int len = strlen(name);

    if (query_end - query >= len + SIZEOF_DNS_QUERY + 15) {
      query = copy_and_encode_name((char *) (hdr + 1), name);

      query = append_nsec_record(query, actual_rr, max_ttl);

      // Set the length code correctly
      pbuf_realloc(p, query - ((char*) (p->payload)));

      send_packet(p, dst_addr, dst_port, NULL);
    }
  }
}

/**
 * This sends a single A record and the NSEC record as additional
 */

static void 
mdns_send_a_rr(struct mdns_hdr *req, const char *name, struct ip_addr *dst_addr, u16_t dst_port) {
  int max_ttl = dst_addr ? 10 : 7200;
  struct pbuf *p;
  p = pbuf_alloc(PBUF_TRANSPORT,
		  SIZEOF_DNS_HDR + MDNS_MAX_NAME_LENGTH * 2 + SIZEOF_DNS_QUERY, PBUF_RAM);
  if (p != NULL) {
    LWIP_ASSERT("pbuf must be in one piece", p->next == NULL);
    /* fill dns header */
    struct mdns_hdr *hdr = (struct mdns_hdr*) p->payload;
    os_memset(hdr, 0, SIZEOF_DNS_HDR);
    hdr->id = req->id;
    hdr->flags1 = DNS_FLAG1_RESPONSE;
    hdr->numanswers = htons(1);
    hdr->numextrarr = htons(1);
    char *query = (char*) hdr + SIZEOF_DNS_HDR;
    char *query_end = (char *) p->payload + p->tot_len;
    // Now copy over the dns name 
    int len = strlen(name) + 1;

    if (query_end - query >= len + SIZEOF_DNS_QUERY + 4 + 2 + 4 + 15) {
      query = copy_and_encode_name((char *) (hdr + 1), name);

      struct mdns_answer ans;

      ans.type = htons(DNS_RRTYPE_A);
      ans.class = htons(DNS_RRCLASS_IN);
      ans.ttl = htonl(min(max_ttl, 300));
      ans.len = htons(4);

      MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);
      query = query + SIZEOF_DNS_ANSWER;
      char *addr_ptr = query;
      query += 4;

      // Now add the NSEC record
      *query++ = 0xc0;
      *query++ = sizeof(*hdr);
      query = append_nsec_record(query, DNS_RRTYPE_A, max_ttl);

      // Set the length code correctly
      pbuf_realloc(p, query - ((char*) (p->payload)));

      send_packet(p, dst_addr, dst_port, addr_ptr);
    }
  }
}

/**
 * Receive input function for DNS response packets arriving for the dns UDP pcb.
 *
 * @params see udp.h
 */
static void ICACHE_FLASH_ATTR
mdns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr,
		u16_t port) {
	u16_t i;
	struct mdns_hdr *hdr;
	u8_t nquestions;
	LWIP_UNUSED_ARG(arg);
	LWIP_UNUSED_ARG(pcb);
	struct nodemcu_mdns_info *info = (struct nodemcu_mdns_info *)arg;
	/* is the dns message too big ? */
	if (p->tot_len > DNS_MSG_SIZE) {
		LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: pbuf too big\n"));
		/* free pbuf and return */
		goto memerr1;
	}

	/* is the dns message big enough ? */
	if (p->tot_len < (SIZEOF_DNS_HDR + SIZEOF_DNS_QUERY + SIZEOF_DNS_ANSWER)) {
		LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: pbuf too small\n"));
		/* free pbuf and return */
		goto memerr1;
	}
	/* copy dns payload inside static buffer for processing */
	if (pbuf_copy_partial(p, mdns_payload, p->tot_len, 0) == p->tot_len) {
		/* The ID in the DNS header should be our entry into the name table. */
		hdr = (struct mdns_hdr*) mdns_payload;

		i = htons(hdr->id);

		nquestions = htons(hdr->numquestions);
		//nanswers   = htons(hdr->numanswers);
		/* if we have a question send an answer if necessary */
		u8_t qno;
		u8_t *qptr = (u8_t *) (hdr + 1);
		u8_t *qend = mdns_payload + p->tot_len;
		for (qno = 0; qno < nquestions && qptr < qend; qno++) {
		  char tmpBuf[PUCK_DATASHEET_SIZE + PUCK_SERVICE_LENGTH];
		  struct mdns_query qry;

		  int namelen = mdns_namelen(qptr, qend - qptr);

		  memcpy(&qry, namelen + qptr, sizeof(qry));

		  u16_t qry_type = ntohs(qry.type);

		  if (port == 5353 && (ntohs(qry.class) & 0x8000) == 0) {
		    addr = NULL;
		  }

		  u32_t actual_rr = 0;

		  const char *no_rr_name = NULL;

		  /* MDNS_DS_DOES_NAME_CHECK */
		  /* Check if the name in the "question" part match with the name of the MDNS DS service. */
		  if (mdns_compare_name((unsigned char *) DNS_SD_SERVICE,
				  (unsigned char *) qptr, (unsigned char *) hdr) == 0) {
		    if (qry_type == DNS_RRTYPE_PTR || qry_type == DNS_RRTYPE_ANY) {
		      mdns_send_service_type(i, addr, port);
		    } else {
		      no_rr_name = DNS_SD_SERVICE;
		      actual_rr = DNS_RRTYPE_PTR;
		    }
		  } else if (mdns_compare_name((unsigned char *) service_name_with_suffix,
				  (unsigned char *) qptr, (unsigned char *) hdr) == 0) {
		    if (qry_type == DNS_RRTYPE_PTR || qry_type == DNS_RRTYPE_ANY) {
		      mdns_send_service(info, i, addr, port);
		    } else {
		      no_rr_name = service_name_with_suffix;
		      actual_rr = DNS_RRTYPE_PTR;
		    }
		  } else {
		    c_strlcpy(tmpBuf,ms_info->host_name, sizeof(tmpBuf));
		    c_strlcat(tmpBuf, ".", sizeof(tmpBuf));
		    c_strlcat(tmpBuf, MDNS_LOCAL, sizeof(tmpBuf));
		    no_rr_name = tmpBuf;

		    if (mdns_compare_name((unsigned char *) tmpBuf,
				  (unsigned char *) qptr, (unsigned char *) hdr) == 0) {
		      if (qry_type == DNS_RRTYPE_A || qry_type == DNS_RRTYPE_ANY) {
			mdns_send_a_rr(hdr, tmpBuf, addr, port);
		      } else {
			actual_rr = DNS_RRTYPE_A;
		      }
		    } else {
		      c_strlcpy(tmpBuf,ms_info->host_desc, sizeof(tmpBuf));
		      c_strlcat(tmpBuf, ".", sizeof(tmpBuf));
		      c_strlcat(tmpBuf, service_name_with_suffix, sizeof(tmpBuf));
		      if (mdns_compare_name((unsigned char *) tmpBuf,
				  (unsigned char *) qptr, (unsigned char *) hdr) == 0) {
			if (qry_type == DNS_RRTYPE_TXT || qry_type == DNS_RRTYPE_SRV || qry_type == DNS_RRTYPE_ANY) {
			  mdns_send_service(info, i, addr, port);
			} else {
			  actual_rr = (DNS_RRTYPE_TXT << 8) + DNS_RRTYPE_SRV;
			}
		      }
		    }
		  }

		  if (actual_rr) {
		    mdns_send_no_rr(hdr, no_rr_name, actual_rr, addr, port);
		  }

		  qptr += namelen + sizeof(qry);		// Now points to next question
		}
	}
memerr1:
	/* free pbuf */
	pbuf_free(p);
	return;
}

static void
mdns_free_info(struct nodemcu_mdns_info *info) {
  os_free((void *) info);
}

/**
 * close the UDP pcb .
 */
void ICACHE_FLASH_ATTR
nodemcu_mdns_close(void)
{
  os_timer_disarm(&mdns_timer);

  if (mdns_pcb != NULL) {
    udp_remove(mdns_pcb);
  }
  if (mdns_payload) {
    os_free(mdns_payload);
  }
  mdns_payload = NULL;
  mdns_pcb = NULL;
  mdns_free_info(ms_info);
  ms_info = NULL;
}

static void ICACHE_FLASH_ATTR
mdns_set_servicename(const char *name) {
	char tmpBuf[128];
	os_sprintf(tmpBuf, "_%s._tcp.local", name);
	if (service_name_with_suffix) {
	  os_free(service_name_with_suffix);
	}
	service_name_with_suffix = c_strdup(tmpBuf);
}

static u8_t reg_counter;

static void
mdns_reg_handler_restart(void) {
  reg_counter = 99;
}

static void ICACHE_FLASH_ATTR
mdns_reg(struct nodemcu_mdns_info *info) {
  mdns_send_service(info,0,0,0);
  if (reg_counter++ > 10) {
    mdns_send_service_type(0,0,0);
    reg_counter = 0;
  }
}

static struct nodemcu_mdns_info *
mdns_dup_info(const struct nodemcu_mdns_info *info) {
  struct nodemcu_mdns_info *result;

  // calculate length
  int len = sizeof(struct nodemcu_mdns_info);

  len += c_strlen(info->host_name) + 1;
  len += c_strlen(info->host_desc) + 1;
  len += c_strlen(info->service_name) + 1;
  int i;
  for (i = 0; i < sizeof(info->txt_data) / sizeof(info->txt_data[0]) && info->txt_data[i]; i++) {
    len += c_strlen(info->txt_data[i]) + 1;
  }

#define COPY_OVER(dest, src, p)  len = c_strlen(src) + 1; memcpy(p, src, len); dest = p; p += len

  result = (struct nodemcu_mdns_info *) os_zalloc(len);
  if (result) {
    char *p = (char *) (result + 1);
    result->service_port = info->service_port;
    COPY_OVER(result->host_name, info->host_name, p);
    COPY_OVER(result->host_desc, info->host_desc, p);
    COPY_OVER(result->service_name, info->service_name, p);
    for (i = 0; i < sizeof(info->txt_data) / sizeof(info->txt_data[0]) && info->txt_data[i]; i++) {
      COPY_OVER(result->txt_data[i], info->txt_data[i], p);
    }
  }

#undef COPY_OVER

  return result;
}

/**
 * Initialize the resolver: set up the UDP pcb and configure the default server
 * (NEW IP).
 * 
 * returns TRUE if it worked, FALSE if it failed.
 */
bool ICACHE_FLASH_ATTR
nodemcu_mdns_init(struct nodemcu_mdns_info *info) {
  /* initialize default DNS server address */
  multicast_addr.addr = DNS_MULTICAST_ADDRESS;
  struct ip_info ipconfig;
  mdns_free_info(ms_info);
  ms_info = mdns_dup_info(info);		// Save the passed block. We need all the data forever

  if (!ms_info) {
    return FALSE;
  }

  if (mdns_payload) {
    os_free(mdns_payload);
  }
  mdns_payload = (u8_t *) os_malloc(DNS_MSG_SIZE);
  if (!mdns_payload) {
    MDNS_DBG("Alloc fail\n");
    return FALSE;
  }

  LWIP_DEBUGF(DNS_DEBUG, ("dns_init: initializing\n"));

  mdns_set_servicename(ms_info->service_name);

  // get the host name as instrumentName_serialNumber for MDNS
  // set the name of the service, the same as host name
  MDNS_DBG("host_name = %s\n", ms_info->host_name);
  MDNS_DBG("server_name = %s\n", service_name_with_suffix);

  /* initialize mDNS */
  mdns_pcb = udp_new();

  if (!mdns_pcb) {
    return FALSE;
  }
  /* join to the multicast address 224.0.0.251 */
  if(wifi_get_opmode() & 0x01) {
    struct netif *sta_netif = (struct netif *)eagle_lwip_getif(0x00);
    if (sta_netif && sta_netif->ip_addr.addr && igmp_joingroup(&sta_netif->ip_addr, &multicast_addr) != ERR_OK) {
      MDNS_DBG("sta udp_join_multigrup failed!\n");
      return FALSE;
    };
  }
  if(wifi_get_opmode() & 0x02) {
    struct netif *ap_netif = (struct netif *)eagle_lwip_getif(0x01);
    if (ap_netif && ap_netif->ip_addr.addr && igmp_joingroup(&ap_netif->ip_addr, &multicast_addr) != ERR_OK) {
      MDNS_DBG("ap udp_join_multigrup failed!\n");
      return FALSE;
    };
  }
  register_flag = 1;
  /* join to any IP address at the port 5353 */
  if (udp_bind(mdns_pcb, IP_ADDR_ANY, DNS_MDNS_PORT) != ERR_OK) {
	  MDNS_DBG("udp_bind failed!\n");
	  return FALSE;
  };

  /*loopback function for the multicast(224.0.0.251) messages received at port 5353*/
  udp_recv(mdns_pcb, mdns_recv, ms_info);
  mdns_flag = 1;
  /*
   * Register the name of the instrument
   */

  //MDNS_DBG("About to start timer\n");
  os_timer_disarm(&mdns_timer);
  os_timer_setfn(&mdns_timer, (os_timer_func_t *)mdns_reg,ms_info);
  os_timer_arm(&mdns_timer, 1000 * 280, 1);
  /* kick off the first one right away */
  mdns_reg_handler_restart();
  mdns_reg(ms_info);

  return TRUE;
}

#endif /* LWIP_MDNS */
