
/* 
 * Argyll Color Correction System
 * ChromCast support
 *
 * Author: Graeme W. Gill
 * Date:   28/8/2014
 *
 * Copyright 2014 Graeme W. Gill
 * All rights reserved.
 *
 * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
 * see the License2.txt file for licencing details.
 *
 */

/*
 * This class provides simple access to the Google ChromeCast
 * for the purposes of generating Video Test patches.
 */

/*
	TTBD:	Ideally we should use the system mDNS resource if it is available,
	and fall back to our mDNS code if not.

	ie. Linux may have nothing, be using avahi-daemon,
	            possibly Zeroconf (== Bonjour ?)
	  Avahi has Bonjour compatibility library ??

	ie. OS X is using mDNSRespo (Bonjour ?)

	MSWin may have Bonjour installed on it ?
	 Has dns-sd command - what's that using (SSDP/UPnP ?)

	  From <http://stackoverflow.com/questions/8659638/how-does-windows-know-how-to-resolve-mdns-queries>

		Bonjour for Windows allows any software using the standard name resolution APIs
		to resolve mDNS names; it does so by registering a DLL (mdnsnsp.dll) as a
		namespace provider using WSCInstallNameSpace.

		The corresponding code is included in the mDNSResponder source (in particular,
		look at the mdnsNSP and NSPTool components).

	To test on Linux:

	  To check what's listening: sudo netstat -anp | grep 5353

	  /etc/init.d/avahi-daemon stop
	  /etc/init.d/avahi-daemon start

	To test on OS X:

	  To check what's listening: sudo netstat -an | grep 5353

	  launchctl unload -w /System/Library/LaunchDaemons/com.apple.mDNSResponder.plist
	  launchctl load -w /System/Library/LaunchDaemons/com.apple.mDNSResponder.plist
 */


#include <time.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <stdarg.h>
#include <math.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include "copyright.h"
#include "aconfig.h"
#include <sys/types.h>
#ifndef SALONEINSTLIB
#include "numlib.h"
#else
#include "numsup.h"
#endif
#include "conv.h"
#include "ccmdns.h"

#undef DEBUG

#if defined(NT) // Windows specific
# if _WIN32_WINNT < 0x0400
#  undef _WIN32_WINNT
#  define _WIN32_WINNT 0x0400 // To make it link in VS2005
# endif
# define WIN32_LEAN_AND_MEAN
# include <winsock2.h>
# include <windows.h>
# include <ws2tcpip.h>

# include <process.h>
# include <direct.h>
# include <io.h>

# define ERRNO GetLastError()
# define UDP_SOCKET_TIMEOUT WSAETIMEDOUT

#else /* !NT = Unix, OS X */

# include <errno.h>
# include <sys/types.h>
# include <sys/wait.h>
# include <sys/socket.h>
# include <sys/select.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <sys/time.h>
# include <stdint.h>
# include <inttypes.h>
# include <netdb.h>

# include <pwd.h>
# include <unistd.h>
# include <dirent.h>

typedef int SOCKET;
# define ERRNO errno

# define INVALID_SOCKET -1
# define SOCKET_ERROR -1
# define UDP_SOCKET_TIMEOUT EAGAIN

#define closesocket(xxx) close(xxx);

#endif

#ifdef DEBUG
# define DBG(xxx) a1logd xxx ;
#else
# define DBG(xxx) ;
#endif  /* DEBUG */

/* ================================================================ */

/* mDNS details */
#define DESTINATION_MCAST "224.0.0.251"
#define DESTINATION_MCASTV6 "FF02::FB"
#define SOURCE_PORT 5353 
#define DESTINATION_PORT 5353 

#define BUFSIZE 1024

/* Various DNS defines */
#define DNS_CLASS_IN 0x0001

// Used by ChromeCast
#define DNS_TYPE_PTR  12	/* Domain name pointer				*/
#define DNS_TYPE_TXT  16	/* Text String						*/
#define DNS_TYPE_SRV  33	/* Server selection (SRV) record	*/
#define DNS_TYPE_A	   1	/* Host address (A) record			*/
#define DNS_TYPE_AAAA 28	/* IPv6 address ???					*/
#define DNS_TYPE_NSEC 47	/* DNS Next Secure Name				*/


#ifdef NEVER
/* Print out a V6 address in zero compresed form */
/* (It would be nice to add zero compression) */
static char *print_IPV6_Address(ORD8 *buf) {
	static char sbuf[40], *p = sbuf;
	int i;
	for (i = 0; i < 8; i++) {
		p += sprintf(p, "%x", buf[i * 2 + 1] * 256 + buf[i * 2 + 0]);
		if (i < 7)
		*p++ = ':';
	}
	*p++ = '\000';
	return sbuf;
}
#endif

