
/* 
 * 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		/* [und] */

#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
# pragma message("######### ccmdns.c DEBUG enabled !!!!! ########")
# define DBG(xxx) a1logd xxx ;
# define DBG2(xxx) a1logd xxx ;
# define DLEV 0
# define DLEVP1 0
#else
# define DBG(xxx) ;
# define DBG2(xxx) a1logd xxx ;
# define DLEV 2
# define DLEVP1 3
#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 2048

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

/* DNS query type */
#define DNS_TYPE_A	   1	/* Host IP address record			*/
#define DNS_TYPE_PTR  12	/* Canonical domain name 			*/
#define DNS_TYPE_TXT  16	/* Text String						*/
#define DNS_TYPE_AAAA 28	/* Host IPV6 address record         */
#define DNS_TYPE_SRV  33	/* Server selection (SRV) record	*/
#define DNS_TYPE_NSEC 47	/* DNS Next Secure Name				*/


char *cctype2str(cctype typ) {
	switch (typ) {
		case cctyp_unkn:
			return "Unknown";
		case cctyp_1:
			return "One";
		case cctyp_2:
			return "Two";
		case cctyp_Audio:
			return "Audio";
		case cctyp_Ultra:
			return "Ultra";
		case cctyp_Other:
			return "Other";
		default:
			return "Unexpected";
	}
}

#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. String has max length of 63 */
/* (because values higher than that are used for a compression scheme.) */
/* 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 > 63)
		len = 63;	// Hmm. Or what ?
	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 and recieve socket */
/* Return nz on error */
static int init_socket_mDNS(SOCKET *psock) {
	int nRet, nOptVal; 
	int off;
	SOCKET sock; 
	struct sockaddr_in stSourceAddr; 
	struct ip_mreq stIpMreq;

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

	/* get a datagram (UDP) socket */ 
	sock = socket(PF_INET, SOCK_DGRAM, 0); 
	if (sock == INVALID_SOCKET) { 
		DBG((g_log,0,"opening 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;
	} 

	/* - - - - - - - */
	/*   Send setup  */

	/* 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)) 
	} 

	/* Is this desirable ? */

	/* 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;
	} 

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

	/* - - - - - - - */
	/* Recieve setup */

	/* 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 timeout 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 timeout failed with %d\n",ERRNO))
			closesocket(sock);
			return 1;
		}
	}
#endif

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

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

	return 0;
}

/* Send an mDNS query */
/* On some platforms if we try to transmit & recieve at the same time (what ?) */
/* 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, 0x8000);	/* Flags - query */
	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, DNS_TYPE_PTR);	/* QTYPE */
	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, cctype *ptyp, int *pcaflags, 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 & free *ids */
static int receive_mDNS(SOCKET sock, ccast_id ***ids, int *nids, int emsec) {
	unsigned int smsec;
	unsigned int nSize;
	int off, size;
	ORD8 achInBuf[BUFSIZE]; 
	struct sockaddr_in stSourceAddr; 

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

	/* While there is still wait time */
	for (smsec = msec_time(), emsec += smsec;msec_time() <= emsec;) {
		int i;
		char *name, *ip;
		cctype typ;
		int caflags;					/* (We're not currently saving this) */
		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 */
			DBG2((g_log,DLEV,"recvfrom failed with %d\n",ERRNO))
			free_ccids(*ids);
			*ids = NULL;
			return 1;
		}

		DBG2((g_log,DLEVP1,"Got mDNS message length %d bytes\n",size))
#ifdef DEBUG
		adump_bytes(g_log, "    ", achInBuf, 0, size);
#endif

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

			/* If we found an entry */
			if (name != NULL && ip != NULL) {
				DBG((g_log,0,"Got a name '%s', IP '%s', type %s\n",name,ip, cctype2str(typ)))

				/* Check if it is a Chromecast-Audio or Other */
				if (typ == cctyp_Audio
				 || typ == cctyp_Other) {
					DBG((g_log,0,"Ignoring Chromecast-Audio/Other\n"))
					free(name);
					free(ip);

				} else {
			
					/* 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]->typ = typ;
							(*nids)++;
							(*ids)[*nids] = NULL;		/* End marker */
						}
					}
				}
			}
		}
	}

	DBG2((g_log,DLEVP1,"receive_mDNS() returning %d in list\n",*nids))

	return 0;
}

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