/* Write a DNS string to a buffer. */
/* return the offset of the next byte after the string */
static int write_string(ORD8 *buf, int off, char *s) {
	int len = strlen(s);
	if (len >= 0xc0)
		len = 0xc0;	// Hmm.
	buf[off] = len;
	off++;
	memcpy(buf + off, s, len);
	off += len;
	return off;
}

/* Return NZ on error */
static int init_mDNS() {

#ifdef NT
	WSADATA data;
	if (WSAStartup(MAKEWORD(2,2), &data)) {
		DBG((g_log,0,"WSAStartup failed\n"))
		return 1;
	}
#endif

	return 0;
}

/* Setup the send socket */
/* Return nz on error */
static int init_send_mDNS(SOCKET *psock) {
	int nRet, nOptVal; 
	int off;
	SOCKET sock; 
	struct sockaddr_in stSourceAddr; 

	DBG((g_log,0,"init_send_mDNS() called\n")) 

	/* get a datagram (UDP) socket */ 
	sock = socket(PF_INET, SOCK_DGRAM, 0); 
	if (sock == INVALID_SOCKET) { 
		DBG((g_log,0,"opening send UDP socked failed with %d\n",ERRNO)) 
		return 1;
	} 

	/*----------------------- to send --------------------------- */

	/* Theoretically, you do not need any special preparation to 
	 * send to a multicast address. However, you may want a few
	 * things to overcome the limits of the default behavior 
	 */

	// If we're doing a one-shot, we shouldn't transmit from port 5353, */
	// but ChromCast won't see the packet if we don't. */

	/* We can't send from port 5353 if someone else is using it, */
	/* so set the SO_REUSEADDR option (which is enough for MSWin), */
	/* and SO_REUSEPORT for OS X and Linux */
	{
		int on = 1;
		if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on))) {
			DBG((g_log,0,"setsockopt(SO_REUSEADDR)  failed with %d\n",ERRNO))
			closesocket(sock);
			return 1;
		}

		/* Need this to be able to open port on Unix like systems */
#ifndef NT
# ifndef SO_REUSEPORT
#  ifdef __APPLE__
#   define SO_REUSEPORT 0x0200 
#  else	/* Linux */
#   define SO_REUSEPORT 15 
#  endif
# endif
		if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const char *)&on, sizeof(on))) {
			DBG((g_log,0,"setsockopt(SO_REUSEPORT)  failed with %d\n",ERRNO))
		}
#endif
	}

	/* init source address structure */ 
	stSourceAddr.sin_family = PF_INET; 
	stSourceAddr.sin_port = htons(SOURCE_PORT); 
	stSourceAddr.sin_addr.s_addr = INADDR_ANY; 

	/* 
	 * Calling bind() is not required, but some implementations need it 
	 * before you can reference any multicast socket options
	 * and in this case we must be on port 5353 for ChromeCast to see the packet
	 */ 
	nRet = bind(sock, (struct sockaddr *)&stSourceAddr, 
				 sizeof(struct sockaddr)); 
	if (nRet == SOCKET_ERROR) { 
		DBG((g_log,0,"bind failed with %d\n",ERRNO))
		closesocket(sock);
		return 1;
	} 

	/* disable loopback of multicast datagrams we send, since the 
	 * default--according to Steve Deering--is to loopback all
	 * datagrams sent on any interface which is a member of the
	 * destination group address of that datagram.
	 */ 
	nOptVal = 0; 
	nRet = setsockopt (sock, IPPROTO_IP, IP_MULTICAST_LOOP, 
				(char *)&nOptVal, sizeof(int)); 
	if (nRet == SOCKET_ERROR) { 
		/* rather than notifying the user, we make note that this option 
		 * failed. Some WinSocks don't support this option, and default
		 * with loopback disabled), so this failure is of no consequence.
		 * However, if we *do* get loop-backed data, we'll know why
		 */ 
		DBG((g_log,0,"[disabling loopback failed with %d]\n",ERRNO)) 
	} 

#ifdef NEVER		// We only want this to be local
	/* increase the IP TTL from the default of one to 64, so our
	 * multicast datagrams can get off of the local network 
	 */
	nOptVal = 64; 
	nRet = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL,
				 (char *)&nOptVal, sizeof(int)); 
	if (nRet == SOCKET_ERROR) { 
		DBG((g_log,0,"increasing TTL failed with %d\n",ERRNO))
		closesocket(sock);
		return 1;
	} 
#endif

	if (psock != NULL)
		*psock = sock;

	DBG((g_log,0,"init sending mDNS succeed\n",ERRNO))

	return 0;
}

/* Send an mDNS quesry */
/* on some platforms if we try to transmit & recieve at the same time */
/* Return nz on error */
static int send_mDNS(SOCKET sock) {
	int nRet; 
	int off;
	ORD8 achOutBuf[BUFSIZE]; 
	struct sockaddr_in stDestAddr; 

	DBG((g_log,0,"send_mDNS() called\n")) 

	/* Initialize the Destination Address structure and send */ 
	stDestAddr.sin_family = PF_INET; 
	stDestAddr.sin_addr.s_addr = inet_addr(DESTINATION_MCAST);
	stDestAddr.sin_port = htons(DESTINATION_PORT); 

	/* Setup the FQDN data packet to send */
	write_ORD16_be(achOutBuf + 0, 0);	/* ID */
	write_ORD16_be(achOutBuf + 2, 0);	/* Flags */
	write_ORD16_be(achOutBuf + 4, 1);	/* QDCOUNT - one question */
	write_ORD16_be(achOutBuf + 6, 0);	/* ANCOUNT */
	write_ORD16_be(achOutBuf + 8, 0);	/* NSCOUNT */
	write_ORD16_be(achOutBuf + 10, 0);	/* ARCOUNT */
	off = 12;
	off = write_string(achOutBuf, off, "_googlecast");
	off = write_string(achOutBuf, off, "_tcp");
	off = write_string(achOutBuf, off, "local");
	write_ORD8(achOutBuf + off, 0);			/* null string */
	off += 1;
	write_ORD16_be(achOutBuf + off, 0x000c);	/* QCOUNT */
	off += 2;
	/* Set top bit to get a unicast response (not for ChromeCast though) */
	write_ORD16_be(achOutBuf + off, 0x0001);	/* QCLASS */
	off += 2;

	nRet = sendto(sock, (char *)achOutBuf, off,
					0, 
					(struct sockaddr *) &stDestAddr, 
					sizeof(struct sockaddr)); 
	if (nRet == SOCKET_ERROR) { 
		DBG((g_log,0,"sending mDNS query failed with %d\n",ERRNO))
		return 1;
	} 

	DBG((g_log,0,"sending mDNS query succeed\n",ERRNO))

	return 0;
}

static int parse_dns(char **name, char **ip, ORD8 *buf, int size);

/* Free up what get_ccids returned */
void free_ccids(ccast_id **ids) {
	if (ids != NULL) {
		int i;

		for (i = 0; ids[i] != NULL; i++) {
			free(ids[i]->name);
			free(ids[i]->ip);
			free(ids[i]);
		}
		free(ids);
	}
}

/* Spend the given time waiting for replies. */
/* (Note than on MSWin this will be a minimum of 500msec) */
/* Add any ChromeCast replies to the list. */
/* return nz on error */
static int init_receive_mDNS(SOCKET *psock) {
	int nRet; 
	int off, size;
	struct sockaddr_in stSourceAddr; 
	struct ip_mreq stIpMreq; 
	SOCKET sock;

	DBG((g_log,0,"init_receive_mDNS() called\n"))

	/* get a datagram (UDP) socket */ 
	sock = socket(PF_INET, SOCK_DGRAM, 0); 
	if (sock == INVALID_SOCKET) { 
		DBG((g_log,0,"opening receive UDP socked failed with %d\n",ERRNO)) 
		return 1;
	} 

	/* We can't receive from port 5353 if someone else is using it, */
	/* so set the SO_REUSEADDR option (which is enough for MSWin), */
	/* and SO_REUSEPORT for OS X and Linux */
	{
		int on = 1, off = 0;
		if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *) &on, sizeof(on))) {
			DBG((g_log,0,"setsockopt(SO_REUSEADDR)  failed with %d\n",ERRNO))
			closesocket(sock);
			return 1;
		}

		/* Need this to be able to open port on Unix like systems */
#ifndef NT
# ifndef SO_REUSEPORT
#  ifdef __APPLE__
#   define SO_REUSEPORT 0x0200 
#  else	/* Linux */
#   define SO_REUSEPORT 15 
#  endif
# endif
		if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const char *)&on, sizeof(on))) {
			DBG((g_log,0,"setsockopt(SO_REUSEPORT)  failed with %d\n",ERRNO))
		}