/* Get a list of Video output capable Chromecasts. Return NULL on error */
/* Last pointer in array is NULL */
/* Takes 1.5 second to return */
ccast_id **get_ccids(
) {
	ccast_id **ids = NULL;
	int nids = 0;
	int i, j, k;
	unsigned int smsec;
	int waittime = 200;
	SOCKET sock;

	DBG2((g_log,DLEV,"get_ccids: called\n"))

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

	if (init_socket_mDNS(&sock)) {
		DBG2((g_log,0,"get_ccids: init_socket_mDNS() failed\n"))
		return NULL;
	}


	smsec = msec_time();

	/* Try a few times, with increasing response wait time */
	for (k = 1;
		    ((msec_time() - smsec) < 700)
		 || (nids == 0 && (msec_time() - smsec) < 1600);
	     k++) {

		DBG2((g_log,DLEV,"get_ccids: Sending mDNS query #%d:\n",k))
		if (send_mDNS(sock)) {
			DBG2((g_log,0,"get_ccids: send_mDNS() #1 failed\n"))
			closesocket(sock);
			return NULL;
		}
	
		DBG2((g_log,DLEV,"get_ccids: Waiting for mDNS reply #%d:\n",k))
		if (receive_mDNS(sock, &ids, &nids, waittime)) {
			DBG2((g_log,0,"get_ccids: receive_mDNS() #%d failed\n",k))
			closesocket(sock);
			return NULL;
		}
		DBG2((g_log,DLEV,"get_ccids: have %d %s\n",nids, nids == 1 ? "reply" : "replies"))

		if (waittime < 500)
			waittime = 500;
	}
	closesocket(sock);

	/* If no ChromCasts found, return an empty list */
	if (ids == NULL) {
		DBG2((g_log,DLEV,"get_ccids: no devices found\n"))
		if ((ids = calloc(sizeof(ccast_id *), 1)) == NULL) {
			DBG2((g_log,0,"get_ccids: 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;
			}
		}
	}

	for (i = 0; ids[i] != NULL; i++) {
		DBG2((g_log,DLEV,"  Entry %d:\n",i))
		DBG2((g_log,DLEV,"   Name: %s\n",ids[i]->name))
		DBG2((g_log,DLEV,"   IP:   %s\n",ids[i]->ip))
		DBG2((g_log,DLEV,"   Type: %s\n",cctype2str(ids[i]->typ)))
	}
	DBG2((g_log,DLEV,"get_ccids: Returning %d devices\n",i))

	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;

	DBG((g_log,8,"read_string_imp called for off %d (0x%x) rec %d\n",off,off,rec));

	if (rec > 10) {		/* Too many recursions */
		DBG((g_log,8,"read_string_imp too many recursions\n"));
		return -1;		/* Error */
	}

	if (off >= size) {
		DBG((g_log,8,"read_string_imp off %d >= size %d\n",off,size));
		return -1;		/* Error */
	}

	for (;;) {
		DBG((g_log,8,"top of loop at off %d\n",off));
		len = buf[off];
		if ((len & 0xc0) == 0xc0) {	/* Is it an offset marker */
			int poff;

			DBG((g_log,8,"got pointer\n"));
			if ((size - off) < 2) {
				DBG((g_log,8,"read_string_imp size %d - off %d < 2\n",size,off));
				return -1;
			}

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

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

			DBG((g_log,8,"read_string_imp recursing\n"));
			read_string_imp(rv, slen, buf, poff, size, rec+1);

			if (slen != NULL) {
				DBG((g_log,8,"after recurse, slen = %d, off = %d (0x%x)\n",*slen,off,off));
			}
			break;				/* we're done */

		} else {
			DBG((g_log,8,"got string length %d\n",len));

			off++;
			if ((off + len) >= size) {
				DBG((g_log,8,"read_string_imp size off %d + len %d >= size %d\n",off,len,size));
				return -1;
			}

			if (len == 0) {
				DBG((g_log,8,"read_string_imp len == 0 - done\n"));
				break;				/* we're done */
			}

			if (rv != NULL) {
				memcpy(*rv, buf + off, len);

#ifdef DEBUG
				(*rv)[len] = '\000';
				DBG((g_log,8,"Copied string %p = '%s'\n",*rv,*rv));
#endif

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

			if (slen != NULL) {
				(*slen) += len;
				DBG((g_log,8,"slen now = %d\n",*slen));
			}

		}
		d1 = 1;

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

		if (rv != NULL) {
			(*rv)[0] = '.';
			(*rv)++;
		}
	}
	if (slen != NULL) {
		DBG((g_log,8,"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;

	DBG((g_log,7,"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) {
		DBG((g_log,7,"read_string_imp length returned error\n"));
		return toff;
	}
	DBG((g_log,7,"read_string_imp got length %d\n",len));

	if (len == 0) {
		DBG((g_log,7,"Got zero length string\n"));
		len++;			/* Room for null string */
	}
	if ((*rv = trv = malloc(len)) == NULL) {
		DBG((g_log,7,"malloc for string failed\n"));
		return -1;
	}
	DBG((g_log,7,"loced %p\n",*rv));
	off = read_string_imp(&trv, NULL, buf, off, size, 0);
	if (off >= 0) {
		(*rv)[len-1] = '\000';
		DBG((g_log,7,"read string ok: %p = '%s'\n",*rv, *rv));
	} else {
		DBG((g_log,7,"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, and set Friendly name (if known) + formal name + IP */
/* Return updated off value or -1 on error */
int parse_reply(int rrix, char **pname, char **pip, cctype *ptyp, int *pcaflags, ORD8 *buf, int off, int size) {
	char *sv;
	int rtype, rclass, rdlength;
	unsigned int ttl;

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

	DBG((g_log,0," Got string '%s', now off = 0x%x\n",sv,off))

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

	if ((size - off) < 2) {
		free(sv);
		return -1;
	}
	rclass = read_ORD16_be(buf + off); off += 2;
	DBG((g_log,0," RCLASS = 0x%04x, now off = 0x%x\n",rclass,off))
	/* 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, now off = 0x%x\n",ttl,off))

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

	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, *fn = NULL;
		int rdsize = off + rdlength;
		int caflags = 0;

		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;
		}

#ifdef NEVER
		DBG((g_log,0," TXT data:\n"))
		adump_bytes(g_log, "    ", buf + off, 0, rdlength);
#endif

		/*	Chromecast TXT DATA format:

			Buffer is series of strings, each beginning with a byte length.
			(This is from <https://datatracker.ietf.org/doc/html/rfc6763>
			Each string is id=value, with the following known values:

			id=c17a8e82ee7187d5013e2d12c61bbd40		uuidNoHyphens
			rm=669B9448366A01CB
			ve=05									SW version ????
			md=Chromecast							Model ?
			   										i.e. Chromecast-Audio
			   										i.e. Chromecast-Ultra
			   										i.e. Group
			ic=/setup/icon.png						Icon file ??
			fn=Chromecast6892						Friendly name
			ca=4101									Capabilities bits ???
			st=1									Application running ??
			bs=FA8FCA566645							Application ID ??
			rs=Pattern generator ready				Application state ??

		 */

		/* Parse the Chromacast TX Data */
		for (; off < rdsize; ) {
			int slen;
			char *ss;

			/* Read the string length */
			if ((rdsize - off) < 1)
				goto done_tx;

			slen = read_ORD8(buf + off); off += 1;

			if ((rdsize - off) < slen)
				goto done_tx;
			
			if ((ss = malloc(slen + 1)) == NULL) {
				DBG((g_log,0,"malloc for sub-string failed\n"));
				return -1;
			}

			memcpy(ss, buf + off, slen);
			ss[slen] = '\000';
			off += slen;

			DBG((g_log,0," TX Sub-string '%s'\n", ss))

			/* Record info we want: */
			if (strncmp(ss, "fn=", 3) == 0) {	/* Friendly name */
				if ((fn = malloc(slen -3 +1)) == NULL) {
					DBG((g_log,0,"malloc for fn-string failed\n"));
					return -1;
				}
				strcpy(fn, ss + 3);
			}

			if (strncmp(ss, "ca=", 3) == 0) {	/* Capability bits ? */
				caflags = atoi(ss + 3);
			}
			free(ss);
		}

	  done_tx:

		if (fn != NULL) {
			DBG((g_log,0," Chromacast '%s', fn '%s'\n", sv, fn))
		} else {
			DBG((g_log,0," Chromacast '%s'\n", sv))
		}

		DBG2((g_log,DLEV,"ca bits 0x%x\n", caflags))

		if (strncmp(sv, "Chromecast-Ultra", 16) == 0) {
			*ptyp = cctyp_Ultra;
		} else if (strncmp(sv, "Chromecast-Audio", 16) == 0) {
			*ptyp = cctyp_Audio;
		} else {
			// Hmm. A CC1 or non-Google device.

			/* We're guessing that bit 0 of the ca bits == Video capable */
			/* and bit 1 of the ca bits == Audio capable */
			if (caflags & 1)
				*ptyp = cctyp_1;
			else if (caflags & 4)
				*ptyp = cctyp_Audio;
			else
				*ptyp = cctyp_Other;
		}

		if (fn != NULL) {
			*pname = fn;
		} else {
			free(fn);
			if ((*pname = strdup(sv)) == NULL) {
				DBG((g_log,0,"strdup failed\n"))
				free(sv);
				return -1;
			}
		}

		off = rdsize;

	} 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))
	
		off += rdlength;
	} 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] * 256 + buf[off+1],
		buf[off+2] * 256 + buf[off+3],
		buf[off+4] * 256 + buf[off+5],
		buf[off+6] * 256 + buf[off+7],
		buf[off+8] * 256 + buf[off+9],
		buf[off+10] * 256 + buf[off+11],
		buf[off+12] * 256 + buf[off+13],
		buf[off+14] * 256 + buf[off+15]);
		DBG((g_log,0," V6 address = %s\n",*pip))

		off += rdlength;
	} 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, cctype *ptyp, int *pcaflags, 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++) {
		DBG((g_log,0,"QD %d\n",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(0, pname, pip, ptyp, pcaflags, 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(1, pname, pip, ptyp, pcaflags, 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(2, pname, pip, ptyp, pcaflags, 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

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

*/

/*
	.ca values:

	Original CC		   5 = 0x0005
	Latest CC1		4101 = 0x1005
	Latest CC2		4101 = 0x1005
	Latest CC3		4101 = 0x1005
	Vizio TV        2053 = 0x0805
	Toshiba TV		4101 = 0x1005
	Audio CC        4100 = 0x1004
	Sound bar		2052 = 0x0804

	Google documents the following Capability strings:

	VIDEO_OUT The receiver supports video output.
	VIDEO_IN The receiver supports video input (camera).
	AUDIO_OUT The receiver supports audio output.
	AUDIO_IN The receiver supports audio input (microphone).
	MULTIZONE_GROUP The receiver represents a multi-zone group.

	Which may correspond to ca bits 0, 1, 2, 3 respectively ?

*/

/*
	Original CC 1 dump ?

    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......@

*/

/*

	ChromeCast 1 dump

0000: 00 00 84 00 00 00 00 01 00 00 00 03 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 00 78 00 44 2b 43  cal........x.D+C
0030: 68 72 6f 6d 65 63 61 73 74 2d 63 31 37 61 65 38  hromecast-c17ae8
0040: 38 32 65 37 65 31 38 37 35 64 30 31 33 65 64 32  82e7e1875d013ed2
0050: 31 32 63 31 36 62 62 64 34 30 0b 5f 67 6f 6f 67  12c16bbd40._goog
0060: 6c 65 63 61 73 74 04 5f 74 63 70 05 6c 6f 63 61  lecast._tcp.loca
0070: 6c 00 2b 43 68 72 6f 6d 65 63 61 73 74 2d 63 31  l.+Chromecast-c1
0080: 37 61 65 38 38 32 65 37 65 31 38 37 35 64 30 31  7ae882e7e1875d01
0090: 33 65 64 32 31 32 63 31 36 62 62 64 34 30 0b 5f  3ed212c16bbd40._
00a0: 67 6f 6f 67 6c 65 63 61 73 74 04 5f 74 63 70 05  googlecast._tcp.
00b0: 6c 6f 63 61 6c 00 00 10 80 01 00 00 11 94 00 82  local...........
00c0: 23 69 64 3d 63 31 37 61 65 38 38 32 65 37 65 31  #id=c17ae882e7e1
00d0: 38 37 35 64 30 31 33 65 64 32 31 32 63 31 36 62  875d013ed212c16b
00e0: 62 64 34 30 03 72 6d 3d 05 76 65 3d 30 35 0d 6d  bd40.rm=.ve=05.m
00f0: 64 3d 43 68 72 6f 6d 65 63 61 73 74 12 69 63 3d  d=Chromecast.ic=
0100: 2f 73 65 74 75 70 2f 69 63 6f 6e 2e 70 6e 67 11  /setup/icon.png.
0110: 66 6e 3d 43 68 72 6f 6d 65 63 61 73 74 36 38 39  fn=Chromecast689
0120: 32 07 63 61 3d 34 31 30 31 04 73 74 3d 30 0f 62  2.ca=4101.st=0.b
0130: 73 3d 46 41 38 46 43 41 35 36 36 36 34 35 03 72  s=FA8FCA566645.r
0140: 73 3d 2b 43 68 72 6f 6d 65 63 61 73 74 2d 63 31  s=+Chromecast-c1
0150: 37 61 65 38 38 32 65 37 65 31 38 37 35 64 30 31  7ae882e7e1875d01
0160: 33 65 64 32 31 32 63 31 36 62 62 64 34 30 0b 5f  3ed212c16bbd40._
0170: 67 6f 6f 67 6c 65 63 61 73 74 04 5f 74 63 70 05  googlecast._tcp.
0180: 6c 6f 63 61 6c 00 00 21 80 01 00 00 00 78 00 32  local..!.....x.2
0190: 00 00 00 00 1f 49 24 63 31 37 61 65 38 38 32 2d  .....I$c17ae882-
01a0: 65 37 65 31 2d 38 37 35 64 2d 30 31 33 65 2d 64  e7e1-875d-013e-d
01b0: 32 31 32 63 31 36 62 62 64 34 30 05 6c 6f 63 61  212c16bbd40.loca
01c0: 6c 00 24 63 31 37 61 65 38 38 32 2d 65 37 65 31  l.$c17ae882-e7e1
01d0: 2d 38 37 35 64 2d 30 31 33 65 2d 64 32 31 32 63  -875d-013e-d212c
01e0: 31 36 62 62 64 34 30 05 6c 6f 63 61 6c 00 00 01  16bbd40.local...
01f0: 80 01 00 00 00 78 00 04 c0 a8 01 68              .....x.....h


ChromeCast 2 dump:

0000: 00 00 84 00 00 00 00 01 00 00 00 03 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 00 78 00 44 2b 43  cal........x.D+C
0030: 68 72 6f 6d 65 63 61 73 74 2d 38 32 38 30 30 66  hromecast-82800f
0040: 65 63 33 37 37 36 35 39 38 66 35 38 64 34 61 61  ec3776598f58d4aa
0050: 63 39 65 36 35 64 30 30 30 61 0b 5f 67 6f 6f 67  c9e65d000a._goog
0060: 6c 65 63 61 73 74 04 5f 74 63 70 05 6c 6f 63 61  lecast._tcp.loca
0070: 6c 00 2b 43 68 72 6f 6d 65 63 61 73 74 2d 38 32  l.+Chromecast-82
0080: 38 30 30 66 65 63 33 37 37 36 35 39 38 66 35 38  800fec3776598f58
0090: 64 34 61 61 63 39 65 36 35 64 30 30 30 61 0b 5f  d4aac9e65d000a._
00a0: 67 6f 6f 67 6c 65 63 61 73 74 04 5f 74 63 70 05  googlecast._tcp.
00b0: 6c 6f 63 61 6c 00 00 10 80 01 00 00 11 94 00 a9  local...........
00c0: 23 69 64 3d 38 32 38 30 30 66 65 63 33 37 37 36  #id=82800fec3776
00d0: 35 39 38 66 35 38 64 34 61 61 63 39 65 36 35 64  598f58d4aac9e65d
00e0: 30 30 30 61 13 72 6d 3d 36 36 39 42 39 34 34 38  000a.rm=669B9448
00f0: 36 33 36 30 41 31 43 42 05 76 65 3d 30 35 0d 6d  6360A1CB.ve=05.m
0100: 64 3d 43 68 72 6f 6d 65 63 61 73 74 12 69 63 3d  d=Chromecast.ic=
0110: 2f 73 65 74 75 70 2f 69 63 6f 6e 2e 70 6e 67 11  /setup/icon.png.
0120: 66 6e 3d 43 68 72 6f 6d 65 63 61 73 74 36 35 30  fn=Chromecast650
0130: 32 07 63 61 3d 34 31 30 31 04 73 74 3d 31 0f 62  2.ca=4101.st=1.b
0140: 73 3d 46 41 38 46 43 41 39 45 33 45 30 31 1a 72  s=FA8FCA9E3E01.r
0150: 73 3d 50 61 74 74 65 72 6e 20 67 65 6e 65 72 61  s=Pattern genera
0160: 74 6f 72 20 72 65 61 64 79 2b 43 68 72 6f 6d 65  tor ready+Chrome
0170: 63 61 73 74 2d 38 32 38 30 30 66 65 63 33 37 37  cast-82800fec377
0180: 36 35 39 38 66 35 38 64 34 61 61 63 39 65 36 35  6598f58d4aac9e65
0190: 64 30 30 30 61 0b 5f 67 6f 6f 67 6c 65 63 61 73  d000a._googlecas
01a0: 74 04 5f 74 63 70 05 6c 6f 63 61 6c 00 00 21 80  t._tcp.local..!.
01b0: 01 00 00 00 78 00 32 00 00 00 00 1f 49 24 38 32  ....x.2.....I$82
01c0: 38 30 30 66 65 63 2d 33 37 37 36 2d 35 39 38 66  800fec-3776-598f
01d0: 2d 35 38 64 34 2d 61 61 63 39 65 36 35 64 30 30  -58d4-aac9e65d00
01e0: 30 61 05 6c 6f 63 61 6c 00 24 38 32 38 30 30 66  0a.local.$82800f
01f0: 65 63 2d 33 37 37 36 2d 35 39 38 66 2d 35 38 64  ec-3776-598f-58d
0200: 34 2d 61 61 63 39 65 36 35 64 30 30 30 61 05 6c  4-aac9e65d000a.l
0210: 6f 63 61 6c 00 00 01 80 01 00 00 00 78 00 04 0a  ocal........x...
0220: 00 00 80                                         ...

ChromeCast 3 (Ultra)

0000: 00 00 84 00 00 00 00 01 00 00 00 03 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 00 78 00 34 31 43  cal........x.41C
0030: 68 72 6f 6d 65 63 61 73 74 2d 55 6c 74 72 61 2d  hromecast-Ultra-
0040: 32 38 36 65 36 63 63 65 38 34 65 32 38 65 61 33  286e6cce84e28ea3
0050: 36 63 37 63 33 31 39 32 66 33 35 62 65 30 33 65  6c7c3192f35be03e
0060: c0 0c c0 2e 00 10 80 01 00 00 11 94 00 8d 23 69  ..............#i
0070: 64 3d 32 38 36 65 36 63 63 65 38 34 65 32 38 65  d=286e6cce84e28e
0080: 61 33 36 63 37 63 33 31 39 32 66 33 35 62 65 30  a36c7c3192f35be0
0090: 33 65 03 72 6d 3d 05 76 65 3d 30 35 13 6d 64 3d  3e.rm=.ve=05.md=
00a0: 43 68 72 6f 6d 65 63 61 73 74 20 55 6c 74 72 61  Chromecast Ultra
00b0: 12 69 63 3d 2f 73 65 74 75 70 2f 69 63 6f 6e 2e  .ic=/setup/icon.
00c0: 70 6e 67 16 66 6e 3d 43 68 72 6f 6d 65 63 61 73  png.fn=Chromecas
00d0: 74 55 6c 74 72 61 36 32 35 30 07 63 61 3d 34 31  tUltra6250.ca=41
00e0: 30 31 04 73 74 3d 30 0f 62 73 3d 46 41 38 46 43  01.st=0.bs=FA8FC
00f0: 41 37 30 38 35 42 35 03 72 73 3d c0 2e 00 21 80  A7085B5.rs=...!.
0100: 01 00 00 00 78 00 2d 00 00 00 00 1f 49 24 32 38  ....x.-.....I$28
0110: 36 65 36 63 63 65 2d 38 34 65 32 2d 38 65 61 33  6e6cce-84e2-8ea3
0120: 2d 36 63 37 63 2d 33 31 39 32 66 33 35 62 65 30  -6c7c-3192f35be0
0130: 33 65 c0 1d c1 0d 00 01 80 01 00 00 00 78 00 04  3e...........x..
0140: 0a 00 01 1d                                      ....

*/


/* Other non-Google devices:

Vizio M60-D1 :- Smart TV

00000000  00 00 84 00 00 00 00 01  00 00 00 03 0b 5f 67 6f  |............._go|
00000010  6f 67 6c 65 63 61 73 74  04 5f 74 63 70 05 6c 6f  |oglecast._tcp.lo|
00000020  63 61 6c 00 00 0c 00 01  00 00 00 78 00 40 27 4d  |cal........x.@'M|
00000030  36 30 2d 44 31 2d 37 63  35 35 36 37 39 31 35 38  |60-D1-7c55679158|
00000040  38 64 63 64 33 37 63 37  37 32 33 31 61 30 64 64  |8dcd37c77231a0dd|
00000050  31 35 61 35 30 36 0b 5f  67 6f 6f 67 6c 65 63 61  |15a506._googleca|
00000060  73 74 04 5f 74 63 70 05  6c 6f 63 61 6c 00 27 4d  |st._tcp.local.'M|
00000070  36 30 2d 44 31 2d 37 63  35 35 36 37 39 31 35 38  |60-D1-7c55679158|
00000080  38 64 63 64 33 37 63 37  37 32 33 31 61 30 64 64  |8dcd37c77231a0dd|
00000090  31 35 61 35 30 36 0b 5f  67 6f 6f 67 6c 65 63 61  |15a506._googleca|
000000a0  73 74 04 5f 74 63 70 05  6c 6f 63 61 6c 00 00 10  |st._tcp.local...|
000000b0  80 01 00 00 11 94 00 80  23 69 64 3d 37 63 35 35  |........#id=7c55|
000000c0  36 37 39 31 35 38 38 64  63 64 33 37 63 37 37 32  |6791588dcd37c772|
000000d0  33 31 61 30 64 64 31 35  61 35 30 36 13 72 6d 3d  |31a0dd15a506.rm=|
000000e0  31 33 39 46 33 32 34 34  38 34 44 46 41 39 32 34  |139F324484DFA924|
000000f0  05 76 65 3d 30 35 09 6d  64 3d 4d 36 30 2d 44 31  |.ve=05.md=M60-D1|
00000100  12 69 63 3d 2f 73 65 74  75 70 2f 69 63 6f 6e 2e  |.ic=/setup/icon.|
00000110  70 6e 67 0f 66 6e 3d 56  69 7a 69 6f 20 4d 36 30  |png.fn=Vizio M60|
00000120  2d 44 31 07 63 61 3d 32  30 35 33 04 73 74 3d 30  |-D1.ca=2053.st=0|
00000130  03 62 73 3d 03 72 73 3d  27 4d 36 30 2d 44 31 2d  |.bs=.rs='M60-D1-|
00000140  37 63 35 35 36 37 39 31  35 38 38 64 63 64 33 37  |7c556791588dcd37|
00000150  63 37 37 32 33 31 61 30  64 64 31 35 61 35 30 36  |c77231a0dd15a506|
00000160  0b 5f 67 6f 6f 67 6c 65  63 61 73 74 04 5f 74 63  |._googlecast._tc|
00000170  70 05 6c 6f 63 61 6c 00  00 21 80 01 00 00 00 78  |p.local..!.....x|
00000180  00 32 00 00 00 00 1f 49  24 37 63 35 35 36 37 39  |.2.....I$7c55679|
00000190  31 2d 35 38 38 64 2d 63  64 33 37 2d 63 37 37 32  |1-588d-cd37-c772|
000001a0  2d 33 31 61 30 64 64 31  35 61 35 30 36 05 6c 6f  |-31a0dd15a506.lo|
000001b0  63 61 6c 00 24 37 63 35  35 36 37 39 31 2d 35 38  |cal.$7c556791-58|
000001c0  38 64 2d 63 64 33 37 2d  63 37 37 32 2d 33 31 61  |8d-cd37-c772-31a|
000001d0  30 64 64 31 35 61 35 30  36 05 6c 6f 63 61 6c 00  |0dd15a506.local.|
000001e0  00 01 80 01 00 00 00 78  00 04 c0 a8 00 3e        |.......x.....>|
000001ee

SmartCast Sound Bar 3851-D0  :- Sound bar

00000000  00 00 84 00 00 00 00 01  00 00 00 03 0b 5f 67 6f  |............._go|
00000010  6f 67 6c 65 63 61 73 74  04 5f 74 63 70 05 6c 6f  |oglecast._tcp.lo|
00000020  63 61 6c 00 00 0c 00 01  00 00 00 78 00 22 09 53  |cal........x.".S|
00000030  42 33 38 35 31 2d 44 30  0b 5f 67 6f 6f 67 6c 65  |B3851-D0._google|
00000040  63 61 73 74 04 5f 74 63  70 05 6c 6f 63 61 6c 00  |cast._tcp.local.|
00000050  09 53 42 33 38 35 31 2d  44 30 0b 5f 67 6f 6f 67  |.SB3851-D0._goog|
00000060  6c 65 63 61 73 74 04 5f  74 63 70 05 6c 6f 63 61  |lecast._tcp.loca|
00000070  6c 00 00 10 80 01 00 00  11 94 00 8a 23 69 64 3d  |l...........#id=|
00000080  64 35 36 63 33 66 30 39  36 66 30 31 30 31 36 66  |d56c3f096f01016f|
00000090  33 64 37 30 65 62 62 63  36 32 36 34 65 66 33 31  |3d70ebbc6264ef31|
000000a0  05 76 65 3d 30 34 1e 6d  64 3d 53 6d 61 72 74 43  |.ve=04.md=SmartC|
000000b0  61 73 74 20 53 6f 75 6e  64 20 42 61 72 20 33 38  |ast Sound Bar 38|
000000c0  35 31 2d 44 30 12 69 63  3d 2f 73 65 74 75 70 2f  |51-D0.ic=/setup/|
000000d0  69 63 6f 6e 2e 70 6e 67  0c 66 6e 3d 53 42 33 38  |icon.png.fn=SB38|
000000e0  35 31 2d 44 30 07 63 61  3d 32 30 35 32 04 73 74  |51-D0.ca=2052.st|
000000f0  3d 30 0f 62 73 3d 46 46  46 46 46 46 46 46 46 46  |=0.bs=FFFFFFFFFF|
00000100  46 46 03 72 73 3d 09 53  42 33 38 35 31 2d 44 30  |FF.rs=.SB3851-D0|
00000110  0b 5f 67 6f 6f 67 6c 65  63 61 73 74 04 5f 74 63  |._googlecast._tc|
00000120  70 05 6c 6f 63 61 6c 00  00 21 80 01 00 00 00 78  |p.local..!.....x|
00000130  00 17 00 00 00 00 1f 49  09 53 42 33 38 35 31 2d  |.......I.SB3851-|
00000140  44 30 05 6c 6f 63 61 6c  00 09 53 42 33 38 35 31  |D0.local..SB3851|
00000150  2d 44 30 05 6c 6f 63 61  6c 00 00 01 80 01 00 00  |-D0.local.......|
00000160  00 78 00 04 c0 a8 00 26                           |.x.....&|
00000168

ZChromecast Toshiba :- TV ?

00000000  00 00 84 00 00 00 00 01  00 00 00 03 0b 5f 67 6f  |............._go|
00000010  6f 67 6c 65 63 61 73 74  04 5f 74 63 70 05 6c 6f  |oglecast._tcp.lo|
00000020  63 61 6c 00 00 0c 00 01  00 00 00 78 00 2e 2b 43  |cal........x..+C|
00000030  68 72 6f 6d 65 63 61 73  74 2d 64 61 30 33 34 62  |hromecast-da034b|
00000040  31 39 30 30 34 34 62 63  38 65 35 33 30 31 34 37  |190044bc8e530147|
00000050  63 37 61 65 33 32 63 35  61 33 c0 0c c0 2e 00 10  |c7ae32c5a3......|
00000060  80 01 00 00 11 94 00 87  23 69 64 3d 64 61 30 33  |........#id=da03|
00000070  34 62 31 39 30 30 34 34  62 63 38 65 35 33 30 31  |4b190044bc8e5301|
00000080  34 37 63 37 61 65 33 32  63 35 61 33 03 72 6d 3d  |47c7ae32c5a3.rm=|
00000090  05 76 65 3d 30 35 0d 6d  64 3d 43 68 72 6f 6d 65  |.ve=05.md=Chrome|
000000a0  63 61 73 74 12 69 63 3d  2f 73 65 74 75 70 2f 69  |cast.ic=/setup/i|
000000b0  63 6f 6e 2e 70 6e 67 16  66 6e 3d 5a 43 68 72 6f  |con.png.fn=ZChro|
000000c0  6d 65 63 61 73 74 20 54  6f 73 68 69 62 61 07 63  |mecast Toshiba.c|
000000d0  61 3d 34 31 30 31 04 73  74 3d 30 0f 62 73 3d 46  |a=4101.st=0.bs=F|
000000e0  41 38 46 43 41 37 30 32  30 36 32 03 72 73 3d c0  |A8FCA702062.rs=.|
000000f0  2e 00 21 80 01 00 00 00  78 00 2d 00 00 00 00 1f  |..!.....x.-.....|
00000100  49 24 64 61 30 33 34 62  31 39 2d 30 30 34 34 2d  |I$da034b19-0044-|
00000110  62 63 38 65 2d 35 33 30  31 2d 34 37 63 37 61 65  |bc8e-5301-47c7ae|
00000120  33 32 63 35 61 33 c0 1d  c1 01 00 01 80 01 00 00  |32c5a3..........|
00000130  00 78 00 04 c0 a8 00 1b                           |.x......|
00000138

*/

/* 

	Can get info from Chromecast http server at http://XX.XX.XX.XX:8008/ssdp/device-desc.xml

	Get info about the app running: http://XX.XX.XX.XX:8008/apps/ChromeCast

*/