#endif
	}

	/* init source address structure */ 
	stSourceAddr.sin_family = PF_INET; 
	stSourceAddr.sin_port = htons(SOURCE_PORT); 
	stSourceAddr.sin_addr.s_addr = INADDR_ANY; 

	/* 
	 * Calling bind() is not required, but some implementations need it 
	 * before you can reference any multicast socket options
	 * and in this case we must be on port 5353.
	 */ 
	nRet = bind(sock, (struct sockaddr *)&stSourceAddr, 
				sizeof(struct sockaddr)); 
	if (nRet == SOCKET_ERROR) { 
		DBG((g_log,0,"bind failed with %d\n",ERRNO))
		closesocket(sock);
		return 1;
	} 

	/* join the multicast group we want to receive datagrams from */ 
	stIpMreq.imr_multiaddr.s_addr = inet_addr(DESTINATION_MCAST); /* group addr */ 
	stIpMreq.imr_interface.s_addr = INADDR_ANY; /* use default */ 
	nRet = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
					(char *)&stIpMreq, sizeof (struct ip_mreq));

	if (nRet == SOCKET_ERROR) { 
		DBG((g_log,0,"registering for read events failed with %d\n",ERRNO))
		closesocket(sock);
		return 1;
	} 

	/* Make this timeout after 100 msec second */
#ifdef NT
	{
		DWORD tv;
		tv = 100;
		if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)) < 0) {
			DBG((g_log,0,"setsockopt timout failed with %d\n",ERRNO))
			closesocket(sock);
			return 1;
		}
	}
#else
	{
		struct timeval tv;
		tv.tv_sec = 0;
		tv.tv_usec = 100 * 1000;
		if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)) < 0) {
			DBG((g_log,0,"setsockopt timout failed with %d\n",ERRNO))
			closesocket(sock);
			return 1;
		}
	}
#endif

	if (psock != NULL)
		*psock = sock;

	return 0;
}

/* Spend the given time waiting for replies. */
/* (Note than on MSWin this will be a minimum of 500msec) */
/* Add any ChromeCast replies to the list. */
/* return nz on error & free *ids */
static int receive_mDNS(SOCKET sock, ccast_id ***ids, int emsec) {
	int nids = 0;
	unsigned int smsec;
	unsigned int nSize;
	int off, size;
	ORD8 achInBuf[BUFSIZE]; 
	struct sockaddr_in stSourceAddr; 

	/* Count the number of current id's */
	if (*ids != NULL) {
		for (nids = 0; (*ids)[nids] != NULL; nids++)
			;
	}

	DBG((g_log,0,"receive_mDNS() called with %d ids\n",nids))

	for (smsec = msec_time(), emsec += smsec;msec_time() <= emsec;) {
		int i;
		char *name, *ip;
		struct sockaddr stSockAddr; 

		/* Recv the available data */ 
		nSize = sizeof(struct sockaddr); 
		size = recvfrom(sock, (char *)achInBuf, 
						BUFSIZE, 0, (struct sockaddr *) &stSockAddr, &nSize); 
		if (size == SOCKET_ERROR) { 
			if (ERRNO == UDP_SOCKET_TIMEOUT)
				continue;			/* Timeout */
			DBG((g_log,0,"recvfrom failed with %d\n",ERRNO))
			free_ccids(*ids);
			*ids = NULL;
			return 1;
		}
		DBG((g_log,0,"Got mDNS message length %d bytes\n",size))
#ifdef DEBUG
		adump_bytes(g_log, "    ", achInBuf, 0, size);
#endif

		if (parse_dns(&name, &ip, achInBuf, size) != 0) {
			DBG((g_log,0,"Failed to parse the reply\n"))
		} else {
			DBG((g_log,0,"Parsed reply OK\n"))

//if (*pnids > 0) {
//	name = strdup("Argyll1234");
//	ip = strdup("10.0.0.129");
//}
			/* If we found an entry */
			if (name != NULL && ip != NULL) {
				DBG((g_log,0,"Got a name '%s' & IP '%s'\n",name,ip))
				/* Check if it is a duplcate */
				for (i = 0; i < nids; i++) {
					if (strcmp((*ids)[i]->name, name) == 0
					 && strcmp((*ids)[i]->ip, ip) == 0)
						break;	/* yes */
				} 
				if (i < nids) {
					DBG((g_log,0,"Duplicate\n"))
					free(name);
					free(ip);
				} else {
					ccast_id **tids;
					if ((tids = realloc(*ids, (nids + 2) * sizeof(ccast_id *))) == NULL
					 || (*ids = tids, (*ids)[nids] = malloc(sizeof(ccast_id)), (*ids)[nids]) == NULL) {
						DBG((g_log,0,"realloc/malloc fail\n"))
						free(name);
						free(ip);
						free_ccids(*ids);
						*ids = NULL;
						return 1;
					} else {
						DBG((g_log,0,"Adding entry\n"))
						(*ids)[nids]->name = name;
						(*ids)[nids]->ip = ip;
						(*ids)[++nids] = NULL;		/* End marker */
					}
				}
			}
		}
	}

	DBG((g_log,0,"receive_mDNS() returning %d in list\n",nids))

	return 0;
}

/* ==================================================================== */

/* Get a list of Chromecasts. Return NULL on error */
/* Last pointer in array is NULL */
/* Takes 0.5 second to return */
ccast_id **get_ccids() {
	ccast_id **ids = NULL;
	int i, j;
	unsigned int smsec;
	SOCKET ssock, rsock;

	if (init_mDNS()) {
		DBG((g_log,0,"init_mDNS() failed\n"))
		return NULL;
	}

	if (init_send_mDNS(&ssock)) {
		DBG((g_log,0,"init_send_mDNS() failed\n"))
		return NULL;
	}

	if (init_receive_mDNS(&rsock)) {
		DBG((g_log,0,"init_receive_mDNS() failed\n"))
		closesocket(ssock);
		return NULL;
	}

	smsec = msec_time();

	DBG((g_log,0,"Sending mDNS query:\n"))
	if (send_mDNS(ssock)) {
		DBG((g_log,0,"send_mDNS() #1 failed\n"))
		closesocket(ssock);
		closesocket(rsock);
		return NULL;
	}

	if (receive_mDNS(rsock, &ids, 100)) {
		DBG((g_log,0,"receive_mDNS() #1 failed\n"))
		closesocket(ssock);
		closesocket(rsock);
		return NULL;
	}

	if (ids == NULL && (msec_time() - smsec) < 200) {

		DBG((g_log,0,"Sending another mDNS query:\n"))
		if (send_mDNS(ssock)) {
			DBG((g_log,0,"send_mDNS() #2 failed\n"))
			closesocket(ssock);
			closesocket(rsock);
			return NULL;
		}

		if (receive_mDNS(rsock, &ids, 500)) {
			DBG((g_log,0,"receive_mDNS() #2 failed\n"))
			closesocket(ssock);
			closesocket(rsock);
			return NULL;
		}
	}

	if (ids == NULL) {

		DBG((g_log,0,"Sending a final mDNS query:\n"))
		if (send_mDNS(ssock)) {
			DBG((g_log,0,"send_mDNS() #3 failed\n"))
			closesocket(ssock);
			closesocket(rsock);
			return NULL;
		}

		if (receive_mDNS(rsock, &ids, 500)) {
			DBG((g_log,0,"receive_mDNS() #3 failed\n"))
			closesocket(ssock);
			closesocket(rsock);
			return NULL;
		}
	}

	closesocket(ssock);
	closesocket(rsock);

	/* If no ChromCasts found, return an empty list */
	if (ids == NULL) {
		if ((ids = calloc(sizeof(ccast_id *), 1)) == NULL) {
			DBG((g_log,0,"calloc fail\n"))
			return NULL;
		}
	}
	/* Sort the results so that it is stable */
	for (i = 0; ids[i] != NULL && ids[i+1] != NULL; i++) {
		for (j = i+1; ids[j] != NULL; j++) {
			if (strcmp(ids[i]->name, ids[j]->name) > 0) {
				ccast_id *tmp;
				tmp = ids[i];
				ids[i] = ids[j];
				ids[j] = tmp;
			}
		}
	}

	return ids;
}

void ccast_id_copy(ccast_id *dst, ccast_id *src) {
	dst->name = strdup(src->name);
	dst->ip = strdup(src->ip);
}

ccast_id *ccast_id_clone(ccast_id *src) {
	ccast_id *dst;

	if ((dst = calloc(sizeof(ccast_id), 1)) == NULL)
		return dst;

	if (src->name != NULL && (dst->name = strdup(src->name)) == NULL) {
		free(dst);
		return NULL;
	}
	if (src->ip != NULL && (dst->ip = strdup(src->ip)) == NULL) {
		free(dst->name);
		free(dst);
		return NULL;
	}
	return dst;
}

void ccast_id_del(ccast_id *id) {
	free(id->name);
	free(id->ip);
	free(id);
}

/* ==================================================================== */

/* Read a string with a trailing '.' added. */
/* Return the next offset or -1 and *rv = NULL on error */
static int read_string_imp(char **rv, int *slen, ORD8 *buf, int off, int size, int rec) {
	int len;
	int d1 = 0;

//printf("~1 read_string_imp called for off 0x%x rec %d\n",off,rec);

	if (rec > 10)		/* Too many recursions */
		return -1;		/* Error */

	if (off >= size)
		return -1;		/* Error */

	for (;;) {
//printf("~1 top of loop at off 0x%x\n",off);
		len = buf[off];
		if (len == 0xc0) {		/* Is it an offset marker */
			int poff;
//printf("~1 got pointer\n");
			if ((size - off) < 2)
				return -1;

			poff = read_ORD16_be(buf + off); off += 2;
			poff -= 0xc000;

			if (poff < 0 || poff >= size)
				return -1;

			read_string_imp(rv, slen, buf, poff, size, rec+1);
//if (slen != NULL) printf("~1 after recurse, slen = %d, off = 0x%x\n",*slen,off);

			break;				/* we're done */

		} else {
//printf("~1 got string length %d\n",len);
			off++;
			if ((off + len) >= size)
				return -1;

			if (len == 0)
				break;				/* we're done */

			if (rv != NULL) {
				memcpy(*rv, buf + off, len);
//(*rv)[len] = '\000'; printf("Copied string %p = '%s'\n",*rv,*rv);

				*rv += len;
			}
			off += len;

			if (slen != NULL) {
				(*slen) += len;
//printf("~1 slen now = %d\n",*slen);
			}

		}
		d1 = 1;

		if (slen != NULL)
			(*slen)++;

		if (rv != NULL) {
			(*rv)[0] = '.';
			(*rv)++;
		}
	}
//if (slen != NULL) printf("~1 returning slen = %d\n",*slen);
	return off;
}

/* Read a string */
/* Return the next offset or -1 and *rv = NULL on error */
static int read_string(char **rv, ORD8 *buf, int off, int size) {
	int len = 0, toff = off;
	char *trv;

//printf("~1 read_string called for off 0x%x\n",off);

	/* See how long it will be */
	if ((toff = read_string_imp(NULL, &len, buf, off, size, 0)) < 0) {
//printf("~1 read_string_imp length returned error\n");
		return toff;
	}
//printf("~1 read_string_imp got length %d\n",len);

	if (len == 0) {
//printf("~1 Got zero length string\n");
		len++;			/* Room for null string */
	}
	if ((*rv = trv = malloc(len)) == NULL) {
		return -1;
	}
//printf("Malloced %p\n",*rv);
	off = read_string_imp(&trv, NULL, buf, off, size, 0);
	if (off >= 0) {
		(*rv)[len-1] = '\000';
//printf("~1 read string ok: %p = '%s'\n",*rv, *rv);
	}
//else printf("~1 reading string failed\n");
	return off;
}
 
/* Parse an mDNS query */
/* Return updated off value or -1 on error */
int parse_query(ORD8 *buf, int off, int size) {
	char *sv;
	int qtype, qclass;

	DBG((g_log,0,"Parsing query at 0x%x:\n",off))
	if ((off = read_string(&sv, buf, off, size)) < 0)
		return -1;
	DBG((g_log,0," Got string '%s'\n",sv))
	free(sv);

	if ((size - off) < 2)
		return -1;
	qtype = read_ORD16_be(buf + off); off += 2;
	DBG((g_log,0," QTYPE = 0x%04x\n",qtype))

	if ((size - off) < 2)
		return -1;
	qclass = read_ORD16_be(buf + off); off += 2;
	DBG((g_log,0," QCLASS = 0x%04x\n",qtype))

	return off;
}

/* Parse an mDNS reply */
/* Return updated off value or -1 on error */
int parse_reply(char **pname, char **pip, ORD8 *buf, int off, int size) {
	char *sv;
	int rtype, rclass, rdlength;
	unsigned int ttl;

	DBG((g_log,0,"Parsing reply at 0x%x:\n",off))
	if ((off = read_string(&sv, buf, off, size)) < 0)
		return -1;

	DBG((g_log,0," Got string '%s'\n",sv))

	if ((size - off) < 2) {
		free(sv);
		return -1;
	}
	rtype = read_ORD16_be(buf + off); off += 2;
	DBG((g_log,0," RTYPE = %d\n",rtype))

	if ((size - off) < 2) {
		free(sv);
		return -1;
	}
	rclass = read_ORD16_be(buf + off); off += 2;
	DBG((g_log,0," RCLASS = 0x%04x\n",rclass))
	/* rclass top bit is cache flush bit */

	if ((rclass & 0x7fff) != DNS_CLASS_IN) {
		DBG((g_log,0," response has RCLASS != 0x%04x\n",DNS_CLASS_IN))
		free(sv);
		return -1;
	}

	if ((size - off) < 4)
		return -1;
	ttl = read_ORD32_be(buf + off); off += 4;
	DBG((g_log,0," TTL = %u\n",ttl))

	if ((size - off) < 2)
		return -1;
	rdlength = read_ORD16_be(buf + off); off += 2;
	DBG((g_log,0," RDLENGTH = %d\n",rdlength))

	if ((off + rdlength) > size) {
		DBG((g_log,0," response RDLENGTH is longer than remaining buffer (%d)\n",size - off))
		free(sv);
		return -1;
	}
	
	/* Just decode the replies we need */
	if (rtype == DNS_TYPE_TXT) {	/* Check it's a ChromeCast & get its name */
		char *cp;
		if ((cp = strchr(sv, '.')) == NULL) {
			free(sv);
			return -1;
		}
		*cp = '\000';
		if (strcmp(cp+1, "_googlecast._tcp.local") != 0) {
			DBG((g_log,0,"Not a chromecast (got '%s')\n",cp+1))
			free(sv);
			return -1;
		}

		DBG((g_log,0," Chromacast '%s'\n", sv))
		if ((*pname = strdup(sv)) == NULL) {
			DBG((g_log,0,"strdup failed\n"))
			free(sv);
			return -1;
		}
	} else if (rtype == DNS_TYPE_A) {
		/* Should we check name matches ? */
		if ((*pip = malloc(3 * 4 + 3 + 1)) == NULL) {
			DBG((g_log,0,"malloc failed\n"))
			free(*pname);
			free(sv);
		}
		sprintf(*pip, "%d.%d.%d.%d", buf[off], buf[off+1], buf[off+2], buf[off+3]);
		DBG((g_log,0," V4 address = %s\n",*pip))
	
	} else if (rtype == DNS_TYPE_AAAA) {		/* The IPV6 address */
		/* Should we check name matches ? */
		if ((*pip = malloc(8 * 4 + 7 + 1)) == NULL) {
			DBG((g_log,0,"malloc failed\n"))
			free(*pname);
			free(sv);
		}
		sprintf(*pip, "%x:%x:%x:%x:%x:%x:%x:%x",
		buf[off+0] * 245 + buf[off+1],
		buf[off+2] * 245 + buf[off+3],
		buf[off+4] * 245 + buf[off+5],
		buf[off+6] * 245 + buf[off+7],
		buf[off+8] * 245 + buf[off+9],
		buf[off+10] * 245 + buf[off+11],
		buf[off+12] * 245 + buf[off+13],
		buf[off+14] * 245 + buf[off+15]);
		DBG((g_log,0," V6 address = %s\n",*pip))

	} else {
		DBG((g_log,0," Skipping reply at 0x%x\n",off))
	}
	off += rdlength;
	free(sv);

	return off;
}

/* Parse an mDNS reply into a ChromCast name & IP address */
/* Allocate and return name and IP on finding ChromeCast reply, NULL otherwise */
/* Return nz on failure */
static int parse_dns(char **pname, char **pip, ORD8 *buf, int size) {
	int i, off = 0;
	int id, flags, qdcount, ancount, nscount, arcount;

	DBG((g_log,0,"Parsing mDNS reply:\n"))

	*pname = NULL;
	*pip = NULL;

	// Parse reply header
	if ((size - off) < 2) return 1;
	id = read_ORD16_be(buf + off); off += 2;
	DBG((g_log,0," ID = %d\n",id))

	if ((size - off) < 2) return 1;
	flags = read_ORD16_be(buf + off); off += 2;
	DBG((g_log,0," FLAGS = 0x%04x\n",flags))

	if ((size - off) < 2) return 1;
	qdcount = read_ORD16_be(buf + off); off += 2;
	DBG((g_log,0," QDCOUNT = %d\n",qdcount))

	if ((size - off) < 2) return 1;
	ancount = read_ORD16_be(buf + off); off += 2;
	DBG((g_log,0," ANCOUNT = %d\n",ancount))

	if ((size - off) < 2) return 1;
	nscount = read_ORD16_be(buf + off); off += 2;
	DBG((g_log,0," NSCOUNT = %d\n",nscount))

	if ((size - off) < 2) return 1;
	arcount = read_ORD16_be(buf + off); off += 2;
	DBG((g_log,0," ARCOUNT = %d\n",arcount))

//printf("~1 after strings, off = 0x%x\n",off);

	// Parse all the queries (QDCOUNT)
	for (i = 0; i < qdcount; i++) {
		if ((off = parse_query(buf, off, size)) < 0) {
			DBG((g_log,0," ### Parsing query failed ###\n"))
			return 1;
		}
	}

	// Parse all the answers (ANCOUNT)
	for (i = 0; i < ancount; i++) {
		if ((off = parse_reply(pname, pip, buf, off, size)) < 0) {
			DBG((g_log,0," ### Parsing answer failed ###\n"))
			return 1;
		}
	}

	// Parse all the NS records (NSCOUNT)
	for (i = 0; i < nscount; i++) {
		if ((off = parse_reply(pname, pip, buf, off, size)) < 0) {
			DBG((g_log,0," ### Parsing NS record failed ###\n"))
			return 1;
		}
	}

	// Parse all the addition RR answers (ARCOUNT)
	for (i = 0; i < arcount; i++) {
		if ((off = parse_reply(pname, pip, buf, off, size)) < 0) {
			DBG((g_log,0," ### Parsing additional records failed ###\n"))
			return 1;
		}
	}
	return 0;
}

/*
	Analysis of ChromeCast reply

    0000: 00 00  84 00  00 00  00 01  00 00  00 05  0b 5f 67 6f  ............._go

	ID 		0x0000		// ID 0
	Flags	0x8400		// Standard query response, No error
	QDCOUNT 0x0000		// No queries
	ANCOUNT 0x0001		// 1 Answer
	NSCOUNT 0x0000
	ARCOUNT 0x0005		// 5 Additional records

	// Answer
                                              0b 5f 67 6f  _go
    0010: 6f 67 6c 65 63 61 73 74                          oglecast

    0010:                         04 5f 74 63 70           _tcp

    0010: 05 6c 6f  lo
    0020: 63 61 6c 00                                       cal.

	"_googlecast" "_tcp" "cal" ""		//TLD

    0020: 00 0c  00 01  00 00 11 94  00 11  0e 43  ...........C

	RTYPE    0x000c
	RCLASS   0x0001
	TTL      0x00001194 
	RDLENGTH 0x0011

	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    0000: 00 00 84 00 00 00 00 01 00 00 00 05 0b 5f 67 6f  ............._go
    0010: 6f 67 6c 65 63 61 73 74 04 5f 74 63 70 05 6c 6f  oglecast._tcp.lo
    0020: 63 61 6c 00 00 0c 00 01 00 00 11 94 00 11 0e 43  cal............C
    0030: 68 72 6f 6d 65 63 61 73 74 36 38 39 32 c0 0c c0  hromecast6892...
    0040: 2e 00 10 80 01 00 00 11 94 00 67 23 69 64 3d 63  ..........g#id=c
    0050: 31 37 61 65 38 38 32 65 37 65 31 38 37 35 64 30  17ae882e7e1875d0
    0060: 31 33 65 64 32 31 32 63 31 36 62 62 64 34 30 05  13ed212c16bbd40.
    0070: 76 65 3d 30 32 0d 6d 64 3d 43 68 72 6f 6d 65 63  ve=02.md=Chromec
    0080: 61 73 74 12 69 63 3d 2f 73 65 74 75 70 2f 69 63  ast.ic=/setup/ic
    0090: 6f 6e 2e 70 6e 67 11 66 6e 3d 43 68 72 6f 6d 65  on.png.fn=Chrome
    00a0: 63 61 73 74 36 38 39 32 04 63 61 3d 35 04 73 74  cast6892.ca=5.st
    00b0: 3d 30 c0 2e 00 21 80 01 00 00 00 78 00 17 00 00  =0...!.....x....
    00c0: 00 00 1f 49 0e 43 68 72 6f 6d 65 63 61 73 74 36  ...I.Chromecast6
    00d0: 38 39 32 c0 1d c0 c4 00 01 80 01 00 00 00 78 00  892...........x.
    00e0: 04 0a 00 00 80 c0 2e 00 2f 80 01 00 00 11 94 00  ......../.......
    00f0: 09 c0 2e 00 05 00 00 80 00 40 c0 c4 00 2f 80 01  .........@.../..
    0100: 00 00 00 78 00 05 c0 c4 00 01 40                 ...x......@


*/

