mDNS.c   [plain text]


// ***************************************************************************
// mDNS.c
// This file defines all of mDNS, including
// mDNS Service Discovery, mDNS Responder, and mDNS Searcher.
//
// This code is completely 100% portable C. It does not depend on any external header files
// from outside the mDNS project -- all the types it expects to find are defined right here.
//
// The previous point is very important: This file does not depend on any external
// header files. It should complile on *any* platform that has a C compiler, without
// making *any* assumptions about availability of so-called "standard" C functions,
// routines, or types (which may or may not be present on any given platform).
// ***************************************************************************

/*
 * Formatting notes:
 * This code follows the "Whitesmiths style" C indentation rules. Plenty of discussion
 * on C indentation can be found on the web, such as <http://www.kafejo.com/komp/1tbs.htm>,
 * but for the sake of brevity here I will say just this: Curly braces are not syntactially
 * part of an "if" statement; they are the beginning and ending markers of a compound statement;
 * therefore common sense dictates that if they are part of a compound statement then they
 * should be indented to the same level as everything else in that compound statement.
 * Indenting curly braces at the same level as the "if" implies that curly braces are
 * part of the "if", which is false. (This is as misleading as people who write "char* x,y;"
 * thinking that variables x and y are both of type "char*" -- and anyone who doesn't
 * understand why variable y is not of type "char*" just proves the point that poor code
 * layout leads people to unfortunate misunderstandings about how the C language really works.)
 */

#include "mDNSClientAPI.h"				// Defines the interface provided to the client layer above
#include "mDNSPlatformFunctions.h"		// Defines the interface required of the supporting layer below
#include "mDNSsprintf.h"

#if(defined(_MSC_VER))
	// Disable warnings about Microsoft Visual Studio/C++ not understanding "pragma unused"
    #pragma warning( disable:4068 )
#endif

// ***************************************************************************
#if 0
#pragma mark - DNS Protocol Constants
#endif

typedef enum
	{
	kDNSFlag0_QR_Mask     = 0x80,		// Query or response?
	kDNSFlag0_QR_Query    = 0x00,
	kDNSFlag0_QR_Response = 0x80,
	
	kDNSFlag0_OP_Mask     = 0x78,		// Operation type
	kDNSFlag0_OP_StdQuery = 0x00,
	kDNSFlag0_OP_Iquery   = 0x08,
	kDNSFlag0_OP_Status   = 0x10,
	kDNSFlag0_OP_Unused3  = 0x18,
	kDNSFlag0_OP_Notify   = 0x20,
	kDNSFlag0_OP_Update   = 0x28,
	
	kDNSFlag0_QROP_Mask   = kDNSFlag0_QR_Mask | kDNSFlag0_OP_Mask,
	
	kDNSFlag0_AA          = 0x04,		// Authoritative Answer?
	kDNSFlag0_TC          = 0x02,		// Truncated?
	kDNSFlag0_RD          = 0x01,		// Recursion Desired?
	kDNSFlag1_RA          = 0x80,		// Recursion Available?
	
	kDNSFlag1_Zero        = 0x40,		// Reserved; must be zero
	kDNSFlag1_AD          = 0x20,		// Authentic Data [RFC 2535]
	kDNSFlag1_CD          = 0x10,		// Checking Disabled [RFC 2535]

	kDNSFlag1_RC          = 0x0F,		// Response code
	kDNSFlag1_RC_NoErr    = 0x00,
	kDNSFlag1_RC_FmtErr   = 0x01,
	kDNSFlag1_RC_SrvErr   = 0x02,
	kDNSFlag1_RC_NXDomain = 0x03,
	kDNSFlag1_RC_NotImpl  = 0x04,
	kDNSFlag1_RC_Refused  = 0x05,
	kDNSFlag1_RC_YXDomain = 0x06,
	kDNSFlag1_RC_YXRRSet  = 0x07,
	kDNSFlag1_RC_NXRRSet  = 0x08,
	kDNSFlag1_RC_NotAuth  = 0x09,
	kDNSFlag1_RC_NotZone  = 0x0A
	} DNS_Flags;

// ***************************************************************************
#if 0
#pragma mark -
#pragma mark - Program Constants
#endif

mDNSexport const ResourceRecord zeroRR = { 0 };
mDNSexport const mDNSIPPort zeroIPPort = { { 0 } };
mDNSexport const mDNSIPAddr zeroIPAddr = { { 0 } };
mDNSexport const mDNSIPAddr onesIPAddr = { { 255, 255, 255, 255 } };

#define UnicastDNSPortAsNumber 53
#define MulticastDNSPortAsNumber 5353
mDNSexport const mDNSIPPort UnicastDNSPort     = { { UnicastDNSPortAsNumber   >> 8, UnicastDNSPortAsNumber   & 0xFF } };
mDNSexport const mDNSIPPort MulticastDNSPort   = { { MulticastDNSPortAsNumber >> 8, MulticastDNSPortAsNumber & 0xFF } };
mDNSexport const mDNSIPAddr AllDNSLinkGroup  = { { 224,   0,   0, 251 } };
mDNSexport const mDNSIPAddr AllDNSAdminGroup = { { 239, 255, 255, 251 } };

static const mDNSOpaque16 zeroID = { { 0, 0 } };
static const mDNSOpaque16 QueryFlags    = { { kDNSFlag0_QR_Query    | kDNSFlag0_OP_StdQuery,                0 } };
static const mDNSOpaque16 ResponseFlags = { { kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery | kDNSFlag0_AA, 0 } };
#define zeroDomainNamePtr ((domainname*)"")

static const char *const mDNS_DomainTypeNames[] =
	{
	"_browse._mdns._udp.local.",
	"_default._browse._mdns._udp.local.",
	"_register._mdns._udp.local.",
	"_default._register._mdns._udp.local."
	};

// ***************************************************************************
#if 0
#pragma mark -
#pragma mark - General Utility Functions
#endif

#if DEBUGBREAKS
mDNSlocal char *DNSTypeName(mDNSu16 rrtype)
	{
	switch (rrtype)
		{
		case kDNSType_A:	return("Address");
		case kDNSType_CNAME:return("CNAME");
		case kDNSType_PTR:	return("PTR");
		case kDNSType_TXT:  return("TXT");
		case kDNSType_SRV:	return("SRV");
		default:			{
							static char buffer[16];
							mDNS_sprintf(buffer, "(%d)", rrtype);
							return(buffer);
							}
		}
	}
#endif

mDNSlocal mDNSu32 mDNSRandom(mDNSu32 max)
	{
	static mDNSu32 seed = 1;
	mDNSu32 mask = 1;
	while (mask < max) mask = (mask << 1) | 1;
	do seed = seed * 21 + 1; while ((seed & mask) > max);
	return (seed & mask);
	}

// ***************************************************************************
#if 0
#pragma mark -
#pragma mark - Domain Name Utility Functions
#endif

// Returns length of a domain name INCLUDING the byte for the final null label
// i.e. for the root label "." it returns one
// For the FQDN "com." it returns 5 (length, three data bytes, final zero)
mDNSexport mDNSu32 DomainNameLength(const domainname *const name)
	{
	const mDNSu8 *src = name->c;
	while (*src)
		{
		if (*src > MAX_DOMAIN_LABEL) return(MAX_DOMAIN_NAME+1);
		src += 1 + *src;
		if (src - name->c >= MAX_DOMAIN_NAME) return(MAX_DOMAIN_NAME+1);
		}
	return((mDNSu32)(src - name->c + 1));
	}

mDNSexport mDNSBool SameDomainLabel(const mDNSu8 *a, const mDNSu8 *b)
	{
	int i;
	const int len = *a++;

	if (len > MAX_DOMAIN_LABEL)
		{ debugf("Malformed label (too long)"); return(mDNSfalse); }

	if (len != *b++) return(mDNSfalse);
	for (i=0; i<len; i++)
		{
		mDNSu8 ac = *a++;
		mDNSu8 bc = *b++;
		if (ac >= 'A' && ac <= 'Z') ac += 'a' - 'A';
		if (bc >= 'A' && bc <= 'Z') bc += 'a' - 'A';
		if (ac != bc) return(mDNSfalse);
		}
	return(mDNStrue);
	}

mDNSexport mDNSBool SameDomainName(const domainname *const d1, const domainname *const d2)
	{
	const mDNSu8 *      a   = d1->c;
	const mDNSu8 *      b   = d2->c;
	const mDNSu8 *const max = d1->c + MAX_DOMAIN_NAME;			// Maximum that's valid

	while (*a || *b)
		{
		if (a + 1 + *a >= max)
			{ debugf("Malformed domain name (more than 255 characters)"); return(mDNSfalse); }
		if (!SameDomainLabel(a, b)) return(mDNSfalse);
		a += 1 + *a;
		b += 1 + *b;
		}
	
	return(mDNStrue);
	}

// CompressedDomainNameLength returns the length of a domain name INCLUDING the byte
// for the final null label i.e. for the root label "." it returns one.
// E.g. for the FQDN "foo.com." it returns 9
// (length, three data bytes, length, three more data bytes, final zero).
// In the case where a parent domain name is provided, and the given name is a child
// of that parent, CompressedDomainNameLength returns the length of the prefix portion
// of the child name, plus TWO bytes for the compression pointer.
// E.g. for the name "foo.com." with parent "com.", it returns 6
// (length, three data bytes, two-byte compression pointer).
mDNSlocal mDNSu32 CompressedDomainNameLength(const domainname *const name, const domainname *parent)
	{
	const mDNSu8 *src = name->c;
	if (parent && parent->c[0] == 0) parent = mDNSNULL;
	while (*src)
		{
		if (*src > MAX_DOMAIN_LABEL) return(MAX_DOMAIN_NAME+1);
		if (parent && SameDomainName((domainname *)src, parent)) return((mDNSu32)(src - name->c + 2));
		src += 1 + *src;
		if (src - name->c >= MAX_DOMAIN_NAME) return(MAX_DOMAIN_NAME+1);
		}
	return((mDNSu32)(src - name->c + 1));
	}

mDNSexport void AppendDomainLabelToName(domainname *const name, const domainlabel *const label)
	{
	int i;
	mDNSu8 *ptr = name->c + DomainNameLength(name) - 1;
	const mDNSu8 *const lim = name->c + MAX_DOMAIN_NAME;
	if (ptr + 1 + label->c[0] + 1 >= lim) return;
	for (i=0; i<=label->c[0]; i++) *ptr++ = label->c[i];
	*ptr++ = 0;										// Put the null root label on the end
	}

// AppendStringLabelToName appends a single label to an existing (possibly empty) domainname.
// The C string contains the label as-is, with no escaping, etc.
// Any dots in the name are literal dots, not label separators
mDNSexport void AppendStringLabelToName(domainname *const name, const char *cstr)
	{
	mDNSu8 *lengthbyte;
	mDNSu8 *ptr = name->c + DomainNameLength(name) - 1;
	const mDNSu8 *lim = name->c + MAX_DOMAIN_NAME - 1;
	if (lim > ptr + MAX_DOMAIN_LABEL + 1)
		lim = ptr + MAX_DOMAIN_LABEL + 1;
	lengthbyte = ptr++;
	while (*cstr && ptr < lim) *ptr++ = (mDNSu8)*cstr++;
	*lengthbyte = (mDNSu8)(ptr - lengthbyte - 1);
	*ptr++ = 0;										// Put the null root label on the end
	}

mDNSexport void AppendDomainNameToName(domainname *const name, const domainname *const append)
	{
	int i;
	mDNSu8 *ptr = name->c + DomainNameLength(name) - 1;
	const mDNSu8 *src = append->c;
	const mDNSu8 *const lim = name->c + MAX_DOMAIN_NAME;
	while(src[0])
		{
		if (ptr + 1 + src[0] + 1 >= lim) return;
		for (i=0; i<=src[0]; i++) *ptr++ = src[i];
		*ptr = 0;	// Put the null root label on the end
		src += i;
		}
	}

// AppendStringNameToName appends zero or more labels to an existing (possibly empty) domainname.
// The C string contains the labels separated by dots, but otherwise as-is, with no escaping, etc.
mDNSexport void AppendStringNameToName(domainname *const name, const char *cstr)
	{
	mDNSu8 *ptr = name->c + DomainNameLength(name) - 1;			// Find end of current name
	const mDNSu8 *const lim = name->c + MAX_DOMAIN_NAME - 1;		// Find limit of how much we can add
	while (*cstr)
		{
		mDNSu8 *const lengthbyte = ptr++;
		const mDNSu8 *const lim2 = ptr + MAX_DOMAIN_LABEL;
		const mDNSu8 *const lim3 = (lim < lim2) ? lim : lim2;
		while (*cstr && *cstr != '.' && ptr < lim3) *ptr++ = (mDNSu8)*cstr++;
		*lengthbyte = (mDNSu8)(ptr - lengthbyte - 1);
		if (*cstr == '.') cstr++;
		}
	
	*ptr++ = 0;										// Put the null root label on the end
	}

//#define IsThreeDigit(X) (IsDigit((X)[1]) && IsDigit((X)[2]) && IsDigit((X)[3]))
//#define ValidEscape(X) (X)[0] == '\\' && ((X)[1] == '\\' || (X)[1] == '\\' || IsThreeDigit(X))

#define mdnsIsLetter(X) (((X) >= 'A' && (X) <= 'Z') || ((X) >= 'a' && (X) <= 'z'))
#define mdnsIsDigit(X) (((X) >= '0' && (X) <= '9'))
#define mdnsValidHostChar(X, notfirst, notlast) (mdnsIsLetter(X) || \
	((notfirst) && (mdnsIsDigit(X) || ((notlast) && (X) == '-'))) )

mDNSexport void ConvertCStringToDomainLabel(const char *src, domainlabel *label)
	{
	mDNSu8       *      ptr   = label->c + 1;					// Where we're putting it
	const mDNSu8 *const limit = ptr + MAX_DOMAIN_LABEL;			// The maximum we can put
	while (*src && ptr < limit)									// While we have characters in the label...
		{
		mDNSu8 c = (mDNSu8)*src++;								// Read the character
		if (c == '\\')											// If escape character, check next character
			{
			if (*src == '\\' || *src == '.')					// If a second escape, or a dot,
				c = (mDNSu8)*src++;								// just use the second character
			else if (mdnsIsDigit(src[0]) && mdnsIsDigit(src[1]) && mdnsIsDigit(src[2]))
				{												// else, if three decimal digits,
				int v0 = src[0] - '0';							// then interpret as three-digit decimal
				int v1 = src[1] - '0';
				int v2 = src[2] - '0';
				int val = v0 * 100 + v1 * 10 + v2;
				if (val <= 255) { c = (mDNSu8)val; src += 3; }	// If valid value, use it
				}
			}
		*ptr++ = c;												// Write the character
		}
	label->c[0] = (mDNSu8)(ptr - label->c - 1);
	}

mDNSexport mDNSu8 *ConvertCStringToDomainName(const char *const cstr, domainname *name)
	{
	const mDNSu8 *src         = (const mDNSu8 *)cstr;					// C string we're reading
	mDNSu8       *ptr         = name->c;								// Where we're putting it
	const mDNSu8 *const limit = ptr + MAX_DOMAIN_NAME;				// The maximum we can put

	while (*src && ptr < limit)										// While more characters, and space to put them...
		{
		mDNSu8 *lengthbyte = ptr++;									// Record where the length is going to go
		while (*src && *src != '.' && ptr < limit)					// While we have characters in the label...
			{
			mDNSu8 c = *src++;										// Read the character
			if (c == '\\')											// If escape character, check next character
				{
				if (*src == '\\' || *src == '.')					// If a second escape, or a dot,
					c = *src++;										// just use the second character
				else if (mdnsIsDigit(src[0]) && mdnsIsDigit(src[1]) && mdnsIsDigit(src[2]))
					{												// else, if three decimal digits,
					int v0 = src[0] - '0';						// then interpret as three-digit decimal
					int v1 = src[1] - '0';
					int v2 = src[2] - '0';
					int val = v0 * 100 + v1 * 10 + v2;
					if (val <= 255) { c = (mDNSu8)val; src += 3; }	// If valid value, use it
					}
				}
			*ptr++ = c;												// Write the character
			}
		if (*src) src++;											// Skip over the trailing dot (if present)
		if (ptr - lengthbyte - 1 > MAX_DOMAIN_LABEL) return(mDNSNULL);	// If illegal label, abort
		*lengthbyte = (mDNSu8)(ptr - lengthbyte - 1);
		}

	if (ptr < limit)												// If we didn't run out of space
		{
		*ptr++ = 0;													// Put the final root label
		return(ptr);												// and return
		}

	return(mDNSNULL);
	}

//#define convertCstringtodomainname(C,D)        convertCstringtodomainname_withescape((C), (D), -1)
//#define convertescapedCstringtodomainname(C,D) convertCstringtodomainname_withescape((C), (D), '\\')

mDNSexport char *ConvertDomainLabelToCString_withescape(const domainlabel *const label, char *ptr, char esc)
	{
	const mDNSu8 *      src = label->c;							// Domain label we're reading
	const mDNSu8        len = *src++;							// Read length of this (non-null) label
	const mDNSu8 *const end = src + len;							// Work out where the label ends
	if (len > MAX_DOMAIN_LABEL) return(mDNSNULL);					// If illegal label, abort
	while (src < end)											// While we have characters in the label
		{
		mDNSu8 c = *src++;
		if (esc)
			{
			if (c == '.')										// If character is a dot,
				*ptr++ = esc;									// Output escape character
			else if (c <= ' ')									// If non-printing ascii,
				{												// Output decimal escape sequence
				*ptr++ = esc;
				*ptr++ = (char) ('0' + (c / 100)     );
				*ptr++ = (char) ('0' + (c /  10) % 10);
				c      = (mDNSu8)('0' + (c      ) % 10);
				}
			}
		*ptr++ = (char)c;										// Copy the character
		}
	*ptr = 0;													// Null-terminate the string
	return(ptr);												// and return
	}

// Note, to guarantee that there will be no possible overrun, cstr must be at least 1005 bytes
// The longest legal domain name is 255 bytes, in the form of three 64-byte labels, one 62-byte label,
// and the null root label.
// If every label character has to be escaped as a four-byte escape sequence, the maximum textual
// ascii display of this is 63*4 + 63*4 + 63*4 + 61*4 = 1000 label characters,
// plus four dots and the null at the end of the C string = 1005
mDNSexport char *ConvertDomainNameToCString_withescape(const domainname *const name, char *ptr, char esc)
	{
	const mDNSu8 *src         = name->c;								// Domain name we're reading
	const mDNSu8 *const max   = name->c + MAX_DOMAIN_NAME;			// Maximum that's valid
	
	if (*src == 0) *ptr++ = '.';									// Special case: For root, just write a dot

	while (*src)													// While more characters in the domain name
		{
		if (src + 1 + *src >= max) return(mDNSNULL);
		ptr = ConvertDomainLabelToCString_withescape((const domainlabel *)src, ptr, esc);
		if (!ptr) return(mDNSNULL);
		src += 1 + *src;
		*ptr++ = '.';												// Write the dot after the label
		}

	*ptr++ = 0;														// Null-terminate the string
	return(ptr);													// and return
	}

// RFC 1034 rules:
// Host names must start with a letter, end with a letter or digit,
// and have as interior characters only letters, digits, and hyphen.

mDNSexport void ConvertUTF8PstringToRFC1034HostLabel(const mDNSu8 UTF8Name[], domainlabel *const hostlabel)
	{
	const mDNSu8 *      src  = &UTF8Name[1];
	const mDNSu8 *const end  = &UTF8Name[1] + UTF8Name[0];
	      mDNSu8 *      ptr  = &hostlabel->c[1];
	const mDNSu8 *const lim  = &hostlabel->c[1] + MAX_DOMAIN_LABEL;
	while (src < end)
		{
		// Delete apostrophes from source name
		if (src[0] == '\'') { src++; continue; }		// Standard straight single quote
		if (src + 2 < end && src[0] == 0xE2 && src[1] == 0x80 && src[2] == 0x99)
			{ src += 3; continue; }	// Unicode curly apostrophe
		if (ptr < lim)
			{
			if (mdnsValidHostChar(*src, (ptr > &hostlabel->c[1]), (src < end-1))) *ptr++ = *src;
			else if (ptr > &hostlabel->c[1] && ptr[-1] != '-') *ptr++ = '-';
			}
		src++;
		}
	while (ptr > &hostlabel->c[1] && ptr[-1] == '-') ptr--;	// Truncate trailing '-' marks
	hostlabel->c[0] = (mDNSu8)(ptr - &hostlabel->c[1]);
	}

mDNSexport mDNSu8 *ConstructServiceName(domainname *const fqdn,
	const domainlabel *const name, const domainname *const type, const domainname *const domain)
	{
	int i, len;
	mDNSu8 *dst = fqdn->c;
	mDNSu8 *max = fqdn->c + MAX_DOMAIN_NAME;
	const mDNSu8 *src;

	if (name)
		{
		src = name->c;									// Put the service name into the domain name
		len = *src;
		if (len >= 0x40) { debugf("ConstructServiceName: service name too long"); return(0); }
		for (i=0; i<=len; i++) *dst++ = *src++;
		}
	
	src = type->c;										// Put the service type into the domain name
	len = *src;
	if (len == 0 || len >= 0x40)  { debugf("ConstructServiceName: Invalid service name"); return(0); }
	if (dst + 1 + len + 1 >= max) { debugf("ConstructServiceName: service type too long"); return(0); }
	for (i=0; i<=len; i++) *dst++ = *src++;

	len = *src;
	if (len == 0 || len >= 0x40)  { debugf("ConstructServiceName: Invalid service name"); return(0); }
	if (dst + 1 + len + 1 >= max) { debugf("ConstructServiceName: service type too long"); return(0); }
	for (i=0; i<=len; i++) *dst++ = *src++;
	
	if (*src) { debugf("ConstructServiceName: Service type must have only two labels"); return(0); }

	src = domain->c;									// Put the service domain into the domain name
	while (*src)
		{
		len = *src;
		if (dst + 1 + len + 1 >= max)
			{ debugf("ConstructServiceName: service domain too long"); return(0); }
		for (i=0; i<=len; i++) *dst++ = *src++;
		}
	
	*dst++ = 0;		// Put the null root label on the end
	return(dst);
	}

mDNSexport mDNSBool DeconstructServiceName(const domainname *const fqdn,
	domainlabel *const name, domainname *const type, domainname *const domain)
	{
	int i, len;
	const mDNSu8 *src = fqdn->c;
	const mDNSu8 *max = fqdn->c + MAX_DOMAIN_NAME;
	mDNSu8 *dst;
	
	dst = name->c;										// Extract the service name from the domain name
	len = *src;
	if (len >= 0x40) { debugf("DeconstructServiceName: service name too long"); return(mDNSfalse); }
	for (i=0; i<=len; i++) *dst++ = *src++;
	
	dst = type->c;										// Extract the service type from the domain name
	len = *src;
	if (len >= 0x40) { debugf("DeconstructServiceName: service type too long"); return(mDNSfalse); }
	for (i=0; i<=len; i++) *dst++ = *src++;

	len = *src;
	if (len >= 0x40) { debugf("DeconstructServiceName: service type too long"); return(mDNSfalse); }
	for (i=0; i<=len; i++) *dst++ = *src++;
	*dst++ = 0;		// Put the null root label on the end of the service type

	dst = domain->c;									// Extract the service domain from the domain name
	while (*src)
		{
		len = *src;
		if (len >= 0x40)
			{ debugf("DeconstructServiceName: service domain label too long"); return(mDNSfalse); }
		if (src + 1 + len + 1 >= max)
			{ debugf("DeconstructServiceName: service domain too long"); return(mDNSfalse); }
		for (i=0; i<=len; i++) *dst++ = *src++;
		}
	*dst++ = 0;		// Put the null root label on the end
	
	return(mDNStrue);
	}

mDNSlocal void IncrementLabelSuffix(domainlabel *name, mDNSBool RichText)
	{
	long val = 0, multiplier = 1, divisor = 1, digits = 1;

	// Get any existing numerical suffix off the name
	while (mdnsIsDigit(name->c[name->c[0]]))
		{ val += (name->c[name->c[0]] - '0') * multiplier; multiplier *= 10; name->c[0]--; }
	
	// If existing suffix, increment it, else start by renaming "Foo" as "Foo2"
	if (multiplier > 1 && val < 999999) val++; else val = 2;

	// Can only add spaces to rich text names, not RFC 1034 names
	if (RichText && name->c[name->c[0]] != ' ' && name->c[0] < MAX_DOMAIN_LABEL)
		name->c[++name->c[0]] = ' ';
	
	while (val >= divisor * 10)
		{ divisor *= 10; digits++; }

	if (name->c[0] > (mDNSu8)(MAX_DOMAIN_LABEL - digits))
		name->c[0] = (mDNSu8)(MAX_DOMAIN_LABEL - digits);
	
	while (divisor)
		{
		name->c[++name->c[0]] = (mDNSu8)('0' + val / divisor);
		val     %= divisor;
		divisor /= 10;
		}
	}

// ***************************************************************************
#if 0
#pragma mark -
#pragma mark - Resource Record Utility Functions
#endif

#define ResourceRecordIsValidAnswer(RR) ( ((RR)->             RecordType & kDNSRecordTypeActiveMask)  && \
		((RR)->Additional1 == mDNSNULL || ((RR)->Additional1->RecordType & kDNSRecordTypeActiveMask)) && \
		((RR)->Additional2 == mDNSNULL || ((RR)->Additional2->RecordType & kDNSRecordTypeActiveMask)) && \
		((RR)->DependentOn == mDNSNULL || ((RR)->DependentOn->RecordType & kDNSRecordTypeActiveMask))  )

#define ResourceRecordIsValidInterfaceAnswer(RR, I) \
	(ResourceRecordIsValidAnswer(RR) && \
	((RR)->InterfaceAddr.NotAnInteger == 0 || (RR)->InterfaceAddr.NotAnInteger == (I).NotAnInteger))

#define DefaultProbeCountForTypeUnique ((mDNSu8)3)
#define DefaultProbeCountForRecordType(X)      ((X) == kDNSRecordTypeUnique ? DefaultProbeCountForTypeUnique : (mDNSu8)0)

#define DefaultAnnounceCountForTypeShared ((mDNSu8)10)
#define DefaultAnnounceCountForTypeUnique ((mDNSu8)2)

#define DefaultAnnounceCountForRecordType(X)   ((X) == kDNSRecordTypeShared      ? DefaultAnnounceCountForTypeShared : \
												(X) == kDNSRecordTypeUnique      ? DefaultAnnounceCountForTypeUnique : \
												(X) == kDNSRecordTypeVerified    ? DefaultAnnounceCountForTypeUnique : \
												(X) == kDNSRecordTypeKnownUnique ? DefaultAnnounceCountForTypeUnique : (mDNSu8)0)

#define DefaultSendIntervalForRecordType(X)    ((X) == kDNSRecordTypeShared   ? mDNSPlatformOneSecond   : \
												(X) == kDNSRecordTypeUnique   ? mDNSPlatformOneSecond/4 : \
												(X) == kDNSRecordTypeVerified ? mDNSPlatformOneSecond/4 : 0)

#define TimeToAnnounceThisRecord(RR,time) ((RR)->AnnounceCount && time - (RR)->NextSendTime >= 0)
#define TimeToSendThisRecord(RR,time) \
	((TimeToAnnounceThisRecord(RR,time) || (RR)->SendPriority) && ResourceRecordIsValidAnswer(RR))

mDNSlocal mDNSBool SameRData(const mDNSu16 rrtype, const RData *const r1, const RData *const r2)
	{
	if (r1->RDLength != r2->RDLength) return(mDNSfalse);
	switch(rrtype)
		{
		case kDNSType_CNAME:// Same as PTR
		case kDNSType_PTR:	return(SameDomainName(&r1->u.name, &r2->u.name));

		case kDNSType_SRV:	return( r1->u.srv.priority          == r2->u.srv.priority          &&
									r1->u.srv.weight            == r2->u.srv.weight            &&
									r1->u.srv.port.NotAnInteger == r2->u.srv.port.NotAnInteger &&
									SameDomainName(&r1->u.srv.target, &r2->u.srv.target));

		default:			return(mDNSPlatformMemSame(r1->u.data, r2->u.data, r1->RDLength));
		}
	}

mDNSlocal mDNSBool ResourceRecordAnswersQuestion(const ResourceRecord *const rr, const DNSQuestion *const q)
	{
	if (rr->InterfaceAddr.NotAnInteger &&
		q ->InterfaceAddr.NotAnInteger &&
		rr->InterfaceAddr.NotAnInteger != q->InterfaceAddr.NotAnInteger) return(mDNSfalse);

	// RR type CNAME matches any query type. QTYPE ANY matches any RR type. QCLASS ANY matches any RR class.
	if (rr->rrtype != kDNSType_CNAME && rr->rrtype  != q->rrtype  && q->rrtype  != kDNSQType_ANY ) return(mDNSfalse);
	if (                                rr->rrclass != q->rrclass && q->rrclass != kDNSQClass_ANY) return(mDNSfalse);
	return(SameDomainName(&rr->name, &q->name));
	}

// SameResourceRecordSignature returns true if two resources records have the same interface, name, type, and class.
// -- i.e. if they would both be given in response to the same question.
// (TTL and rdata may differ)
mDNSlocal mDNSBool SameResourceRecordSignature(const ResourceRecord *const r1, const ResourceRecord *const r2)
	{
	if (!r1) { debugf("SameResourceRecordSignature ERROR: r1 is NULL"); return(mDNSfalse); }
	if (!r2) { debugf("SameResourceRecordSignature ERROR: r2 is NULL"); return(mDNSfalse); }
	if (r1->InterfaceAddr.NotAnInteger &&
		r2->InterfaceAddr.NotAnInteger &&
		r1->InterfaceAddr.NotAnInteger != r2->InterfaceAddr.NotAnInteger) return(mDNSfalse);
	return (r1->rrtype == r2->rrtype && r1->rrclass == r2->rrclass && SameDomainName(&r1->name, &r2->name));
	}

// SameResourceRecordSignatureAnyInterface returns true if two resources records have the same name, type, and class.
// (InterfaceAddr, TTL and rdata may differ)
mDNSlocal mDNSBool SameResourceRecordSignatureAnyInterface(const ResourceRecord *const r1, const ResourceRecord *const r2)
	{
	if (!r1) { debugf("SameResourceRecordSignatureAnyInterface ERROR: r1 is NULL"); return(mDNSfalse); }
	if (!r2) { debugf("SameResourceRecordSignatureAnyInterface ERROR: r2 is NULL"); return(mDNSfalse); }
	return (r1->rrtype == r2->rrtype && r1->rrclass == r2->rrclass && SameDomainName(&r1->name, &r2->name));
	}

// IdenticalResourceRecord returns true if two resources records have
// the same interface, name, type, class, and identical rdata (TTL may differ)
mDNSlocal mDNSBool IdenticalResourceRecord(const ResourceRecord *const r1, const ResourceRecord *const r2)
	{
	if (!SameResourceRecordSignature(r1, r2)) return(mDNSfalse);
	return(SameRData(r1->rrtype, r1->rdata, r2->rdata));
	}

// IdenticalResourceRecordAnyInterface returns true if two resources records have
// the same name, type, class, and identical rdata (InterfaceAddr and TTL may differ)
mDNSlocal mDNSBool IdenticalResourceRecordAnyInterface(const ResourceRecord *const r1, const ResourceRecord *const r2)
	{
	if (!SameResourceRecordSignatureAnyInterface(r1, r2)) return(mDNSfalse);
	return(SameRData(r1->rrtype, r1->rdata, r2->rdata));
	}

// ResourceRecord *ds is the ResourceRecord from the duplicate suppression section of the query
// This is the information that the requester believes to be correct
// ResourceRecord *rr is the answer we are proposing to give, if not suppressed
// This is the information that we believe to be correct
mDNSlocal mDNSBool SuppressDuplicate(const ResourceRecord *const ds, const ResourceRecord *const rr)
	{
	// If RR signature is different, or data is different, then don't suppress
	if (!IdenticalResourceRecord(ds,rr)) return(mDNSfalse);
	
	// If the requester's indicated TTL is less than half the real TTL,
	// we need to give our answer before the requester's copy expires.
	// If the requester's indicated TTL is at least half the real TTL,
	// then we can suppress our answer this time.
	// If the requester's indicated TTL is greater than the TTL we believe,
	// then that's okay, and we don't need to do anything about it.
	// (If two responders on the network are offering the same information,
	// that's okay, and if they are offering the information with different TTLs,
	// the one offering the lower TTL should defer to the one offering the higher TTL.)
	return(ds->rroriginalttl >= rr->rroriginalttl / 2);
	}

mDNSlocal mDNSu32 GetRDLength(const ResourceRecord *const rr, mDNSBool estimate)
	{
	const domainname *const name = estimate ? &rr->name : mDNSNULL;
	switch (rr->rrtype)
		{
		case kDNSType_A:	return(sizeof(rr->rdata->u.ip)); break;
		case kDNSType_CNAME:// Same as PTR
		case kDNSType_PTR:	return(CompressedDomainNameLength(&rr->rdata->u.name, name));
		case kDNSType_TXT:  return(rr->rdata->RDLength); // TXT is not self-describing, so have to just trust rdlength
		case kDNSType_SRV:	return(6 + CompressedDomainNameLength(&rr->rdata->u.srv.target, name));
		default:			debugf("Warning! Don't know how to get length of resource type %d", rr->rrtype);
							return(rr->rdata->RDLength);
		}
	}

// rr is a ResourceRecord in our cache
// (kDNSRecordTypePacketAnswer/kDNSRecordTypePacketAdditional/kDNSRecordTypePacketUniqueAns/kDNSRecordTypePacketUniqueAdd)
mDNSlocal DNSQuestion *CacheRRActive(const mDNS *const m, ResourceRecord *rr)
	{
	DNSQuestion *q;
	for (q = m->ActiveQuestions; q; q=q->next)		// Scan our list of questions
		if (!q->DuplicateOf && ResourceRecordAnswersQuestion(rr, q))
			return(q);
	return(mDNSNULL);
	}

mDNSlocal void SetTargetToHostName(const mDNS *const m, ResourceRecord *const rr)
	{
	switch (rr->rrtype)
		{
		case kDNSType_CNAME:// Same as PTR
		case kDNSType_PTR:	rr->rdata->u.name       = m->hostname1; break;
		case kDNSType_SRV:	rr->rdata->u.srv.target = m->hostname1; break;
		default: debugf("SetTargetToHostName: Dont' know how to set the target of rrtype %d", rr->rrtype); break;
		}
	rr->rdata->RDLength = GetRDLength(rr, mDNSfalse);
	rr->rdestimate      = GetRDLength(rr, mDNStrue);
	
	// If we're in the middle of probing this record, we need to start again,
	// because changing its rdata may change the outcome of the tie-breaker.
	rr->ProbeCount       = DefaultProbeCountForRecordType(rr->RecordType);
	rr->AnnounceCount    = DefaultAnnounceCountForRecordType(rr->RecordType);
	rr->NextSendTime     = mDNSPlatformTimeNow();
	rr->NextSendInterval = DefaultSendIntervalForRecordType(rr->RecordType);
	if (rr->RecordType == kDNSRecordTypeUnique && m->SuppressProbes) rr->NextSendTime = m->SuppressProbes;
	}

mDNSlocal void UpdateHostNameTargets(const mDNS *const m)
	{
	ResourceRecord *rr;
	for (rr = m->ResourceRecords; rr; rr=rr->next)
		if (rr->HostTarget)
			SetTargetToHostName(m, rr);
	}

mDNSlocal mStatus mDNS_Register_internal(mDNS *const m, ResourceRecord *const rr, const mDNSs32 timenow)
	{
	ResourceRecord **p = &m->ResourceRecords;
	while (*p && *p != rr) p=&(*p)->next;
	if (*p)
		{
		debugf("Error! Tried to register a ResourceRecord that's already in the list");
		return(mStatus_AlreadyRegistered);
		}

	if (rr->DependentOn)
		{
		if (rr->RecordType == kDNSRecordTypeUnique)
			rr->RecordType =  kDNSRecordTypeVerified;
		else
			{
			debugf("mDNS_Register_internal: ERROR! %##s: rr->DependentOn && RecordType != kDNSRecordTypeUnique",
				rr->name.c);
			return(mStatus_Invalid);
			}
		if (rr->DependentOn->RecordType != kDNSRecordTypeUnique && rr->DependentOn->RecordType != kDNSRecordTypeVerified)
			{
			debugf("mDNS_Register_internal: ERROR! %##s: rr->DependentOn->RecordType bad type %X",
				rr->name.c, rr->DependentOn->RecordType);
			return(mStatus_Invalid);
			}
		}

	rr->next = mDNSNULL;

	// Field Group 1: Persistent metadata for Authoritative Records
//	rr->Additional1       = set to mDNSNULL in mDNS_SetupResourceRecord; may be overridden by client
//	rr->Additional2       = set to mDNSNULL in mDNS_SetupResourceRecord; may be overridden by client
//	rr->DependentOn       = set to mDNSNULL in mDNS_SetupResourceRecord; may be overridden by client
//	rr->RRSet             = set to mDNSNULL in mDNS_SetupResourceRecord; may be overridden by client
//	rr->Callback          = already set in mDNS_SetupResourceRecord
//	rr->Context           = already set in mDNS_SetupResourceRecord
//	rr->RecordType        = already set in mDNS_SetupResourceRecord
//	rr->HostTarget        = set to mDNSNULL in mDNS_SetupResourceRecord; may be overridden by client

	// Field Group 2: Transient state for Authoritative Records
	rr->Acknowledged      = mDNSfalse;
	rr->ProbeCount        = DefaultProbeCountForRecordType(rr->RecordType);
	rr->AnnounceCount     = DefaultAnnounceCountForRecordType(rr->RecordType);
	rr->IncludeInProbe    = mDNSfalse;
	rr->SendPriority      = 0;
	rr->Requester         = zeroIPAddr;
	rr->NextResponse      = mDNSNULL;
	rr->NR_AnswerTo       = mDNSNULL;
	rr->NR_AdditionalTo   = mDNSNULL;
	rr->LastSendTime      = timenow - mDNSPlatformOneSecond;
	rr->NextSendTime      = timenow;
	if (rr->RecordType == kDNSRecordTypeUnique && m->SuppressProbes) rr->NextSendTime = m->SuppressProbes;
	rr->NextSendInterval  = DefaultSendIntervalForRecordType(rr->RecordType);
	rr->NewRData          = mDNSNULL;
	rr->UpdateCallback    = mDNSNULL;

	// Field Group 3: Transient state for Cache Records
	rr->NextDupSuppress   = mDNSNULL;	// Not strictly relevant for a local record
	rr->TimeRcvd          = 0;			// Not strictly relevant for a local record
	rr->LastUsed          = 0;			// Not strictly relevant for a local record
	rr->UseCount          = 0;			// Not strictly relevant for a local record
	rr->UnansweredQueries = 0;			// Not strictly relevant for a local record
	rr->Active            = mDNSfalse;	// Not strictly relevant for a local record
	rr->NewData           = mDNSfalse;	// Not strictly relevant for a local record

	// Field Group 4: The actual information pertaining to this resource record
//	rr->interface         = already set in mDNS_SetupResourceRecord
//	rr->name.c            = MUST be set by client
//	rr->rrtype            = already set in mDNS_SetupResourceRecord
//	rr->rrclass           = already set in mDNS_SetupResourceRecord
//	rr->rroriginalttl     = already set in mDNS_SetupResourceRecord
//	rr->rrremainingttl    = already set in mDNS_SetupResourceRecord

	if (rr->HostTarget)
		SetTargetToHostName(m, rr);	// This also sets rdlength and rdestimate for us
	else
		{
		rr->rdata->RDLength = GetRDLength(rr, mDNSfalse);
		rr->rdestimate      = GetRDLength(rr, mDNStrue);
		}
//	rr->rdata             = MUST be set by client

	*p = rr;
	return(mStatus_NoError);
	}

// mDNS_Dereg_normal is used for most calls to mDNS_Deregister_internal
// mDNS_Dereg_conflict is used to indicate that this record is being forcibly deregistered because of a conflict
// mDNS_Dereg_repeat is used when cleaning up, for records that may have already been forcibly deregistered
typedef enum { mDNS_Dereg_normal, mDNS_Dereg_conflict, mDNS_Dereg_repeat } mDNS_Dereg_type;

// NOTE: mDNS_Deregister_internal can call a user callback, which may change the record list and/or question list.
// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
mDNSlocal void mDNS_Deregister_internal(mDNS *const m, ResourceRecord *const rr, const mDNSs32 timenow, mDNS_Dereg_type drt)
	{
	mDNSu8 RecordType = rr->RecordType;
	// If this is a shared record and we've announced it at least once,
	// we need to retract that announcement before we delete the record
	if (RecordType == kDNSRecordTypeShared && rr->AnnounceCount <= DefaultAnnounceCountForTypeShared)
		{
		debugf("mDNS_Deregister_internal: Sending deregister for %##s (%s)", rr->name.c, DNSTypeName(rr->rrtype));
		rr->RecordType     = kDNSRecordTypeDeregistering;
		rr->rroriginalttl  = 0;
		rr->rrremainingttl = 0;
		}
	else
		{
		// Find this record in our list of active records
		ResourceRecord **p = &m->ResourceRecords;
		while (*p && *p != rr) p=&(*p)->next;

		if (*p) *p = rr->next;
		else
			{
			// No need to give an error message if we already know this is a potentially repeated deregistration
			if (drt != mDNS_Dereg_repeat)
				debugf("mDNS_Deregister_internal: Record %##s (%s) not found in list", rr->name.c, DNSTypeName(rr->rrtype));
			return;
			}
		// If someone is about to look at this, bump the pointer forward
		if (m->CurrentRecord == rr) m->CurrentRecord = rr->next;
		rr->next = mDNSNULL;

		if      (RecordType == kDNSRecordTypeUnregistered)
			debugf("mDNS_Deregister_internal: Record %##s (%s) already marked kDNSRecordTypeUnregistered", rr->name.c, DNSTypeName(rr->rrtype));
		else if (RecordType == kDNSRecordTypeDeregistering)
			debugf("mDNS_Deregister_internal: Record %##s (%s) already marked kDNSRecordTypeDeregistering", rr->name.c, DNSTypeName(rr->rrtype));
		else
			{
			debugf("mDNS_Deregister_internal: Deleting record for %##s (%s)", rr->name.c, DNSTypeName(rr->rrtype));
			rr->RecordType = kDNSRecordTypeUnregistered;
			}

		if ((drt == mDNS_Dereg_conflict || drt == mDNS_Dereg_repeat) && RecordType == kDNSRecordTypeShared)
			debugf("mDNS_Deregister_internal: Cannot have a conflict on a shared record! %##s (%s)", rr->name.c, DNSTypeName(rr->rrtype));

		// If we have an update queued up which never executed, give the client a chance to free that memory
		if (rr->NewRData)
			{
			RData *OldRData = rr->rdata;
			rr->rdata = rr->NewRData;	// Update our rdata
			rr->NewRData = mDNSNULL;	// Clear the NewRData pointer ...
			if (rr->UpdateCallback) rr->UpdateCallback(m, rr, OldRData);	// ... and let the client know
			}
		
		if (RecordType == kDNSRecordTypeShared && rr->Callback)
			rr->Callback(m, rr, mStatus_MemFree);
		else if (drt == mDNS_Dereg_conflict)
			{
			m->SuppressProbes = timenow + mDNSPlatformOneSecond;
			if (m->SuppressProbes == 0) m->SuppressProbes = 1;
			if (rr->Callback) rr->Callback(m, rr, mStatus_NameConflict);
			}
		}
	}

// ***************************************************************************
#if 0
#pragma mark -
#pragma mark -
#pragma mark - DNS Message Creation Functions
#endif

mDNSlocal void InitializeDNSMessage(DNSMessageHeader *h, mDNSOpaque16 id, mDNSOpaque16 flags)
	{
	h->id             = id;
	h->flags          = flags;
	h->numQuestions   = 0;
	h->numAnswers     = 0;
	h->numAuthorities = 0;
	h->numAdditionals = 0;
	}

mDNSlocal const mDNSu8 *FindCompressionPointer(const mDNSu8 *const base, const mDNSu8 *const end, const mDNSu8 *const domname)
	{
	const mDNSu8 *result = end - *domname - 1;

	if (*domname == 0) return(mDNSNULL);	// There's no point trying to match just the root label
	
	// This loop examines each possible starting position in packet, starting end of the packet and working backwards
	while (result >= base)
		{
		// If the length byte and first character of the label match, then check further to see
		// if this location in the packet will yield a useful name compression pointer.
		if (result[0] == domname[0] && result[1] == domname[1])
			{
			const mDNSu8 *name = domname;
			const mDNSu8 *targ = result;
			while (targ + *name < end)
				{
				// First see if this label matches
				int i;
				const mDNSu8 *pointertarget;
				for (i=0; i <= *name; i++) if (targ[i] != name[i]) break;
				if (i <= *name) break;							// If label did not match, bail out
				targ += 1 + *name;								// Else, did match, so advance target pointer
				name += 1 + *name;								// and proceed to check next label
				if (*name == 0 && *targ == 0) return(result);	// If no more labels, we found a match!
				if (*name == 0) break;							// If no more labels to match, we failed, so bail out

				// The label matched, so now follow the pointer (if appropriate) and then see if the next label matches
				if (targ[0] < 0x40) continue;					// If length value, continue to check next label
				if (targ[0] < 0xC0) break;						// If 40-BF, not valid
				if (targ+1 >= end) break;						// Second byte not present!
				pointertarget = base + (((mDNSu16)(targ[0] & 0x3F)) << 8) + targ[1];
				if (targ < pointertarget) break;				// Pointertarget must point *backwards* in the packet
				if (pointertarget[0] >= 0x40) break;			// Pointertarget must point to a valid length byte
				targ = pointertarget;
				}
			}
		result--;	// We failed to match at this search position, so back up the tentative result pointer and try again
		}
	return(mDNSNULL);
	}

// Put a string of dot-separated labels as length-prefixed labels
// domainname is a fully-qualified name (i.e. assumed to be ending in a dot, even if it doesn't)
// msg points to the message we're building (pass mDNSNULL if we don't want to use compression pointers)
// end points to the end of the message so far
// ptr points to where we want to put the name
// limit points to one byte past the end of the buffer that we must not overrun
// domainname is the name to put
mDNSlocal mDNSu8 *putDomainNameAsLabels(const DNSMessage *const msg,
	mDNSu8 *ptr, const mDNSu8 *const limit, const domainname *const name)
	{
	const mDNSu8 *const base        = (const mDNSu8 *const)msg;
	const mDNSu8 *      np          = name->c;
	const mDNSu8 *const max         = name->c + MAX_DOMAIN_NAME;	// Maximum that's valid
	const mDNSu8 *      pointer     = mDNSNULL;
	const mDNSu8 *const searchlimit = ptr;

	while (*np && ptr < limit-1)		// While we've got characters in the name, and space to write them in the message...
		{
		if (np + 1 + *np >= max)
			{ debugf("Malformed domain name (more than 255 characters)"); return(mDNSNULL); }

		if (base) pointer = FindCompressionPointer(base, searchlimit, np);
		if (pointer)					// Use a compression pointer if we can
			{
			mDNSu16 offset = (mDNSu16)(pointer - base);
			*ptr++ = (mDNSu8)(0xC0 | (offset >> 8));
			*ptr++ = (mDNSu8)(        offset      );
			return(ptr);
			}
		else							// Else copy one label and try again
			{
			int i;
			mDNSu8 len = *np++;
			if (ptr + 1 + len >= limit) return(mDNSNULL);
			*ptr++ = len;
			for (i=0; i<len; i++) *ptr++ = *np++;
			}
		}

	if (ptr < limit)												// If we didn't run out of space
		{
		*ptr++ = 0;													// Put the final root label
		return(ptr);												// and return
		}

	return(mDNSNULL);
	}

mDNSlocal mDNSu8 *putRData(const DNSMessage *const msg, mDNSu8 *ptr, const mDNSu8 *const limit,
	const mDNSu16 rrtype, const RData *const rdata)
	{
	switch (rrtype)
		{
		case kDNSType_A:	if (rdata->RDLength != 4)
								{
								debugf("putRData: Illegal length %d for kDNSType_A", rdata->RDLength);
								return(mDNSNULL);
								}
							if (ptr + 4 > limit) return(mDNSNULL);
							*ptr++ = rdata->u.ip.b[0];
							*ptr++ = rdata->u.ip.b[1];
							*ptr++ = rdata->u.ip.b[2];
							*ptr++ = rdata->u.ip.b[3];
							return(ptr);

		case kDNSType_CNAME:// Same as PTR
		case kDNSType_PTR:	return(putDomainNameAsLabels(msg, ptr, limit, &rdata->u.name));

		case kDNSType_TXT:  if (ptr + rdata->RDLength > limit) return(mDNSNULL);
							mDNSPlatformMemCopy(rdata->u.data, ptr, rdata->RDLength);
							return(ptr + rdata->RDLength);

		case kDNSType_SRV:	if (ptr + 6 > limit) return(mDNSNULL);
							*ptr++ = (mDNSu8)(rdata->u.srv.priority >> 8);
							*ptr++ = (mDNSu8)(rdata->u.srv.priority     );
							*ptr++ = (mDNSu8)(rdata->u.srv.weight   >> 8);
							*ptr++ = (mDNSu8)(rdata->u.srv.weight       );
							*ptr++ = rdata->u.srv.port.b[0];
							*ptr++ = rdata->u.srv.port.b[1];
							return(putDomainNameAsLabels(msg, ptr, limit, &rdata->u.srv.target));

		default:			if (ptr + rdata->RDLength > limit) return(mDNSNULL);
							debugf("putRData: Warning! Writing resource type %d as raw data", rrtype);
							mDNSPlatformMemCopy(rdata->u.data, ptr, rdata->RDLength);
							return(ptr + rdata->RDLength);
		}
	}

// Put a domain name, type, class, ttl, length, and type-specific data
// domainname is a fully-qualified name
// Only pass the "m" and "timenow" parameters in cases where the LastSendTime is to be updated,
// and the kDNSClass_UniqueRRSet bit set
mDNSlocal mDNSu8 *putResourceRecord(DNSMessage *const msg, mDNSu8 *ptr,
	mDNSu16 *count, ResourceRecord *rr, mDNS *const m, const mDNSs32 timenow)
	{
	mDNSu8 *endofrdata;
	mDNSu32 actualLength;
	const mDNSu8 *limit = msg->data + AbsoluteMaxDNSMessageData;
	
	// If we have a single large record to put in the packet, then we allow the packet to be up to 9K bytes,
	// but in the normal case we try to keep the packets below 1500 to avoid IP fragmentation on standard Ethernet
	if (msg->h.numAnswers || msg->h.numAuthorities || msg->h.numAdditionals)
		limit = msg->data + NormalMaxDNSMessageData;

	if (rr->RecordType == kDNSRecordTypeUnregistered)
		{
		debugf("putResourceRecord ERROR! Attempt to put kDNSRecordTypeUnregistered");
		return(ptr);
		}

	ptr = putDomainNameAsLabels(msg, ptr, limit, &rr->name);
	if (!ptr || ptr + 10 >= limit) return(mDNSNULL);	// If we're out-of-space, return mDNSNULL
	ptr[0] = (mDNSu8)(rr->rrtype  >> 8);
	ptr[1] = (mDNSu8)(rr->rrtype      );
	ptr[2] = (mDNSu8)(rr->rrclass >> 8);
	ptr[3] = (mDNSu8)(rr->rrclass     );
	ptr[4] = (mDNSu8)(rr->rrremainingttl >> 24);
	ptr[5] = (mDNSu8)(rr->rrremainingttl >> 16);
	ptr[6] = (mDNSu8)(rr->rrremainingttl >>  8);
	ptr[7] = (mDNSu8)(rr->rrremainingttl      );
	endofrdata = putRData(msg, ptr+10, limit, rr->rrtype, rr->rdata);
	if (!endofrdata) { debugf("Ran out of space in putResourceRecord!"); return(mDNSNULL); }

	// Go back and fill in the actual number of data bytes we wrote
	// (actualLength can be less than rdlength when domain name compression is used)
	actualLength = (mDNSu32)(endofrdata - ptr - 10);
	ptr[8] = (mDNSu8)(actualLength >> 8);
	ptr[9] = (mDNSu8)(actualLength     );

	if (m)														// If the 'm' parameter was passed in...
		{
		rr->LastSendTime = timenow;								// ... then update LastSendTime
		if (rr->RecordType & kDNSRecordTypeUniqueMask)			// If it is supposed to be unique
			{
			const ResourceRecord *a = mDNSNULL;
			// If we find a member of the same RRSet (same name/type/class)
			// that hasn't been updated within the last quarter second, don't set the bit
			for (a = m->ResourceRecords; a; a=a->next)
				if (SameResourceRecordSignatureAnyInterface(rr, a))
					if (timenow - a->LastSendTime > mDNSPlatformOneSecond/4)
						break;
			if (a == mDNSNULL)
				ptr[2] |= kDNSClass_UniqueRRSet >> 8;
			}
		}

	(*count)++;
	return(endofrdata);
	}

#if 0
mDNSlocal mDNSu8 *putEmptyResourceRecord(DNSMessage *const msg, mDNSu8 *ptr, const mDNSu8 *const limit,
	mDNSu16 *count, const ResourceRecord *rr)
	{
	ptr = putDomainNameAsLabels(msg, ptr, limit, &rr->name);
	if (!ptr || ptr + 10 > limit) return(mDNSNULL);		// If we're out-of-space, return mDNSNULL
	ptr[0] = (mDNSu8)(rr->rrtype  >> 8);				// Put type
	ptr[1] = (mDNSu8)(rr->rrtype      );
	ptr[2] = (mDNSu8)(rr->rrclass >> 8);				// Put class
	ptr[3] = (mDNSu8)(rr->rrclass     );
	ptr[4] = ptr[5] = ptr[6] = ptr[7] = 0;				// TTL is zero
	ptr[8] = ptr[9] = 0;								// RDATA length is zero
	(*count)++;
	return(ptr + 10);
	}
#endif

mDNSlocal mDNSu8 *putQuestion(DNSMessage *const msg, mDNSu8 *ptr, const mDNSu8 *const limit,
							const domainname *const name, mDNSu16 rrtype, mDNSu16 rrclass)
	{
	ptr = putDomainNameAsLabels(msg, ptr, limit, name);
	if (!ptr || ptr+4 >= limit) return(mDNSNULL);			// If we're out-of-space, return mDNSNULL
	ptr[0] = (mDNSu8)(rrtype  >> 8);
	ptr[1] = (mDNSu8)(rrtype      );
	ptr[2] = (mDNSu8)(rrclass >> 8);
	ptr[3] = (mDNSu8)(rrclass     );
	msg->h.numQuestions++;
	return(ptr+4);
	}

// ***************************************************************************
#if 0
#pragma mark -
#pragma mark - DNS Message Parsing Functions
#endif

mDNSlocal const mDNSu8 *skipDomainName(const DNSMessage *const msg, const mDNSu8 *ptr, const mDNSu8 *const end)
	{
	mDNSu32 total = 0;

	if (ptr < (mDNSu8*)msg || ptr >= end)
		{ debugf("skipDomainName: Illegal ptr not within packet boundaries"); return(mDNSNULL); }

	while (1)						// Read sequence of labels
		{
		const mDNSu8 len = *ptr++;	// Read length of this label
		if (len == 0) return(ptr);	// If length is zero, that means this name is complete
		switch (len & 0xC0)
			{
			case 0x00:	if (ptr + len >= end)					// Remember: expect at least one more byte for the root label
							{ debugf("skipDomainName: Malformed domain name (overruns packet end)"); return(mDNSNULL); }
						if (total + 1 + len >= MAX_DOMAIN_NAME)	// Remember: expect at least one more byte for the root label
							{ debugf("skipDomainName: Malformed domain name (more than 255 characters)"); return(mDNSNULL); }
						ptr += len;
						total += 1 + len;
						break;

			case 0x40:	debugf("skipDomainName: Extended EDNS0 label types 0x%X not supported", len); return(mDNSNULL);
			case 0x80:	debugf("skipDomainName: Illegal label length 0x%X", len); return(mDNSNULL);
			case 0xC0:	return(ptr+1);
			}
		}
	}

// Routine to fetch an FQDN from the DNS message, following compression pointers if necessary.
mDNSlocal const mDNSu8 *getDomainName(const DNSMessage *const msg, const mDNSu8 *ptr, const mDNSu8 *const end,
	domainname *const name)
	{
	const mDNSu8 *nextbyte = mDNSNULL;					// Record where we got to before we started following pointers
	mDNSu8       *np = name->c;							// Name pointer
	const mDNSu8 *const limit = np + MAX_DOMAIN_NAME;	// Limit so we don't overrun buffer

	if (ptr < (mDNSu8*)msg || ptr >= end)
		{ debugf("getDomainName: Illegal ptr not within packet boundaries"); return(mDNSNULL); }

	*np = 0;						// Tentatively place the root label here (may be overwritten if we have more labels)

	while (1)						// Read sequence of labels
		{
		const mDNSu8 len = *ptr++;	// Read length of this label
		if (len == 0) break;		// If length is zero, that means this name is complete
		switch (len & 0xC0)
			{
			int i;
			mDNSu16 offset;

			case 0x00:	if (ptr + len >= end)		// Remember: expect at least one more byte for the root label
							{ debugf("getDomainName: Malformed domain name (overruns packet end)"); return(mDNSNULL); }
						if (np + 1 + len >= limit)	// Remember: expect at least one more byte for the root label
							{ debugf("getDomainName: Malformed domain name (more than 255 characters)"); return(mDNSNULL); }
						*np++ = len;
						for (i=0; i<len; i++) *np++ = *ptr++;
						*np = 0;	// Tentatively place the root label here (may be overwritten if we have more labels)
						break;

			case 0x40:	debugf("getDomainName: Extended EDNS0 label types 0x%X not supported in name %##s", len, name->c);
						return(mDNSNULL);

			case 0x80:	debugf("getDomainName: Illegal label length 0x%X in domain name %##s", len, name->c); return(mDNSNULL);

			case 0xC0:	offset = (mDNSu16)((((mDNSu16)(len & 0x3F)) << 8) | *ptr++);
						if (!nextbyte) nextbyte = ptr;	// Record where we got to before we started following pointers
						ptr = (mDNSu8 *)msg + offset;
						if (ptr < (mDNSu8*)msg || ptr >= end)
							{ debugf("getDomainName: Illegal compression pointer not within packet boundaries"); return(mDNSNULL); }
						if (*ptr & 0xC0)
							{ debugf("getDomainName: Compression pointer must point to real label"); return(mDNSNULL); }
						break;
			}
		}
	
	if (nextbyte) return(nextbyte);
	else return(ptr);
	}

mDNSlocal const mDNSu8 *skipResourceRecord(const DNSMessage *msg, const mDNSu8 *ptr, const mDNSu8 *end)
	{
	mDNSu16 pktrdlength;

	ptr = skipDomainName(msg, ptr, end);
	if (!ptr) { debugf("skipResourceRecord: Malformed RR name"); return(mDNSNULL); }
	
	if (ptr + 10 > end) { debugf("skipResourceRecord: Malformed RR -- no type/class/ttl/len!"); return(mDNSNULL); }
	pktrdlength = (mDNSu16)((mDNSu16)ptr[8] <<  8 | ptr[9]);
	ptr += 10;
	if (ptr + pktrdlength > end) { debugf("skipResourceRecord: RDATA exceeds end of packet"); return(mDNSNULL); }

	return(ptr + pktrdlength);
	}

mDNSlocal const mDNSu8 *getResourceRecord(const DNSMessage *msg, const mDNSu8 *ptr, const mDNSu8 *end,
	const mDNSIPAddr InterfaceAddr, const mDNSs32 timenow, mDNSu8 RecordType, ResourceRecord *rr, RData *RDataStorage)
	{
	mDNSu16 pktrdlength;

	rr->next              = mDNSNULL;
	
	// Field Group 1: Persistent metadata for Authoritative Records
	rr->Additional1       = mDNSNULL;
	rr->Additional2       = mDNSNULL;
	rr->DependentOn       = mDNSNULL;
	rr->RRSet             = mDNSNULL;
	rr->Callback          = mDNSNULL;
	rr->Context           = mDNSNULL;
	rr->RecordType        = RecordType;
	rr->HostTarget        = mDNSfalse;

	// Field Group 2: Transient state for Authoritative Records
	rr->Acknowledged      = mDNSfalse;
	rr->ProbeCount        = 0;
	rr->AnnounceCount     = 0;
	rr->IncludeInProbe    = mDNSfalse;
	rr->SendPriority      = 0;
	rr->Requester         = zeroIPAddr;
	rr->NextResponse      = mDNSNULL;
	rr->NR_AnswerTo       = mDNSNULL;
	rr->NR_AdditionalTo   = mDNSNULL;
	rr->LastSendTime      = 0;
	rr->NextSendTime      = 0;
	rr->NextSendInterval  = 0;
	rr->NewRData          = mDNSNULL;
	rr->UpdateCallback    = mDNSNULL;

	// Field Group 3: Transient state for Cache Records
	rr->NextDupSuppress   = mDNSNULL;
	rr->TimeRcvd          = timenow;
	rr->LastUsed          = timenow;
	rr->UseCount          = 0;
	rr->UnansweredQueries = 0;
	rr->Active            = mDNSfalse;
	rr->NewData           = mDNStrue;

	// Field Group 4: The actual information pertaining to this resource record
	rr->InterfaceAddr     = InterfaceAddr;
	ptr = getDomainName(msg, ptr, end, &rr->name);
	if (!ptr) { debugf("getResourceRecord: Malformed RR name"); return(mDNSNULL); }

	if (ptr + 10 > end) { debugf("getResourceRecord: Malformed RR -- no type/class/ttl/len!"); return(mDNSNULL); }
	
	rr->rrtype            = (mDNSu16)((mDNSu16)ptr[0] <<  8 | ptr[1]);
	rr->rrclass           = (mDNSu16)((mDNSu16)ptr[2] <<  8 | ptr[3]) & kDNSQClass_Mask;
	rr->rroriginalttl     = (mDNSu32)((mDNSu32)ptr[4] << 24 | (mDNSu32)ptr[5] << 16 | (mDNSu32)ptr[6] << 8 | ptr[7]);
	if (rr->rroriginalttl > 0x70000000UL / mDNSPlatformOneSecond)
		rr->rroriginalttl = 0x70000000UL / mDNSPlatformOneSecond;
	rr->rrremainingttl    = 0;
	pktrdlength           = (mDNSu16)((mDNSu16)ptr[8] <<  8 | ptr[9]);
	if (ptr[2] & (kDNSClass_UniqueRRSet >> 8))
		rr->RecordType |= kDNSRecordTypeUniqueMask;
	ptr += 10;
	if (ptr + pktrdlength > end) { debugf("getResourceRecord: RDATA exceeds end of packet"); return(mDNSNULL); }

	if (RDataStorage)
		rr->rdata = RDataStorage;
	else
		{
		rr->rdata = &rr->rdatastorage;
		rr->rdata->MaxRDLength = sizeof(RDataBody);
		}

	switch (rr->rrtype)
		{
		case kDNSType_A:	rr->rdata->u.ip.b[0] = ptr[0];
							rr->rdata->u.ip.b[1] = ptr[1];
							rr->rdata->u.ip.b[2] = ptr[2];
							rr->rdata->u.ip.b[3] = ptr[3];
							break;

		case kDNSType_CNAME:// CNAME is same as PTR
		case kDNSType_PTR:	if (!getDomainName(msg, ptr, end, &rr->rdata->u.name))
								{ debugf("getResourceRecord: Malformed CNAME/PTR RDATA name"); return(mDNSNULL); }
							//debugf("%##s PTR %##s rdlen %d", rr->name.c, rr->rdata->u.name.c, pktrdlength);
							break;

		case kDNSType_TXT:  if (pktrdlength > rr->rdata->MaxRDLength)
								{
								debugf("getResourceRecord: TXT rdata size (%d) exceeds storage (%d)",
									pktrdlength, rr->rdata->MaxRDLength);
								return(mDNSNULL);
								}
							rr->rdata->RDLength = pktrdlength;
							mDNSPlatformMemCopy(ptr, rr->rdata->u.data, pktrdlength);
							break;

		case kDNSType_SRV:	rr->rdata->u.srv.priority = (mDNSu16)((mDNSu16)ptr[0] <<  8 | ptr[1]);
							rr->rdata->u.srv.weight   = (mDNSu16)((mDNSu16)ptr[2] <<  8 | ptr[3]);
							rr->rdata->u.srv.port.b[0] = ptr[4];
							rr->rdata->u.srv.port.b[1] = ptr[5];
							if (!getDomainName(msg, ptr+6, end, &rr->rdata->u.srv.target))
								{ debugf("getResourceRecord: Malformed SRV RDATA name"); return(mDNSNULL); }
							//debugf("%##s SRV %##s rdlen %d", rr->name.c, rr->rdata->u.srv.target.c, pktrdlength);
							break;

		default:			if (pktrdlength > rr->rdata->MaxRDLength)
								{
								debugf("getResourceRecord: rdata %d size (%d) exceeds storage (%d)",
									rr->rrtype, pktrdlength, rr->rdata->MaxRDLength);
								return(mDNSNULL);
								}
							debugf("getResourceRecord: Warning! Reading resource type %d as opaque data", rr->rrtype);
							// Note: Just because we don't understand the record type, that doesn't
							// mean we fail. The DNS protocol specifies rdlength, so we can
							// safely skip over unknown records and ignore them.
							// We also grab a binary copy of the rdata anyway, since the caller
							// might know how to interpret it even if we don't.
							rr->rdata->RDLength = pktrdlength;
							mDNSPlatformMemCopy(ptr, rr->rdata->u.data, pktrdlength);
							break;
		}

	rr->rdata->RDLength   = GetRDLength(rr, mDNSfalse);
	rr->rdestimate        = GetRDLength(rr, mDNStrue);
	return(ptr + pktrdlength);
	}

mDNSlocal const mDNSu8 *skipQuestion(const DNSMessage *msg, const mDNSu8 *ptr, const mDNSu8 *end)
	{
	ptr = skipDomainName(msg, ptr, end);
	if (!ptr) { debugf("skipQuestion: Malformed domain name in DNS question section"); return(mDNSNULL); }
	if (ptr+4 > end) { debugf("skipQuestion: Malformed DNS question section -- no query type and class!"); return(mDNSNULL); }
	return(ptr+4);
	}

mDNSlocal const mDNSu8 *getQuestion(const DNSMessage *msg, const mDNSu8 *ptr, const mDNSu8 *end, const mDNSIPAddr InterfaceAddr,
	DNSQuestion *question)
	{
	question->InterfaceAddr = InterfaceAddr;
	ptr = getDomainName(msg, ptr, end, &question->name);
	if (!ptr) { debugf("Malformed domain name in DNS question section"); return(mDNSNULL); }
	if (ptr+4 > end) { debugf("Malformed DNS question section -- no query type and class!"); return(mDNSNULL); }
	
	question->rrtype  = (mDNSu16)((mDNSu16)ptr[0] << 8 | ptr[1]);			// Get type
	question->rrclass = (mDNSu16)((mDNSu16)ptr[2] << 8 | ptr[3]);			// and class
	return(ptr+4);
	}

mDNSlocal const mDNSu8 *LocateAnswers(const DNSMessage *const msg, const mDNSu8 *const end)
	{
	int i;
	const mDNSu8 *ptr = msg->data;
	for (i = 0; i < msg->h.numQuestions && ptr; i++) ptr = skipQuestion(msg, ptr, end);
	return(ptr);
	}

mDNSlocal const mDNSu8 *LocateAuthorities(const DNSMessage *const msg, const mDNSu8 *const end)
	{
	int i;
	const mDNSu8 *ptr = LocateAnswers(msg, end);
	for (i = 0; i < msg->h.numAnswers && ptr; i++) ptr = skipResourceRecord(msg, ptr, end);
	return(ptr);
	}

// ***************************************************************************
#if 0
#pragma mark -
#pragma mark -
#pragma mark - Packet Sending Functions
#endif

mDNSlocal mStatus mDNSSendDNSMessage(const mDNS *const m, DNSMessage *const msg, const mDNSu8 *const end,
	mDNSIPAddr src, mDNSIPPort srcport, mDNSIPAddr dst, mDNSIPPort dstport)
	{
	mStatus status;
	mDNSu16 numQuestions   = msg->h.numQuestions;
	mDNSu16 numAnswers     = msg->h.numAnswers;
	mDNSu16 numAuthorities = msg->h.numAuthorities;
	mDNSu16 numAdditionals = msg->h.numAdditionals;
	
	// Put all the integer values in IETF byte-order (MSB first, LSB second)
	mDNSu8 *ptr = (mDNSu8 *)&msg->h.numQuestions;
	*ptr++ = (mDNSu8)(numQuestions   >> 8);
	*ptr++ = (mDNSu8)(numQuestions       );
	*ptr++ = (mDNSu8)(numAnswers     >> 8);
	*ptr++ = (mDNSu8)(numAnswers         );
	*ptr++ = (mDNSu8)(numAuthorities >> 8);
	*ptr++ = (mDNSu8)(numAuthorities     );
	*ptr++ = (mDNSu8)(numAdditionals >> 8);
	*ptr++ = (mDNSu8)(numAdditionals     );
	
	// Send the packet on the wire
	status = mDNSPlatformSendUDP(m, msg, end, src, srcport, dst, dstport);
	
	// Put all the integer values back the way they were before we return
	msg->h.numQuestions   = numQuestions;
	msg->h.numAnswers     = numAnswers;
	msg->h.numAuthorities = numAuthorities;
	msg->h.numAdditionals = numAdditionals;

	return(status);
	}

mDNSlocal mDNSBool HaveResponses(const mDNS *const m, const mDNSs32 timenow)
	{
	ResourceRecord *rr;
	if (m->SleepState)
		{
		for (rr = m->ResourceRecords; rr; rr=rr->next)
			if (rr->RecordType == kDNSRecordTypeShared && rr->rrremainingttl == 0)
				return(mDNStrue);
		}
	else
		{
		for (rr = m->ResourceRecords; rr; rr=rr->next)
			{
			if (rr->RecordType == kDNSRecordTypeDeregistering)
				return(mDNStrue);
	
			if (rr->AnnounceCount && ResourceRecordIsValidAnswer(rr) && timenow - rr->NextSendTime >= 0)
				return(mDNStrue);
	
			if (rr->SendPriority >= kDNSSendPriorityAnswer && ResourceRecordIsValidAnswer(rr))
				return(mDNStrue);
			}
		}
	return(mDNSfalse);
	}

// NOTE: DiscardDeregistrations calls mDNS_Deregister_internal which can call a user callback, which may change
// the record list and/or question list.
// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
mDNSlocal void DiscardDeregistrations(mDNS *const m, mDNSs32 timenow)
	{
	if (m->CurrentRecord) debugf("DiscardDeregistrations ERROR m->CurrentRecord already set");
	m->CurrentRecord = m->ResourceRecords;
	
	while (m->CurrentRecord)
		{
		ResourceRecord *rr = m->CurrentRecord;
		m->CurrentRecord = rr->next;
		if (rr->RecordType == kDNSRecordTypeDeregistering)
			{
			rr->RecordType    = kDNSRecordTypeShared;
			rr->AnnounceCount = DefaultAnnounceCountForTypeShared+1;
			mDNS_Deregister_internal(m, rr, timenow, mDNS_Dereg_normal);
			}
		}
	}

// This routine sends as many records as it can fit in a single DNS Response Message, in order of priority.
// If there are any deregistrations, announcements, or answers that don't fit, they are left in the work list for next time.
// If there are any additionals that don't fit, they are discarded -- they were optional anyway.
// NOTE: BuildResponse calls mDNS_Deregister_internal which can call a user callback, which may change
// the record list and/or question list.
// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
mDNSlocal mDNSu8 *BuildResponse(mDNS *const m,
	DNSMessage *const response, mDNSu8 *responseptr, const mDNSIPAddr InterfaceAddr, const mDNSs32 timenow)
	{
	ResourceRecord *rr;
	mDNSu8 *newptr;
	int numDereg    = 0;
	int numAnnounce = 0;
	int numAnswer   = 0;
	mDNSs32 minExistingAnnounceInterval = 0;

	if (m->CurrentRecord) debugf("BuildResponse ERROR m->CurrentRecord already set");
	m->CurrentRecord = m->ResourceRecords;
	
	// If we're sleeping, only send deregistrations
	if (m->SleepState)
		{
		while (m->CurrentRecord)
			{
			ResourceRecord *rr = m->CurrentRecord;
			m->CurrentRecord = rr->next;
			if (rr->InterfaceAddr.NotAnInteger == InterfaceAddr.NotAnInteger &&
				rr->RecordType == kDNSRecordTypeShared && rr->rrremainingttl == 0 &&
				(newptr = putResourceRecord(response, responseptr, &response->h.numAnswers, rr, mDNSNULL, 0)))
				{
				numDereg++;
				responseptr = newptr;
				rr->rrremainingttl = rr->rroriginalttl;
				}
			}
		}
	else
		{
		// 1. Look for deregistrations we need to send
		while (m->CurrentRecord)
			{
			ResourceRecord *rr = m->CurrentRecord;
			m->CurrentRecord = rr->next;
			if (rr->InterfaceAddr.NotAnInteger == InterfaceAddr.NotAnInteger)
				{
				if (rr->NewRData)								// If we have new data for this record
					{
					RData *OldRData = rr->rdata;
					if (ResourceRecordIsValidAnswer(rr))		// First see if we have to de-register the old data
						{
						rr->rrremainingttl = 0;					// Clear rroriginalttl before putting record
						newptr = putResourceRecord(response, responseptr, &response->h.numAnswers, rr, mDNSNULL, 0);
						if (newptr)
							{
							numDereg++;
							responseptr = newptr;
							}
						rr->rrremainingttl = rr->rroriginalttl;	// Now restore rroriginalttl
						}
					rr->rdata = rr->NewRData;	// Update our rdata
					rr->NewRData = mDNSNULL;	// Clear the NewRData pointer ...
					if (rr->UpdateCallback) rr->UpdateCallback(m, rr, OldRData);	// ... and let the client know
					}
				if (rr->RecordType == kDNSRecordTypeDeregistering &&
					(newptr = putResourceRecord(response, responseptr, &response->h.numAnswers, rr, mDNSNULL, 0)))
					{
					numDereg++;
					responseptr = newptr;
					rr->RecordType    = kDNSRecordTypeShared;
					rr->AnnounceCount = DefaultAnnounceCountForTypeShared+1;
					mDNS_Deregister_internal(m, rr, timenow, mDNS_Dereg_normal);
					}
				}
			}
	
		// 2. Look for announcements we are due to send in the next second
		for (rr = m->ResourceRecords; rr; rr=rr->next)
			{
			if (rr->InterfaceAddr.NotAnInteger == InterfaceAddr.NotAnInteger &&
				rr->AnnounceCount && ResourceRecordIsValidAnswer(rr) &&
				timenow + mDNSPlatformOneSecond - rr->NextSendTime >= 0)
					{
					newptr = putResourceRecord(response, responseptr, &response->h.numAnswers, rr, m, timenow);
					if (newptr)
						{
						numAnnounce++;
						responseptr = newptr;
						}
					// If we were able to put the record, then update the state variables
					// If we were unable to put the record because it is too large to fit, even though
					// there are no other answers in the packet, then pretend we succeeded anyway,
					// or we'll end up in an infinite loop trying to send a record that will never fit
					if (response->h.numAnswers == 0) debugf("BuildResponse announcements failed");
					if (newptr || response->h.numAnswers == 0)
						{
						if (minExistingAnnounceInterval > rr->NextSendInterval)
							minExistingAnnounceInterval = rr->NextSendInterval;
						rr->SendPriority      = 0;
						rr->Requester         = zeroIPAddr;
						rr->AnnounceCount--;
						rr->NextSendTime     += rr->NextSendInterval;
						if (rr->NextSendTime - (timenow + rr->NextSendInterval/2) < 0)
							rr->NextSendTime = (timenow + rr->NextSendInterval/2);
						rr->NextSendInterval *= 2;
						}
					}
			}
	
		// 2a. Look for additional announcements that are worth accelerating
		// They must be (a) at least half-way to their next announcement and
		// (b) at an interval equal or less than any of the ones we've already put in
		for (rr = m->ResourceRecords; rr; rr=rr->next)
			{
			if (rr->InterfaceAddr.NotAnInteger == InterfaceAddr.NotAnInteger &&
				rr->AnnounceCount && ResourceRecordIsValidAnswer(rr) &&
				timenow - (rr->LastSendTime + rr->NextSendInterval/4) >= 0 &&
				rr->NextSendInterval <= minExistingAnnounceInterval)
					{
					newptr = putResourceRecord(response, responseptr, &response->h.numAnswers, rr, m, timenow);
					if (newptr)
						{
						numAnnounce++;
						responseptr = newptr;
						}
					// If we were able to put the record, then update the state variables
					// If we were unable to put the record because it is too large to fit, even though
					// there are no other answers in the packet, then pretend we succeeded anyway,
					// or we'll end up in an infinite loop trying to send a record that will never fit
					if (response->h.numAnswers == 0) debugf("BuildResponse announcements failed");
					if (newptr || response->h.numAnswers == 0)
						{
						rr->SendPriority      = 0;
						rr->Requester         = zeroIPAddr;
						rr->AnnounceCount--;
						rr->NextSendTime      = timenow + rr->NextSendInterval;
						rr->NextSendInterval *= 2;
						}
					}
			}
	
		// 3. Look for answers we need to send
		for (rr = m->ResourceRecords; rr; rr=rr->next)
			if (rr->InterfaceAddr.NotAnInteger == InterfaceAddr.NotAnInteger &&
				rr->SendPriority >= kDNSSendPriorityAnswer && ResourceRecordIsValidAnswer(rr))
					{
					newptr = putResourceRecord(response, responseptr, &response->h.numAnswers, rr, m, timenow);
					if (newptr)
						{
						numAnswer++;
						responseptr = newptr;
						}
					// If we were able to put the record, then update the state variables
					// If we were unable to put the record because it is too large to fit, even though
					// there are no other answers in the packet then pretend we succeeded anyway,
					// or we'll end up in an infinite loop trying to send a record that will never fit
					if (response->h.numAnswers == 0) debugf("BuildResponse answers failed");
					if (newptr || response->h.numAnswers == 0)
						{
						rr->SendPriority = 0;
						rr->Requester    = zeroIPAddr;
						}
					}
	
		// 4. Add additionals, if there's space
		for (rr = m->ResourceRecords; rr; rr=rr->next)
			if (rr->InterfaceAddr.NotAnInteger == InterfaceAddr.NotAnInteger &&
				rr->SendPriority == kDNSSendPriorityAdditional)
				{
				if (ResourceRecordIsValidAnswer(rr) &&
					(newptr = putResourceRecord(response, responseptr, &response->h.numAdditionals, rr, m, timenow)))
						responseptr = newptr;
				rr->SendPriority = 0;	// Clear SendPriority anyway, even if we didn't put the additional in the packet
				rr->Requester    = zeroIPAddr;
				}
		}

	if (numDereg || numAnnounce || numAnswer || response->h.numAdditionals)
		verbosedebugf("BuildResponse Built %d Deregistration%s, %d Announcement%s, %d Answer%s, %d Additional%s",
			numDereg,                   numDereg                   == 1 ? "" : "s",
			numAnnounce,                numAnnounce                == 1 ? "" : "s",
			numAnswer,                  numAnswer                  == 1 ? "" : "s",
			response->h.numAdditionals, response->h.numAdditionals == 1 ? "" : "s");

	return(responseptr);
	}

mDNSlocal void SendResponses(mDNS *const m, const mDNSs32 timenow)
	{
	DNSMessage response;
	DNSMessageHeader baseheader;
	mDNSu8 *baselimit, *responseptr;
	NetworkInterfaceInfo *intf;
	ResourceRecord *rr, *r2;

	// Run through our list of records,
	// and if there's a record which is supposed to be unique that we're proposing to give as an answer,
	// then make sure that the whole RRSet with that name/type/class is also marked for answering.
	// Otherwise, if we set the kDNSClass_UniqueRRSet bit on a record, then other RRSet members
	// that have not been sent recently will get flushed out of client caches.
	for (rr = m->ResourceRecords; rr; rr=rr->next)
		if (rr->RecordType & kDNSRecordTypeUniqueMask)
			if (TimeToSendThisRecord(rr,timenow))
				for (r2 = m->ResourceRecords; r2; r2=r2->next)
					if (r2 != rr && timenow - r2->LastSendTime > mDNSPlatformOneSecond/4)
						if (SameResourceRecordSignatureAnyInterface(rr, r2))
							r2->SendPriority = kDNSSendPriorityAnswer;

	// First build the generic part of the message
	InitializeDNSMessage(&response.h, zeroID, ResponseFlags);
	baselimit = BuildResponse(m, &response, response.data, zeroIPAddr, timenow);
	baseheader = response.h;

	for (intf = m->HostInterfaces; intf; intf = intf->next)
		{
		// Restore the header to the counts for the generic records
		response.h = baseheader;
		// Now add any records specific to this interface
		responseptr = BuildResponse(m, &response, baselimit, intf->ip, timenow);
		if (response.h.numAnswers > 0)	// We *never* send a packet with only additionals in it
			{
			mDNSSendDNSMessage(m, &response, responseptr, intf->ip, MulticastDNSPort, AllDNSLinkGroup, MulticastDNSPort);
			debugf("SendResponses Sent %d Answer%s, %d Additional%s on %.4a",
				response.h.numAnswers,     response.h.numAnswers     == 1 ? "" : "s",
				response.h.numAdditionals, response.h.numAdditionals == 1 ? "" : "s", &intf->ip);
			}
		}
	}

#define TimeToSendThisQuestion(Q,time) (!(Q)->DuplicateOf && time - (Q)->NextQTime >= 0)

mDNSlocal mDNSBool HaveQueries(const mDNS *const m, const mDNSs32 timenow)
	{
	ResourceRecord *rr;
	DNSQuestion *q;

	// 1. See if we've got any cache records in danger of expiring
	for (rr = m->rrcache; rr; rr=rr->next)
		if (rr->UnansweredQueries < 2)
			{
			mDNSs32 onetenth = ((mDNSs32)rr->rroriginalttl * mDNSPlatformOneSecond) / 10;
			mDNSs32 t0 = rr->TimeRcvd + (mDNSs32)rr->rroriginalttl * mDNSPlatformOneSecond;
			mDNSs32 t1 = t0 - onetenth;
			mDNSs32 t2 = t1 - onetenth;

			if (timenow - t1 >= 0 || (rr->UnansweredQueries < 1 && timenow - t2 >= 0))
				{
				DNSQuestion *q = CacheRRActive(m, rr);
				if (q) q->NextQTime = timenow;
				}
			}
	
	// 2. Scan our list of questions to see if it's time to send any of them
	for (q = m->ActiveQuestions; q; q=q->next)
		if (TimeToSendThisQuestion(q, timenow))
			return(mDNStrue);

	// 3. Scan our list of Resource Records to see if we need to send any probe questions
	for (rr = m->ResourceRecords; rr; rr=rr->next)		// Scan our list of records
		if (rr->RecordType == kDNSRecordTypeUnique && timenow - rr->NextSendTime >= 0)
			return(mDNStrue);

	return(mDNSfalse);
	}

// BuildProbe puts a probe question into a DNS Query packet and if successful, updates the value of queryptr.
// It also sets the record's IncludeInProbe flag so that we know to add an Update Record too
// and updates the forcast for the size of the duplicate suppression (answer) section.
// NOTE: BuildProbe can call a user callback, which may change the record list and/or question list.
// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
mDNSlocal void BuildProbe(mDNS *const m, DNSMessage *query, mDNSu8 **queryptr,
	ResourceRecord *rr, mDNSu32 *answerforecast, const mDNSs32 timenow)
	{
	if (rr->ProbeCount == 0)
		{
		rr->RecordType    = kDNSRecordTypeVerified;
		rr->AnnounceCount = DefaultAnnounceCountForRecordType(rr->RecordType);
		debugf("Probing for %##s (%s) complete", rr->name.c, DNSTypeName(rr->rrtype));
		if (!rr->Acknowledged && rr->Callback)	
			{ rr->Acknowledged = mDNStrue; rr->Callback(m, rr, mStatus_NoError); }
		}
	else
		{
		const mDNSu8 *const limit = query->data + ((query->h.numQuestions) ? NormalMaxDNSMessageData : AbsoluteMaxDNSMessageData);
		mDNSu8 *newptr = putQuestion(query, *queryptr, limit, &rr->name, kDNSQType_ANY, rr->rrclass);
		// We forecast: compressed name (2) type (2) class (2) TTL (4) rdlength (2) rdata (n)
		mDNSu32 forecast = *answerforecast + 12 + rr->rdestimate;
		if (newptr && newptr + forecast < limit)
			{
			*queryptr       = newptr;
			*answerforecast = forecast;
			rr->ProbeCount--;		// Only decrement ProbeCount if we successfully added the record to the packet
			rr->IncludeInProbe = mDNStrue;
			rr->NextSendTime = timenow + rr->NextSendInterval;
			}
		else
			{
			debugf("BuildProbe retracting Question %##s (%s)", rr->name.c, DNSTypeName(rr->rrtype));
			query->h.numQuestions--;
			}
		}
	}

#define MaxQuestionInterval         (3600 * mDNSPlatformOneSecond)
#define GetNextQInterval(X)         (((X)*2) <= MaxQuestionInterval ? ((X)*2) : MaxQuestionInterval)
#define GetNextSendTime(T,EARLIEST) (((T) - (EARLIEST) >= 0) ? (T) : (EARLIEST) )

// BuildQuestion puts a question into a DNS Query packet and if successful, updates the value of queryptr.
// It also appends to the list of duplicate suppression records that need to be included,
// and updates the forcast for the size of the duplicate suppression (answer) section.
mDNSlocal void BuildQuestion(mDNS *const m, DNSMessage *query, mDNSu8 **queryptr, DNSQuestion *q,
	ResourceRecord ***dups_ptr, mDNSu32 *answerforecast, const mDNSs32 timenow)
	{
	const mDNSu8 *const limit = query->data + (query->h.numQuestions ? NormalMaxDNSMessageData : AbsoluteMaxDNSMessageData);
	mDNSu8 *newptr = putQuestion(query, *queryptr, limit, &q->name, q->rrtype, q->rrclass);
	if (!newptr)
		debugf("BuildQuestion: No more space for queries");
	else
		{
		mDNSu32 forecast = *answerforecast;
		ResourceRecord *rr;
		ResourceRecord **d = *dups_ptr;
		mDNSs32 nst = timenow + q->NextQInterval;

		// If we have a resource record in our cache,
		// which is not already in the duplicate suppression list
		// which answers our question,
		// then add it to the duplicate suppression list
		for (rr=m->rrcache; rr; rr=rr->next)
			if (rr->NextDupSuppress == mDNSNULL && d != &rr->NextDupSuppress &&
				ResourceRecordAnswersQuestion(rr, q))
				{
				// Work out the latest time we should ask about this record to refresh it before it expires
				mDNSs32 onetenth = ((mDNSs32)rr->rroriginalttl * mDNSPlatformOneSecond) / 10;
				mDNSs32 t0 = rr->TimeRcvd + (mDNSs32)rr->rroriginalttl * mDNSPlatformOneSecond;
				mDNSs32 t3 = t0 - onetenth*3;

				// If we'll ask again at least twice before it expires, okay to suppress it this time
				if (t3 - nst >= 0)
					{
					*d = rr;	// Link this record into our duplicate suppression chain
					d = &rr->NextDupSuppress;
					// We forecast: compressed name (2) type (2) class (2) TTL (4) rdlength (2) rdata (n)
					forecast += 12 + rr->rdestimate;
					}
				else
					rr->UnansweredQueries++;
				}

		// If we're trying to put more than one question in this packet, and it doesn't fit
		// then undo that last question and try again next time
		if (query->h.numQuestions > 1 && newptr + forecast >= limit)
			{
			debugf("BuildQuestion retracting question %##s answerforecast %d", q->name.c, *answerforecast);
			query->h.numQuestions--;
			d = *dups_ptr;		// Go back to where we started and retract these answer records
			while (*d) { ResourceRecord *rr = *d; *d = mDNSNULL; d = &rr->NextDupSuppress; }
			}
		else
			{
			*queryptr       = newptr;		// Update the packet pointer
			*answerforecast = forecast;		// Update the forecast
			*dups_ptr       = d;			// Update the dup suppression pointer
			q->NextQTime     = nst;
			q->ThisQInterval = q->NextQInterval;
			q->NextQInterval = GetNextQInterval(q->ThisQInterval);
			}
		}
	}

// How Standard Queries are generated:
// 1. The Question Section contains the question
// 2. The Additional Section contains answers we already know, to suppress duplicate replies

// How Probe Queries are generated:
// 1. The Question Section contains queries for the name we intend to use, with QType=ANY because
// if some other host is already using *any* records with this name, we want to know about it.
// 2. The Authority Section contains the proposed values we intend to use for one or more
// of our records with that name (analogous to the Update section of DNS Update packets)
// because if some other host is probing at the same time, we each want to know what the other is
// planning, in order to apply the tie-breaking rule to see who gets to use the name and who doesn't.

mDNSlocal mDNSu8 *BuildQueryPacketQuestions(mDNS *const m, DNSMessage *query, mDNSu8 *queryptr,
	ResourceRecord ***dups_ptr, mDNSu32 *answerforecast,
	const mDNSIPAddr InterfaceAddr, const mDNSs32 timenow)
	{
	DNSQuestion *q;
	
	// See which questions need to go out right now
	for (q = m->ActiveQuestions; q; q=q->next)
		if (q->InterfaceAddr.NotAnInteger == InterfaceAddr.NotAnInteger &&
			TimeToSendThisQuestion(q, timenow))
			BuildQuestion(m, query, &queryptr, q, dups_ptr, answerforecast, timenow);

	// See which questions are more than half way to their NextSendTime, and send them too, if we have space
	for (q = m->ActiveQuestions; q; q=q->next)
		if (q->InterfaceAddr.NotAnInteger == InterfaceAddr.NotAnInteger &&
			TimeToSendThisQuestion(q, timenow + q->ThisQInterval/2))
			BuildQuestion(m, query, &queryptr, q, dups_ptr, answerforecast, timenow);

	return(queryptr);
	}

mDNSlocal mDNSu8 *BuildQueryPacketAnswers(DNSMessage *query, mDNSu8 *queryptr,
	ResourceRecord **dups_ptr, const mDNSs32 timenow)
	{
	while (*dups_ptr)
		{
		ResourceRecord *rr = *dups_ptr;
		mDNSu32 timesincercvd = (mDNSu32)(timenow - rr->TimeRcvd);
		mDNSu8 *newptr;
		// Need to update rrremainingttl correctly before we put this cache record in the packet
		rr->rrremainingttl = rr->rroriginalttl - timesincercvd / mDNSPlatformOneSecond;
		newptr = putResourceRecord(query, queryptr, &query->h.numAnswers, rr, mDNSNULL, 0);
		if (newptr)
			{
			*dups_ptr = rr->NextDupSuppress;
			rr->NextDupSuppress = mDNSNULL;
			queryptr = newptr;
			}
		else
			{
			debugf("BuildQueryPacketAnswers: Put %d answers; No more space for duplicate suppression",
				query->h.numAnswers);
			query->h.flags.b[0] |= kDNSFlag0_TC;
			break;
			}
		}
	return(queryptr);
	}

mDNSlocal mDNSu8 *BuildQueryPacketProbes(mDNS *const m, DNSMessage *query, mDNSu8 *queryptr,
	mDNSu32 *answerforecast, const mDNSIPAddr InterfaceAddr, const mDNSs32 timenow)
	{
	if (m->CurrentRecord) debugf("BuildQueryPacketProbes ERROR m->CurrentRecord already set");
	m->CurrentRecord = m->ResourceRecords;
	while (m->CurrentRecord)
		{
		ResourceRecord *rr = m->CurrentRecord;
		m->CurrentRecord = rr->next;
		if (rr->InterfaceAddr.NotAnInteger == InterfaceAddr.NotAnInteger &&
			rr->RecordType == kDNSRecordTypeUnique && timenow - rr->NextSendTime >= 0)
			BuildProbe(m, query, &queryptr, rr, answerforecast, timenow);
		}
	return(queryptr);
	}

mDNSlocal mDNSu8 *BuildQueryPacketUpdates(mDNS *const m, DNSMessage *query, mDNSu8 *queryptr)
	{
	ResourceRecord *rr;
	for (rr = m->ResourceRecords; rr; rr=rr->next)
		if (rr->IncludeInProbe)
			{
			mDNSu8 *newptr = putResourceRecord(query, queryptr, &query->h.numAuthorities, rr, mDNSNULL, 0);
			rr->IncludeInProbe = mDNSfalse;
			if (newptr)
				queryptr = newptr;
			else
				{
				debugf("BuildQueryPacketUpdates: How did we fail to have space for the Update record %##s (%s)?",
					rr->name.c, DNSTypeName(rr->rrtype));
				break;
				}
			}
	return(queryptr);
	}

mDNSlocal void SendQueries(mDNS *const m, const mDNSs32 timenow)
	{
	ResourceRecord *NextDupSuppress = mDNSNULL;
	do
		{
		DNSMessage query;
		DNSMessageHeader baseheader;
		mDNSu8 *baselimit = query.data;
		NetworkInterfaceInfo *intf;
	
		// First build the generic part of the message
		InitializeDNSMessage(&query.h, zeroID, QueryFlags);
		if (!NextDupSuppress)
			{
			ResourceRecord **dups = &NextDupSuppress;
			mDNSu32 answerforecast = 0;
			baselimit = BuildQueryPacketQuestions(m, &query, baselimit, &dups, &answerforecast, zeroIPAddr, timenow);
			baselimit = BuildQueryPacketProbes(m, &query, baselimit, &answerforecast, zeroIPAddr, timenow);
			}
		baselimit = BuildQueryPacketAnswers(&query, baselimit, &NextDupSuppress, timenow);
		baselimit = BuildQueryPacketUpdates(m, &query, baselimit);
		baseheader = query.h;
		
		if (NextDupSuppress) debugf("SendQueries: NextDupSuppress still set... Will continue in next packet");

		for (intf = m->HostInterfaces; intf; intf = intf->next)
			{
			ResourceRecord *NextDupSuppress2 = mDNSNULL;
			do
				{
				// Restore the header to the counts for the generic records
				mDNSu8 *queryptr = baselimit;
				query.h = baseheader;
				// Now add any records specific to this interface, if we can
				if (query.h.numAnswers == 0 && query.h.numAuthorities == 0 && !NextDupSuppress)
					{
					if (!NextDupSuppress2)
						{
						ResourceRecord **dups2 = &NextDupSuppress2;
						mDNSu32 answerforecast2 = 0;
						queryptr = BuildQueryPacketQuestions(m, &query, queryptr, &dups2, &answerforecast2, intf->ip, timenow);
						queryptr = BuildQueryPacketProbes(m, &query, queryptr, &answerforecast2, intf->ip, timenow);
						}
					queryptr = BuildQueryPacketAnswers(&query, queryptr, &NextDupSuppress2, timenow);
					queryptr = BuildQueryPacketUpdates(m, &query, queryptr);
					}
	
				if (queryptr > query.data)
					{
					mDNSSendDNSMessage(m, &query, queryptr, intf->ip, MulticastDNSPort, AllDNSLinkGroup, MulticastDNSPort);
					debugf("SendQueries Sent %d Question%s %d Answer%s %d Update%s on %.4a",
						query.h.numQuestions,   query.h.numQuestions   == 1 ? "" : "s",
						query.h.numAnswers,     query.h.numAnswers     == 1 ? "" : "s",
						query.h.numAuthorities, query.h.numAuthorities == 1 ? "" : "s", &intf->ip);
					}
				} while (NextDupSuppress2);
			}
		} while (NextDupSuppress);
	}

// ***************************************************************************
#if 0
#pragma mark -
#pragma mark - RR List Management & Task Management
#endif

// rr is a new ResourceRecord just received into our cache
// (kDNSRecordTypePacketAnswer/kDNSRecordTypePacketAdditional/kDNSRecordTypePacketUniqueAns/kDNSRecordTypePacketUniqueAdd)
mDNSlocal void TriggerImmediateQuestions(mDNS *const m, const ResourceRecord *const rr, const mDNSs32 timenow)
	{
	// If we just received a new record off the wire that we've never seen before, we want to ask our question again
	// soon, and keep doing that repeatedly (with duplicate suppression) until we stop getting any more responses
	mDNSs32 needquery = timenow + mDNSPlatformOneSecond;
	DNSQuestion *q;
	for (q = m->ActiveQuestions; q; q=q->next)		// Scan our list of questions
		if (!q->DuplicateOf && q->NextQTime - needquery > 0 && ResourceRecordAnswersQuestion(rr, q))
			{
			q->NextQTime     = needquery;
			// As long as responses are still coming in, don't do the exponential backoff
			q->NextQInterval = q->ThisQInterval;
			}
	}

// NOTE: AnswerQuestionWithResourceRecord can call a user callback, which may change the record list and/or question list.
// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
mDNSlocal void AnswerQuestionWithResourceRecord(mDNS *const m, DNSQuestion *q, ResourceRecord *rr, const mDNSs32 timenow)
	{
	mDNSu32 timesincercvd = (mDNSu32)(timenow - rr->TimeRcvd);
	if (rr->rroriginalttl <= timesincercvd / mDNSPlatformOneSecond) rr->rrremainingttl = 0;
	else rr->rrremainingttl = rr->rroriginalttl - timesincercvd / mDNSPlatformOneSecond;

#if DEBUGBREAKS
	if (rr->rrremainingttl)
		{
		if (rr->rrtype == kDNSType_TXT)
			debugf("AnswerQuestionWithResourceRecord Add %##s TXT %#.20s remaining ttl %d",
				rr->name.c, rr->rdata->u.txt.c, rr->rrremainingttl);
		else
			debugf("AnswerQuestionWithResourceRecord Add %##s (%s) remaining ttl %d",
				rr->name.c, DNSTypeName(rr->rrtype), rr->rrremainingttl);
		}
	else
		{
		if (rr->rrtype == kDNSType_TXT)
			debugf("AnswerQuestionWithResourceRecord Del %##s TXT %#.20s UnansweredQueries %d",
				rr->name.c, rr->rdata->u.txt.c, rr->UnansweredQueries);
		else
			debugf("AnswerQuestionWithResourceRecord Del %##s (%s) UnansweredQueries %d",
				rr->name.c, DNSTypeName(rr->rrtype), rr->UnansweredQueries);
		}
#endif

	rr->LastUsed = timenow;
	rr->UseCount++;
	if (q->Callback) q->Callback(m, q, rr);
	}

// AnswerLocalQuestions is called from mDNSCoreReceiveResponse,
// and from TidyRRCache, which is called from mDNSCoreTask and from mDNSCoreReceiveResponse
// AnswerLocalQuestions is *never* called directly as a result of a client API call
// If new questions are created as a result of invoking client callbacks, they will be added to
// the end of the question list, and m->NewQuestions will be set to indicate the first new question.
// rr is a ResourceRecord in our cache
// (kDNSRecordTypePacketAnswer/kDNSRecordTypePacketAdditional/kDNSRecordTypePacketUniqueAns/kDNSRecordTypePacketUniqueAdd)
// NOTE: AnswerLocalQuestions calls AnswerQuestionWithResourceRecord which can call a user callback, which may change
// the record list and/or question list.
// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
mDNSlocal void AnswerLocalQuestions(mDNS *const m, ResourceRecord *rr, const mDNSs32 timenow)
	{
	if (m->CurrentQuestion) debugf("AnswerLocalQuestions ERROR m->CurrentQuestion already set");
	m->CurrentQuestion = m->ActiveQuestions;
	while (m->CurrentQuestion && m->CurrentQuestion != m->NewQuestions)
		{
		DNSQuestion *q = m->CurrentQuestion;
		m->CurrentQuestion = q->next;
		if (ResourceRecordAnswersQuestion(rr, q))
			AnswerQuestionWithResourceRecord(m, q, rr, timenow);
		}
	m->CurrentQuestion = mDNSNULL;
	}

mDNSlocal void AnswerNewQuestion(mDNS *const m, const mDNSs32 timenow)
	{
	ResourceRecord *rr;
	DNSQuestion *q = m->NewQuestions;		// Grab the question we're going to answer
	m->NewQuestions = q->next;				// Advance NewQuestions to the next (if any)

	if (m->lock_rrcache) debugf("AnswerNewQuestion ERROR! Cache already locked!");
	// This should be safe, because calling the client's question callback may cause the
	// question list to be modified, but should not ever cause the rrcache list to be modified.
	// If the client's question callback deletes the question, then m->CurrentQuestion will
	// be advanced, and we'll exit out of the loop
	m->lock_rrcache = 1;
	if (m->CurrentQuestion) debugf("AnswerNewQuestion ERROR m->CurrentQuestion already set");
	m->CurrentQuestion = q;		// Indicate which question we're answering, so we'll know if it gets deleted
	for (rr=m->rrcache; rr && m->CurrentQuestion == q; rr=rr->next)
		if (ResourceRecordAnswersQuestion(rr, q))
			AnswerQuestionWithResourceRecord(m, q, rr, timenow);
	m->CurrentQuestion = mDNSNULL;
	m->lock_rrcache = 0;
	}

mDNSlocal void FlushCacheRecords(mDNS *const m, mDNSIPAddr InterfaceAddr, const mDNSs32 timenow)
	{
	mDNSu32 count = 0;
	ResourceRecord *rr;
	for (rr = m->rrcache; rr; rr=rr->next)
		{
		if (rr->InterfaceAddr.NotAnInteger == InterfaceAddr.NotAnInteger)
			{
			// If the record's interface matches the one we're flushing,
			// then pretend we just received a 'goodbye' packet for this record.
			rr->TimeRcvd          = timenow;
			rr->UnansweredQueries = 0;
			rr->rroriginalttl     = 1;
			count++;
			}
		}
	
	if (count) debugf("FlushCacheRecords Flushing %d Cache Entries on interface %.4a", count, &InterfaceAddr);
	}

// TidyRRCache
// Throw away any cache records that have passed their TTL
// First we prepare a list of records to delete, and pull them off the rrcache list
// Then we go through the list of records to delete, calling the user's question callbacks if necessary
// We do it in two phases like this to guard against the user's question callbacks modifying
// the rrcache list while we're walking it.
mDNSlocal void TidyRRCache(mDNS *const m, const mDNSs32 timenow)
	{
	mDNSu32 count = 0;
	ResourceRecord **rr = &m->rrcache;
	ResourceRecord *deletelist = mDNSNULL;
	
	if (m->lock_rrcache) { debugf("TidyRRCache ERROR! Cache already locked!"); return; }
	m->lock_rrcache = 1;
	
	while (*rr)
		{
		mDNSu32 timesincercvd = (mDNSu32)(timenow - (*rr)->TimeRcvd);
		if ((*rr)->rroriginalttl > timesincercvd / mDNSPlatformOneSecond)
			rr=&(*rr)->next;			// If TTL is greater than time elapsed, save this record
		else
			{
			ResourceRecord *r = *rr;	// Else,
			*rr = r->next;				// detatch this record from the cache list
			r->next = deletelist;		// and move it onto the list of things to delete
			deletelist = r;
			count++;
			}
		}
	
	if (count) verbosedebugf("TidyRRCache Deleting %d Expired Cache Entries", count);

	m->lock_rrcache = 0;

	while (deletelist)
		{
		ResourceRecord *r = deletelist;
		verbosedebugf("TidyRRCache: Deleted %##s (%s)", r->name.c, DNSTypeName(r->rrtype));
		deletelist = deletelist->next;
		AnswerLocalQuestions(m, r, timenow);
		r->next = m->rrcache_free;	// and move it back to the free list
		m->rrcache_free = r;
		m->rrcache_used--;
		}
	}

mDNSlocal ResourceRecord *GetFreeCacheRR(mDNS *const m, const mDNSs32 timenow)
	{
	ResourceRecord *r = m->rrcache_free;

	if (m->lock_rrcache) { debugf("GetFreeCacheRR ERROR! Cache already locked!"); return(mDNSNULL); }
	m->lock_rrcache = 1;
	
	if (r)		// If there are records in the free list, take one
		{
		m->rrcache_free = r->next;
		m->rrcache_used++;
		if (m->rrcache_used >= m->rrcache_report)
			{
			debugf("RR Cache now using %d records", m->rrcache_used);
			m->rrcache_report *= 2;
			}
		}
	else		// Else search for a candidate to recycle
		{
		ResourceRecord **rr = &m->rrcache;
		ResourceRecord **best = mDNSNULL;
		mDNSs32 bestage = -1;

		while (*rr)
			{
			mDNSs32 timesincercvd = timenow - (*rr)->TimeRcvd;

			// Records we've only just received are not candidates for deletion
			if (timesincercvd > 0)
				{
				// Work out a weighted age, which is the number of seconds since this record was last used,
				// divided by the number of times it has been used (we want to keep frequently used records longer).
				mDNSs32 count = (*rr)->UseCount < 100 ? 1 + (mDNSs32)(*rr)->UseCount : 100;
				mDNSs32 age = (timenow - (*rr)->LastUsed) / count;
				mDNSu8 rtype = ((*rr)->RecordType) & ~kDNSRecordTypeUniqueMask;
				if (rtype == kDNSRecordTypePacketAnswer) age /= 2;		// Keep answer records longer than additionals

				// Records that answer still-active questions are not candidates for deletion
				if (bestage < age && !CacheRRActive(m, *rr)) { best = rr; bestage = age; }
				}

			rr=&(*rr)->next;
			}

		if (best)
			{
			r = *best;							// Remember the record we chose
			*best = r->next;					// And detatch it from the free list
			}
		}

	m->lock_rrcache = 0;

	if (r) mDNSPlatformMemZero(r, sizeof(*r));
	return(r);
	}

mDNSlocal void ScheduleNextTask(const mDNS *const m)
	{
	const mDNSs32 timenow = mDNSPlatformTimeNow();
	mDNSs32 nextevent = timenow + 0x78000000;
	const char *msg = "No Event", *sign="";
	mDNSs32 interval, fraction;

	DNSQuestion *q;
	ResourceRecord *rr;
	
	if (m->mDNSPlatformStatus != mStatus_NoError)
		return;
	
	// 1. If sleeping, do nothing
	if (m->SleepState)
		{
		debugf("ScheduleNextTask: Sleeping");
		return;
		}
	
	// 2. If we have new questions added to the list, we need to answer them from cache ASAP
	if (m->NewQuestions)
		{
		nextevent = timenow;
		msg = "New Questions";
		}
	else
		{
		// 3. Scan cache to see if any resource records are going to expire
		for (rr = m->rrcache; rr; rr=rr->next)
			{
			mDNSs32 onetenth = ((mDNSs32)rr->rroriginalttl * mDNSPlatformOneSecond) / 10;
			mDNSs32 t0 = rr->TimeRcvd + (mDNSs32)rr->rroriginalttl * mDNSPlatformOneSecond;
			mDNSs32 t1 = t0 - onetenth;
			mDNSs32 t2 = t1 - onetenth;
			if (rr->UnansweredQueries < 1 && nextevent - t2 > 0 && CacheRRActive(m, rr))
				{
				nextevent = t2;
				msg = "Penultimate Query";
				}
			else if (rr->UnansweredQueries < 2 && nextevent - t1 > 0 && CacheRRActive(m, rr))
				{
				nextevent = t1;
				msg = "Final Expiration Query";
				}
			else if (nextevent - t0 > 0)
				{
				nextevent = t0;
				msg = "Cache Tidying";
				}
			}
		
		// 4. If we're suppressing sending right now, don't bother searching for packet generation events --
		// but do make sure we come back at the end of the suppression time to check again
		if (m->SuppressSending)
			{
			if (nextevent - m->SuppressSending > 0)
				{
				nextevent = m->SuppressSending;
				msg = "Send Suppressed Packets";
				}
			}
		else
			{
			// 5. Scan list of active questions to see if we need to send any queries
			for (q = m->ActiveQuestions; q; q=q->next)
				if (TimeToSendThisQuestion(q, nextevent))
					{
					nextevent = q->NextQTime;
					msg = "Send Questions";
					}

			// 6. Scan list of local resource records to see if we have any
			// deregistrations, probes, announcements, or replies to send
			for (rr = m->ResourceRecords; rr; rr=rr->next)
				{
				if (rr->RecordType == kDNSRecordTypeDeregistering)
					{
					nextevent = timenow;
					msg = "Send Deregistrations";
					}
				else if (rr->SendPriority >= kDNSSendPriorityAnswer && ResourceRecordIsValidAnswer(rr))
					{
					nextevent = timenow;
					msg = "Send Answers";
					}
				else if (rr->RecordType == kDNSRecordTypeUnique && nextevent - rr->NextSendTime > 0)
					{
					nextevent = rr->NextSendTime;
					msg = "Send Probes";
					}
				else if (rr->AnnounceCount && nextevent - rr->NextSendTime > 0 && ResourceRecordIsValidAnswer(rr))
					{
					nextevent = rr->NextSendTime;
					msg = "Send Announcements";
					}
				}
			}
		}

	interval = nextevent - timenow;
	if (interval < 0) { interval = -interval; sign = "-"; }
	fraction = interval % mDNSPlatformOneSecond;
	verbosedebugf("ScheduleNextTask: Next event: <%s> in %s%d.%03d seconds", msg, sign,
		interval / mDNSPlatformOneSecond, fraction * 1000 / mDNSPlatformOneSecond);

	mDNSPlatformScheduleTask(m, nextevent);
	}

mDNSlocal mDNSs32 mDNS_Lock(mDNS *const m)
	{
	mDNSPlatformLock(m);
	++m->mDNS_busy;
	return(mDNSPlatformTimeNow());
	}

mDNSlocal void mDNS_Unlock(mDNS *const m)
	{
	// Upon unlocking, we've usually added some new work to the task list.
	// If we don't decrement mDNS_busy to zero, then we don't have to worry about calling
	// ScheduleNextTask(), because the last lock holder will do it for us on the way out.
	if (--m->mDNS_busy == 0) ScheduleNextTask(m);
	mDNSPlatformUnlock(m);
	}

mDNSexport void mDNSCoreTask(mDNS *const m)
	{
	const mDNSs32 timenow = mDNS_Lock(m);

	verbosedebugf("mDNSCoreTask");
	if (m->mDNS_busy > 1) debugf("mDNSCoreTask: Locking failure! mDNS already busy");
	if (m->CurrentQuestion) debugf("mDNSCoreTask: ERROR! m->CurrentQuestion already set");
	
	if (m->SuppressProbes && timenow - m->SuppressProbes >= 0)
		m->SuppressProbes = 0;

	// 1. See if we can answer any of our new local questions from the cache
	while (m->NewQuestions) AnswerNewQuestion(m, timenow);

	// 2. See what packets we need to send
	if (m->mDNSPlatformStatus != mStatus_NoError || m->SleepState)
		{
		// If the platform code is currently non-operational,
		// then we'll just complete deregistrations immediately,
		// without waiting for the goodbye packet to be sent
		DiscardDeregistrations(m, timenow);
		}
	else if (m->SuppressSending == 0 || timenow - m->SuppressSending >= 0)
		{
		// If the platform code is ready,
		// and we're not suppressing packet generation right now
		// send our responses, probes, and questions
		m->SuppressSending = 0;
		while (HaveResponses(m, timenow)) SendResponses(m, timenow);
		while (HaveQueries  (m, timenow)) SendQueries  (m, timenow);
		}

	if (m->rrcache_size) TidyRRCache(m, timenow);

	mDNS_Unlock(m);
	}

mDNSexport void mDNSCoreSleep(mDNS *const m, mDNSBool sleepstate)
	{
	ResourceRecord *rr;
	const mDNSs32 timenow = mDNS_Lock(m);

	m->SleepState = sleepstate;
	debugf("mDNSCoreSleep: %d", sleepstate);

	if (sleepstate)
		{
		// First mark all the records we need to deregister
		for (rr = m->ResourceRecords; rr; rr=rr->next)
			if (rr->RecordType == kDNSRecordTypeShared && rr->AnnounceCount <= DefaultAnnounceCountForTypeShared)
				rr->rrremainingttl = 0;
		while (HaveResponses(m, timenow)) SendResponses(m, timenow);
		}
	else
		{
		DNSQuestion *q;

		for (rr = m->ResourceRecords; rr; rr=rr->next)
			{
			if (rr->RecordType == kDNSRecordTypeVerified) rr->RecordType = kDNSRecordTypeUnique;
			rr->ProbeCount        = DefaultProbeCountForRecordType(rr->RecordType);
			rr->AnnounceCount     = DefaultAnnounceCountForRecordType(rr->RecordType);
			rr->NextSendTime      = timenow;
			rr->NextSendInterval  = DefaultSendIntervalForRecordType(rr->RecordType);
			}
		for (q = m->ActiveQuestions; q; q=q->next)		// Scan our list of questions
			if (!q->DuplicateOf)
				{
				q->NextQTime     = timenow;
				q->ThisQInterval = mDNSPlatformOneSecond;  // MUST NOT be zero for an active question
				q->NextQInterval = mDNSPlatformOneSecond;
				}
		}

	mDNS_Unlock(m);
	}

// ***************************************************************************
#if 0
#pragma mark -
#pragma mark - Packet Reception Functions
#endif

mDNSlocal mDNSBool AddRecordToResponseList(ResourceRecord **nrp,
	ResourceRecord *rr, const mDNSu8 *answerto, ResourceRecord *additionalto)
	{
	if (rr->NextResponse == mDNSNULL && nrp != &rr->NextResponse)
		{
		*nrp = rr;
		rr->NR_AnswerTo     = answerto;
		rr->NR_AdditionalTo = additionalto;
		return(mDNStrue);
		}
	else debugf("AddRecordToResponseList: %##s (%s) already in list", rr->name.c, DNSTypeName(rr->rrtype));
	return(mDNSfalse);
	}

#define MustSendRecord(RR) ((RR)->NR_AnswerTo || (RR)->NR_AdditionalTo)

mDNSlocal mDNSu8 *GenerateUnicastResponse(const DNSMessage *const query, const mDNSu8 *const end,
	const mDNSIPAddr InterfaceAddr, DNSMessage *const reply, ResourceRecord  *ResponseRecords)
	{
	const mDNSu8    *const limit     = reply->data + sizeof(reply->data);
	const mDNSu8    *ptr             = query->data;
	mDNSu8          *responseptr     = reply->data;
	ResourceRecord  *rr;
	int i;

	// Initialize the response fields so we can answer the questions
	InitializeDNSMessage(&reply->h, query->h.id, ResponseFlags);

	// ***
	// *** 1. Write out the list of questions we are actually going to answer with this packet
	// ***
	for (i=0; i<query->h.numQuestions; i++)						// For each question...
		{
		DNSQuestion q;
		ptr = getQuestion(query, ptr, end, InterfaceAddr, &q);	// get the question...
		if (!ptr) return(mDNSNULL);

		for (rr=ResponseRecords; rr; rr=rr->NextResponse)		// and search our list of proposed answers
			{
			if (rr->NR_AnswerTo == ptr)							// If we're going to generate a record answering this question
				{												// then put the question in the question section
				responseptr = putQuestion(reply, responseptr, limit, &q.name, q.rrtype, q.rrclass);
				if (!responseptr) { debugf("GenerateUnicastResponse: Ran out of space for questions!"); return(mDNSNULL); }
				break;		// break out of the ResponseRecords loop, and go on to the next question
				}
			}
		}

	if (reply->h.numQuestions == 0) { debugf("GenerateUnicastResponse: ERROR! Why no questions?"); return(mDNSNULL); }

	// ***
	// *** 2. Write answers and additionals
	// ***
	for (rr=ResponseRecords; rr; rr=rr->NextResponse)
		{
		if (MustSendRecord(rr))
			{
			if (rr->NR_AnswerTo)
				{
				mDNSu8 *p = putResourceRecord(reply, responseptr, &reply->h.numAnswers, rr, mDNSNULL, 0);
				if (p) responseptr = p;
				else { debugf("GenerateUnicastResponse: Ran out of space for answers!"); reply->h.flags.b[0] |= kDNSFlag0_TC; }
				}
			else
				{
				mDNSu8 *p = putResourceRecord(reply, responseptr, &reply->h.numAdditionals, rr, mDNSNULL, 0);
				if (p) responseptr = p;
				else debugf("GenerateUnicastResponse: No more space for additionals");
				}
			}
		}
	return(responseptr);
	}

// ResourceRecord *pktrr is the ResourceRecord from the response packet we've witnessed on the network
// ResourceRecord *rr is our ResourceRecord
// Returns 0 if there is no conflict
// Returns +1 if there was a conflict and we won
// Returns -1 if there was a conflict and we lost and have to rename
mDNSlocal int CompareRData(ResourceRecord *pkt, ResourceRecord *our)
	{
	mDNSu8 pktdata[256], *pktptr = pktdata, *pktend;
	mDNSu8 ourdata[256], *ourptr = ourdata, *ourend;
	if (!pkt) { debugf("CompareRData ERROR: pkt is NULL"); return(+1); }
	if (!our) { debugf("CompareRData ERROR: our is NULL"); return(+1); }

	pktend = putRData(mDNSNULL, pktdata, pktdata + sizeof(pktdata), pkt->rrtype, pkt->rdata);
	ourend = putRData(mDNSNULL, ourdata, ourdata + sizeof(ourdata), our->rrtype, our->rdata);
	while (pktptr < pktend && ourptr < ourend && *pktptr == *ourptr) { pktptr++; ourptr++; }
	if (pktptr >= pktend && ourptr >= ourend) return(0);			// If data identical, not a conflict

	if (pktptr >= pktend) return(-1);								// Packet data is substring; We lost
	if (ourptr >= ourend) return(+1);								// Our data is substring; We won
	if (*pktptr < *ourptr) return(-1);								// Packet data is numerically lower; We lost
	if (*pktptr > *ourptr) return(+1);								// Our data is numerically lower; We won
	
	debugf("CompareRData: How did we get here?");
	return(-1);
	}

// Find the canonical DependentOn record for this RR received in a packet.
// The DependentOn pointer is typically used for the TXT record of service registrations
// It indicates that there is no inherent conflict detection for the TXT record
// -- it depends on the SRV record to resolve name conflicts
// If we find any identical ResourceRecord in our authoritative list, then follow its DependentOn
// pointers (if any) to make sure we return the canonical DependentOn record
// If the record has no DependentOn, then just return that record's pointer
// Returns NULL if we don't have any local RRs that are identical to the one from the packet
mDNSlocal const ResourceRecord *FindDependentOn(const mDNS *const m, const ResourceRecord *const pktrr)
	{
	const ResourceRecord *rr;
	for (rr = m->ResourceRecords; rr; rr=rr->next)
		{
		if (IdenticalResourceRecordAnyInterface(rr, pktrr))
			{
			while (rr->DependentOn) rr = rr->DependentOn;
			return(rr);
			}
		}
	return(mDNSNULL);
	}

// Find the canonical RRSet pointer for this RR received in a packet.
// If we find any identical ResourceRecord in our authoritative list, then follow its RRSet
// pointers (if any) to make sure we return the canonical member of this name/type/class
// Returns NULL if we don't have any local RRs that are identical to the one from the packet
mDNSlocal const ResourceRecord *FindRRSet(const mDNS *const m, const ResourceRecord *const pktrr)
	{
	const ResourceRecord *rr;
	for (rr = m->ResourceRecords; rr; rr=rr->next)
		{
		if (IdenticalResourceRecordAnyInterface(rr, pktrr))
			{
			while (rr->RRSet && rr != rr->RRSet) rr = rr->RRSet;
			return(rr);
			}
		}
	return(mDNSNULL);
	}

// PacketRRConflict is called when we've received an RR (pktrr) which has the same name
// as one of our records (our) but different rdata.
// 1. If our record is not a type that's supposed to be unique, we don't care.
// 2a. If our record is marked as dependent on some other record for conflict detection, ignore this one.
// 2b. If the packet rr exactly matches one of our other RRs, and *that* record's DependentOn pointer
//     points to our record, ignore this conflict (e.g. the packet record matches one of our
//     TXT records, and that record is marked as dependent on 'our', its SRV record).
// 3. If we have some *other* RR that exactly matches the one from the packet, and that record and our record
//    are members of the same RRSet, then this is not a conflict.
mDNSlocal mDNSBool PacketRRConflict(const mDNS *const m, const ResourceRecord *const our, const ResourceRecord *const pktrr)
	{
	const ResourceRecord *ourset = our->RRSet ? our->RRSet : our;

	// If not supposed to be unique, not a conflict
	if (!(our->RecordType & kDNSRecordTypeUniqueMask)) return(mDNSfalse);

	// If a dependent record, not a conflict
	if (our->DependentOn || FindDependentOn(m, pktrr) == our) return(mDNSfalse);

	// If the pktrr matches a member of ourset, not a conflict
	if (FindRRSet(m, pktrr) == ourset) return(mDNSfalse);

	// Okay, this is a conflict
	return(mDNStrue);
	}

// NOTE: ResolveSimultaneousProbe calls mDNS_Deregister_internal which can call a user callback, which may change
// the record list and/or question list.
// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
mDNSlocal void ResolveSimultaneousProbe(mDNS *const m, const DNSMessage *const query, const mDNSu8 *const end,
	DNSQuestion *q, ResourceRecord *our, const mDNSs32 timenow)
	{
	int i;
	const mDNSu8 *ptr = LocateAuthorities(query, end);
	mDNSBool FoundUpdate = mDNSfalse;

	for (i = 0; i < query->h.numAuthorities; i++)
		{
		ResourceRecord pktrr;
		ptr = getResourceRecord(query, ptr, end, q->InterfaceAddr, 0, 0, &pktrr, mDNSNULL);
		if (!ptr) break;
		if (ResourceRecordAnswersQuestion(&pktrr, q))
			{
			FoundUpdate = mDNStrue;
			if (PacketRRConflict(m, our, &pktrr))
				{
				int result          = (int)pktrr.rrclass - (int)our->rrclass;
				if (!result) result = (int)pktrr.rrtype  - (int)our->rrtype;
				if (!result) result = CompareRData(&pktrr, our);
				switch (result)
					{
					case  1:	debugf("ResolveSimultaneousProbe: %##s (%s): We won",  our->name.c, DNSTypeName(our->rrtype));
								break;
					case  0:	break;
					case -1:	debugf("ResolveSimultaneousProbe: %##s (%s): We lost", our->name.c, DNSTypeName(our->rrtype));
								mDNS_Deregister_internal(m, our, timenow, mDNS_Dereg_conflict);
								return;
					}
				}
			}
		}
	if (!FoundUpdate)
		debugf("ResolveSimultaneousProbe: %##s (%s): No Update Record found", our->name.c, DNSTypeName(our->rrtype));
	}

// ProcessQuery examines a received query to see if we have any answers to give
mDNSlocal mDNSu8 *ProcessQuery(mDNS *const m, const DNSMessage *const query, const mDNSu8 *const end,
	const mDNSIPAddr srcaddr, const mDNSIPAddr InterfaceAddr,
	DNSMessage *const replyunicast, mDNSBool replymulticast, const mDNSs32 timenow)
	{
	ResourceRecord  *ResponseRecords = mDNSNULL;
	ResourceRecord **nrp             = &ResponseRecords;
	mDNSBool         delayresponse   = mDNSfalse;
	mDNSBool         answers         = mDNSfalse;
	const mDNSu8    *ptr             = query->data;
	mDNSu8          *responseptr     = mDNSNULL;
	ResourceRecord  *rr, *rr2;
	int i;

	// If TC flag is set, it means we should expect additional duplicate suppression info may be coming in another packet.
	if (query->h.flags.b[0] & kDNSFlag0_TC) delayresponse = mDNStrue;

	// ***
	// *** 1. Parse Question Section and mark potential answers
	// ***
	for (i=0; i<query->h.numQuestions; i++)						// For each question...
		{
		int NumAnswersForThisQuestion = 0;
		DNSQuestion q;
		ptr = getQuestion(query, ptr, end, InterfaceAddr, &q);	// get the question...
		if (!ptr) goto exit;
		
		// Note: We use the m->CurrentRecord mechanism here because calling ResolveSimultaneousProbe
		// can result in user callbacks which may change the record list and/or question list.
		// Also note: we just mark potential answer records here, without trying to build the
		// "ResponseRecords" list, because we don't want to risk user callbacks deleting records
		//  from that list while we're in the middle of trying to build it.
		if (m->CurrentRecord) debugf("ProcessQuery ERROR m->CurrentRecord already set");
		m->CurrentRecord = m->ResourceRecords;
		while (m->CurrentRecord)
			{
			rr = m->CurrentRecord;
			m->CurrentRecord = rr->next;
			if (ResourceRecordAnswersQuestion(rr, &q))
				{
				if (rr->RecordType == kDNSRecordTypeUnique)
					ResolveSimultaneousProbe(m, query, end, &q, rr, timenow);
				else if (ResourceRecordIsValidAnswer(rr))
					{
					NumAnswersForThisQuestion++;
					if (!rr->NR_AnswerTo) rr->NR_AnswerTo = ptr;	// Mark as potential answer
					}
				}
			}
		// If we couldn't answer this question, someone else might be able to,
		// so use random delay on response to reduce collisions
		if (NumAnswersForThisQuestion == 0) delayresponse = mDNStrue;
		}

	// ***
	// *** 2. Now we can safely build the list of marked answers
	// ***
	for (rr = m->ResourceRecords; rr; rr=rr->next)				// Now build our list of potential answers
		if (rr->NR_AnswerTo)									// If we marked the record...
			if (AddRecordToResponseList(nrp, rr, rr->NR_AnswerTo, mDNSNULL))	// ... add it to the list
				{
				nrp = &rr->NextResponse;
				if (rr->RecordType == kDNSRecordTypeShared) delayresponse = mDNStrue;
				}

	// ***
	// *** 3. Add additional records
	// ***
	for (rr=ResponseRecords; rr; rr=rr->NextResponse)			// For each record we plan to put
		{
		// (Note: This is an "if", not a "while". If we add a record, we'll find it again
		// later in the "for" loop, and we will follow further "additional" links then.)
		if (rr->Additional1 && ResourceRecordIsValidInterfaceAnswer(rr->Additional1, InterfaceAddr) &&
			AddRecordToResponseList(nrp, rr->Additional1, mDNSNULL, rr))
			nrp = &rr->Additional1->NextResponse;

		if (rr->Additional2 && ResourceRecordIsValidInterfaceAnswer(rr->Additional2, InterfaceAddr) &&
			AddRecordToResponseList(nrp, rr->Additional2, mDNSNULL, rr))
			nrp = &rr->Additional2->NextResponse;
		
		// For SRV records, automatically add the Address record(s) for the target host
		if (rr->rrtype == kDNSType_SRV)
			for (rr2=m->ResourceRecords; rr2; rr2=rr2->next)					// Scan list of resource records
				if (rr2->rrtype == kDNSType_A &&								// For all records type "A" ...
					ResourceRecordIsValidInterfaceAnswer(rr2, InterfaceAddr) &&	// ... which are valid for answer ...
					SameDomainName(&rr->rdata->u.srv.target, &rr2->name) &&		// ... whose name is the name of the SRV target
					AddRecordToResponseList(nrp, rr2, mDNSNULL, rr))
					nrp = &rr2->NextResponse;
		}

	// ***
	// *** 4. Parse Answer Section and cancel any records disallowed by duplicate suppression
	// ***
	for (i=0; i<query->h.numAnswers; i++)						// For each record in the query's answer section...
		{
		// Get the record...
		ResourceRecord pktrr, *rr;
		ptr = getResourceRecord(query, ptr, end, InterfaceAddr, timenow, kDNSRecordTypePacketAnswer, &pktrr, mDNSNULL);
		if (!ptr) goto exit;

		// See if it suppresses any of our planned answers
		for (rr=ResponseRecords; rr; rr=rr->NextResponse)
			if (MustSendRecord(rr) && SuppressDuplicate(&pktrr, rr))
				{ rr->NR_AnswerTo = mDNSNULL; rr->NR_AdditionalTo = mDNSNULL; }

		// And see if it suppresses any previously scheduled answers
		for (rr=m->ResourceRecords; rr; rr=rr->next)
			{
			// If this record has been requested by exactly one client, and that client is
			// the same one sending this query, then allow inter-packet duplicate suppression
			if (rr->Requester.NotAnInteger && rr->Requester.NotAnInteger == srcaddr.NotAnInteger)
				if (SuppressDuplicate(&pktrr, rr))
					{
					rr->SendPriority = 0;
					rr->Requester    = zeroIPAddr;
					}
			}
		}

	// ***
	// *** 5. Cancel any additionals that were added because of now-deleted records
	// ***
	for (rr=ResponseRecords; rr; rr=rr->NextResponse)
		if (rr->NR_AdditionalTo && !MustSendRecord(rr->NR_AdditionalTo))
			{ rr->NR_AnswerTo = mDNSNULL; rr->NR_AdditionalTo = mDNSNULL; }

	// ***
	// *** 6. Mark the send flags on the records we plan to send
	// ***
	for (rr=ResponseRecords; rr; rr=rr->NextResponse)
		{
		if (MustSendRecord(rr))
			{
			// For oversized records which we are going to send back to the requester via unicast
			// anyway, don't waste network bandwidth by also sending them via multicast.
			// This means we lose passive conflict detection for these oversized records, but
			// that is a reasonable tradeoff -- these large records usually have an associated
			// SRV record with the same name which will catch conflicts for us anyway.
			mDNSBool LargeRecordWithUnicastReply = (rr->rdestimate > 1024 && replyunicast);

			if (rr->NR_AnswerTo)
				answers = mDNStrue;

			if (replymulticast && !LargeRecordWithUnicastReply)
				{
				// If this query has additional duplicate suppression info
				// coming in another packet, then remember the requesting IP address
				if (query->h.flags.b[0] & kDNSFlag0_TC)
					{
					// We can only store one IP address at a time per record, so if we've already
					// stored one address, set it to some special distinguished value instead
					if (rr->Requester.NotAnInteger == zeroIPAddr.NotAnInteger) rr->Requester = srcaddr;
					else                                                       rr->Requester = onesIPAddr;
					}
				if (rr->NR_AnswerTo)
					{
					// This is a direct answer in response to one of the questions
					rr->SendPriority = kDNSSendPriorityAnswer;
					}
				else
					{
					// This is an additional record supporting one of our answers
					if (rr->SendPriority < kDNSSendPriorityAdditional)
						rr->SendPriority = kDNSSendPriorityAdditional;
					}
				}
			}
		}

	// ***
	// *** 7. If we think other machines are likely to answer these questions, set our packet suppression timer
	// ***
	if (delayresponse && !m->SuppressSending)
		{
		// Pick a random delay between 20ms and 120ms.
		m->SuppressSending = timenow + (mDNSPlatformOneSecond*2 + (mDNSs32)mDNSRandom((mDNSu32)mDNSPlatformOneSecond*10)) / 100;
		if (m->SuppressSending == 0) m->SuppressSending = 1;
		}

	// ***
	// *** 8. If query is from a legacy client, generate a unicast reply too
	// ***
	if (answers && replyunicast)
		responseptr = GenerateUnicastResponse(query, end, InterfaceAddr, replyunicast, ResponseRecords);

exit:
	// ***
	// *** 9. Finally, clear our NextResponse link chain ready for use next time
	// ***
	while (ResponseRecords)
		{
		rr = ResponseRecords;
		ResponseRecords = rr->NextResponse;
		rr->NextResponse    = mDNSNULL;
		rr->NR_AnswerTo     = mDNSNULL;
		rr->NR_AdditionalTo = mDNSNULL;
		}
	
	return(responseptr);
	}

mDNSlocal void mDNSCoreReceiveQuery(mDNS *const m, const DNSMessage *const msg, const mDNSu8 *const end,
	const mDNSIPAddr srcaddr, const mDNSIPPort srcport, const mDNSIPAddr dstaddr, mDNSIPPort dstport, const mDNSIPAddr InterfaceAddr)
	{
	const mDNSs32 timenow        = mDNSPlatformTimeNow();
	DNSMessage    response;
	const mDNSu8 *responseend    = mDNSNULL;
	DNSMessage   *replyunicast   = mDNSNULL;
	mDNSBool      replymulticast = mDNSfalse;
	
	verbosedebugf("Received Query from %.4a:%d to %.4a:%d on %.4a with %d Question%s, %d Answer%s, %d Authorit%s, %d Additional%s",
		&srcaddr, (mDNSu16)srcport.b[0]<<8 | srcport.b[1],
		&dstaddr, (mDNSu16)dstport.b[0]<<8 | dstport.b[1],
		&InterfaceAddr,
		msg->h.numQuestions,   msg->h.numQuestions   == 1 ? "" : "s",
		msg->h.numAnswers,     msg->h.numAnswers     == 1 ? "" : "s",
		msg->h.numAuthorities, msg->h.numAuthorities == 1 ? "y" : "ies",
		msg->h.numAdditionals, msg->h.numAdditionals == 1 ? "" : "s");

	// If this was a unicast query, or it was from an old (non-port-5353) client, then send a unicast response
	if (dstaddr.NotAnInteger != AllDNSLinkGroup.NotAnInteger || srcport.NotAnInteger != MulticastDNSPort.NotAnInteger)
		replyunicast = &response;
	
	// If this was a multicast query, then we need to send a multicast response
	if (dstaddr.NotAnInteger == AllDNSLinkGroup.NotAnInteger) replymulticast = mDNStrue;
	
	responseend = ProcessQuery(m, msg, end, srcaddr, InterfaceAddr, replyunicast, replymulticast, timenow);
	if (replyunicast && responseend)
		{
		mDNSSendDNSMessage(m, replyunicast, responseend, InterfaceAddr, dstport, srcaddr, srcport);
		verbosedebugf("Unicast Response: %d Answer%s, %d Additional%s on %.4a",
			replyunicast->h.numAnswers,     replyunicast->h.numAnswers     == 1 ? "" : "s",
			replyunicast->h.numAdditionals, replyunicast->h.numAdditionals == 1 ? "" : "s", &InterfaceAddr);
		}
	}

// NOTE: mDNSCoreReceiveResponse calls mDNS_Deregister_internal which can call a user callback, which may change
// the record list and/or question list.
// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
	const DNSMessage *const response, const mDNSu8 *end, const mDNSIPAddr dstaddr, const mDNSIPAddr InterfaceAddr)
	{
	int i;
	const mDNSs32 timenow = mDNSPlatformTimeNow();
	
	// We ignore questions (if any) in a DNS response packet
	const mDNSu8 *ptr = LocateAnswers(response, end);

	// All records in a DNS response packet are treated as equally valid statements of truth. If we want
	// to guard against spoof replies, then the only credible protection against that is cryptographic
	// security, e.g. DNSSEC., not worring about which section in the spoof packet contained the record
	int totalrecords = response->h.numAnswers + response->h.numAuthorities + response->h.numAdditionals;

	verbosedebugf("Received Response addressed to %.4a on %.4a with %d Question%s, %d Answer%s, %d Authorit%s, %d Additional%s",
		&dstaddr, &InterfaceAddr,
		response->h.numQuestions,   response->h.numQuestions   == 1 ? "" : "s",
		response->h.numAnswers,     response->h.numAnswers     == 1 ? "" : "s",
		response->h.numAuthorities, response->h.numAuthorities == 1 ? "y" : "ies",
		response->h.numAdditionals, response->h.numAdditionals == 1 ? "" : "s");

	// Other mDNS devices may issue unicast queries (which we correctly answer),
	// but we never *issue* unicast queries, so if we ever receive a unicast
	// response then it is someone trying to spoof us, so ignore it!
	if (dstaddr.NotAnInteger != AllDNSLinkGroup.NotAnInteger)
		{ debugf("** Ignored attempted spoof unicast mDNS response packet **"); return; }

	for (i = 0; i < totalrecords && ptr && ptr < end; i++)
		{
		ResourceRecord pktrr;
		mDNSu8 RecordType = (i < response->h.numAnswers) ? kDNSRecordTypePacketAnswer : kDNSRecordTypePacketAdditional;
		ptr = getResourceRecord(response, ptr, end, InterfaceAddr, timenow, RecordType, &pktrr, mDNSNULL);
		if (!ptr) return;

		// 1. Check that this packet resource record does not conflict with any of ours
		if (m->CurrentRecord) debugf("mDNSCoreReceiveResponse ERROR m->CurrentRecord already set");
		m->CurrentRecord = m->ResourceRecords;
		while (m->CurrentRecord)
			{
			ResourceRecord *rr = m->CurrentRecord;
			m->CurrentRecord = rr->next;
			if (SameResourceRecordSignature(&pktrr, rr))		// If interface, name, type and class match...
				{												// ... check to see if rdata is identical
				if (SameRData(pktrr.rrtype, pktrr.rdata, rr->rdata))
					{
					// If the RR in the packet is identical to ours, just check they're not trying to lower the TTL on us
					if (pktrr.rroriginalttl >= rr->rroriginalttl || m->SleepState)
						rr->SendPriority = kDNSSendPriorityNone;
					else
						rr->SendPriority = kDNSSendPriorityAnswer;
					}
				else
					{
					// else, the packet RR has different rdata -- check to see if this is a conflict
					if (pktrr.rroriginalttl > 0 && PacketRRConflict(m, rr, &pktrr))
						{
						if (rr->rrtype == kDNSType_SRV)
							{
							debugf("mDNSCoreReceiveResponse: Our Data %d %##s", rr->rdata->RDLength,   rr->rdata->u.srv.target.c);
							debugf("mDNSCoreReceiveResponse: Pkt Data %d %##s", pktrr.rdata->RDLength, pktrr.rdata->u.srv.target.c);
							}
						else if (rr->rrtype == kDNSType_TXT)
							{
							debugf("mDNSCoreReceiveResponse: Our Data %d %#s", rr->rdata->RDLength,   rr->rdata->u.txt.c);
							debugf("mDNSCoreReceiveResponse: Pkt Data %d %#s", pktrr.rdata->RDLength, pktrr.rdata->u.txt.c);
							}
						else if (rr->rrtype == kDNSType_A)
							{
							debugf("mDNSCoreReceiveResponse: Our Data %.4a", &rr->rdata->u.ip);
							debugf("mDNSCoreReceiveResponse: Pkt Data %.4a", &pktrr.rdata->u.ip);
							}
						// If we've just whacked this record's ProbeCount, don't need to do it again
						if (rr->ProbeCount <= DefaultProbeCountForTypeUnique)
							{
							if (rr->RecordType == kDNSRecordTypeVerified)
								{
								debugf("mDNSCoreReceiveResponse: Reseting to Probing: %##s (%s)", rr->name.c, DNSTypeName(rr->rrtype));
								// If we'd previously verified this record, put it back to probing state and try again
								rr->RecordType       = kDNSRecordTypeUnique;
								rr->ProbeCount       = DefaultProbeCountForTypeUnique + 1;
								rr->NextSendTime     = timenow;
								rr->NextSendInterval = DefaultSendIntervalForRecordType(kDNSRecordTypeUnique);
								}
							else
								{
								debugf("mDNSCoreReceiveResponse: Will rename %##s (%s)", rr->name.c, DNSTypeName(rr->rrtype));
								// If we're probing for this record (or we assumed it must be unique) we just failed
								mDNS_Deregister_internal(m, rr, timenow, mDNS_Dereg_conflict);
								}
							}
						}
					}
				}
			}

		// 2. See if we want to add this packet resource record to our cache
		if (m->rrcache_size)	// Only try to cache answers if we have a cache to put them in
			{
			ResourceRecord *rr;
			// 2a. Check if this packet resource record is already in our cache
			for (rr = m->rrcache; rr; rr=rr->next)
				{
				// If we found this exact resource record, refresh its TTL
				if (IdenticalResourceRecord(&pktrr, rr))
					{
					//debugf("Found RR %##s size %d already in cache", pktrr.name.c, pktrr.rdata->RDLength);
					rr->TimeRcvd = timenow;
					rr->UnansweredQueries = 0;
					rr->NewData = mDNStrue;
					// If we're deleting a record, push it out one second into the future
					// to give other hosts on the network a chance to protest
					if (pktrr.rroriginalttl == 0) rr->rroriginalttl = 1;
					else rr->rroriginalttl = pktrr.rroriginalttl;
					break;
					}
				}

			// If packet resource record not in our cache, add it now
			// (unless it is just a deletion of a record we never had, in which case we don't care)
			if (!rr && pktrr.rroriginalttl > 0)
				{
				rr = GetFreeCacheRR(m, timenow);
				if (!rr) debugf("No cache space to add record for %#s", pktrr.name.c);
				else
					{
					*rr = pktrr;
					rr->rdata = &rr->rdatastorage;	// For now, all cache records use local storage
					rr->next = m->rrcache;
					m->rrcache = rr;
					if ((rr->RecordType & kDNSRecordTypeUniqueMask) == 0)
						TriggerImmediateQuestions(m, rr, timenow);
					//debugf("Adding RR %##s to cache (%d)", pktrr.name.c, m->rrcache_used);
					AnswerLocalQuestions(m, rr, timenow);
					}
				}
			}
		}

	// If we have a cache, then run through all the new records that we've just added,
	// clear their 'NewData' flags, and if they were marked as unique in the packet,
	// then search our cache for any records with the same name/type/class,
	// and purge them if they are more than one second old.
	if (m->rrcache_size)
		{
		ResourceRecord *rr;
		for (rr = m->rrcache; rr; rr=rr->next)
			{
			if (rr->NewData)
				{
				rr->NewData = mDNSfalse;
				if (rr->RecordType & kDNSRecordTypeUniqueMask)
					{
					ResourceRecord *r;
					for (r = m->rrcache; r; r=r->next)
						if (SameResourceRecordSignature(rr, r) && timenow - r->TimeRcvd > mDNSPlatformOneSecond)
							r->rroriginalttl = 0;
					}
				}
			}
		TidyRRCache(m, timenow);
		}
	}

mDNSexport void mDNSCoreReceive(mDNS *const m, DNSMessage *const msg, const mDNSu8 *const end,
	mDNSIPAddr srcaddr, mDNSIPPort srcport, mDNSIPAddr dstaddr, mDNSIPPort dstport, mDNSIPAddr InterfaceAddr)
	{
	const mDNSu8 StdQ = kDNSFlag0_QR_Query    | kDNSFlag0_OP_StdQuery;
	const mDNSu8 StdR = kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery;
	mDNSu8 QR_OP = (mDNSu8)(msg->h.flags.b[0] & kDNSFlag0_QROP_Mask);
	
	// Read the integer parts which are in IETF byte-order (MSB first, LSB second)
	mDNSu8 *ptr = (mDNSu8 *)&msg->h.numQuestions;
	msg->h.numQuestions   = (mDNSu16)((mDNSu16)ptr[0] <<  8 | ptr[1]);
	msg->h.numAnswers     = (mDNSu16)((mDNSu16)ptr[2] <<  8 | ptr[3]);
	msg->h.numAuthorities = (mDNSu16)((mDNSu16)ptr[4] <<  8 | ptr[5]);
	msg->h.numAdditionals = (mDNSu16)((mDNSu16)ptr[6] <<  8 | ptr[7]);
	
	if (!m) { debugf("mDNSCoreReceive ERROR m is NULL"); return; }
	
	mDNS_Lock(m);
	if (m->mDNS_busy > 1) debugf("mDNSCoreReceive: Locking failure! mDNS already busy");

	if      (QR_OP == StdQ) mDNSCoreReceiveQuery   (m, msg, end, srcaddr, srcport, dstaddr, dstport, InterfaceAddr);
	else if (QR_OP == StdR) mDNSCoreReceiveResponse(m, msg, end,                   dstaddr,          InterfaceAddr);
	else debugf("Unknown DNS packet type %02X%02X (ignored)", msg->h.flags.b[0], msg->h.flags.b[1]);

	// Packet reception often causes a change to the task list:
	// 1. Inbound queries can cause us to need to send responses
	// 2. Conflicing response packets received from other hosts can cause us to need to send defensive responses
	// 3. Other hosts announcing deletion of shared records can cause us to need to re-assert those records
	// 4. Response packets that answer questions may cause our client to issue new questions
	mDNS_Unlock(m);
	}

// ***************************************************************************
#if 0
#pragma mark -
#pragma mark -
#pragma mark - Searcher Functions
#endif

mDNSlocal DNSQuestion *FindDuplicateQuestion(const mDNS *const m, const DNSQuestion *const question)
	{
	DNSQuestion *q;
	for (q = m->ActiveQuestions; q; q=q->next)		// Scan our list of questions
		if (q->rrtype  == question->rrtype  &&
			q->rrclass == question->rrclass &&
			SameDomainName(&q->name, &question->name)) return(q);
	return(mDNSNULL);
	}

// This is called after a question is deleted, in case other identical questions were being
// suppressed as duplicates
mDNSlocal void UpdateQuestionDuplicates(const mDNS *const m, const DNSQuestion *const question)
	{
	DNSQuestion *q;
	for (q = m->ActiveQuestions; q; q=q->next)	// Scan our list of questions
		if (q->DuplicateOf == question)			// To see if any questions were referencing this as their duplicate
			{
			q->NextQTime     = question->NextQTime;
			q->ThisQInterval = question->ThisQInterval;
			q->NextQInterval = question->NextQInterval;
			q->DuplicateOf   = FindDuplicateQuestion(m, q);
			}
	}

mDNSlocal mStatus mDNS_StartQuery_internal(mDNS *const m, DNSQuestion *const question, const mDNSs32 timenow)
	{
	if (m->rrcache_size == 0)	// Can't do queries if we have no cache space allocated
		return(mStatus_NoCache);
	else
		{
		DNSQuestion **q = &m->ActiveQuestions;
		while (*q && *q != question) q=&(*q)->next;

		if (*q)
			{
			debugf("Error! Tried to add a question that's already in the active list");
			return(mStatus_AlreadyRegistered);
			}

		question->next          = mDNSNULL;
		question->NextQTime     = timenow;
		question->ThisQInterval = mDNSPlatformOneSecond;  // MUST NOT be zero for an active question
		question->NextQInterval = mDNSPlatformOneSecond;
		question->DuplicateOf   = FindDuplicateQuestion(m, question);
		*q = question;
		
		if (!m->NewQuestions) m->NewQuestions = question;

		return(mStatus_NoError);
		}
	}

mDNSlocal void mDNS_StopQuery_internal(mDNS *const m, DNSQuestion *const question)
	{
	DNSQuestion **q = &m->ActiveQuestions;
	while (*q && *q != question) q=&(*q)->next;
	if (*q) *q = (*q)->next;
	else debugf("mDNS_StopQuery_internal: Question %##s (%s) not found in active list",
		question->name.c, DNSTypeName(question->rrtype));

	UpdateQuestionDuplicates(m, question);

	question->next = mDNSNULL;
	question->ThisQInterval = 0;
	question->NextQInterval = 0;
	
	// If we just deleted the question that AnswerLocalQuestions() is about to look at,
	// bump its pointer forward one question.
	if (m->CurrentQuestion == question)
		{
		debugf("mDNS_StopQuery_internal: Just deleted the currently active question.");
		m->CurrentQuestion = m->CurrentQuestion->next;
		}

	if (m->NewQuestions    == question)
		{
		debugf("mDNS_StopQuery_internal: Just deleted a new question that wasn't even answered yet.");
		m->NewQuestions    = m->NewQuestions->next;
		}
	
	}

mDNSexport mStatus mDNS_StartQuery(mDNS *const m, DNSQuestion *const question)
	{
	const mDNSs32 timenow = mDNS_Lock(m);
	mStatus status = mDNS_StartQuery_internal(m, question, timenow);
	mDNS_Unlock(m);
	return(status);
	}

mDNSexport void mDNS_StopQuery(mDNS *const m, DNSQuestion *const question)
	{
	mDNS_Lock(m);
	mDNS_StopQuery_internal(m, question);
	mDNS_Unlock(m);
	}

mDNSexport mStatus mDNS_StartBrowse(mDNS *const m, DNSQuestion *const question,
	const domainname *const srv, const domainname *const domain,
	const mDNSIPAddr InterfaceAddr, mDNSQuestionCallback *Callback, void *Context)
	{
	question->InterfaceAddr = InterfaceAddr;
	question->name = *srv;
	AppendDomainNameToName(&question->name, domain);
	question->rrtype    = kDNSType_PTR;
	question->rrclass   = kDNSClass_IN;
	question->Callback  = Callback;
	question->Context   = Context;
	return(mDNS_StartQuery(m, question));
	}

mDNSlocal void FoundServiceInfoSRV(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer)
	{
	ServiceInfoQuery *query = (ServiceInfoQuery *)question->Context;
	if (answer->rrremainingttl == 0) return;
	if (answer->rrtype != kDNSType_SRV) return;

	query->info->port = answer->rdata->u.srv.port;

	// If this is our first answer, then set the GotSRV flag and start the address query
	if (!query->GotSRV)
		{
		query->GotSRV             = mDNStrue;
		query->qADD.name          = answer->rdata->u.srv.target;
		mDNS_StartQuery_internal(m, &query->qADD, mDNSPlatformTimeNow());
		}
	// If this is not our first answer, only re-issue the address query if the target host name has changed
	else if (!SameDomainName(&query->qADD.name, &answer->rdata->u.srv.target))
		{
		mDNS_StopQuery_internal(m, &query->qADD);
		query->qADD.name          = answer->rdata->u.srv.target;
		mDNS_StartQuery_internal(m, &query->qADD, mDNSPlatformTimeNow());
		}

	// Don't need to do ScheduleNextTask because this callback can only ever happen
	// (a) as a result of an immediate result from the mDNS_StartQuery call, or
	// (b) as a result of receiving a packet on the wire
	// both of which will result in a subsequent ScheduleNextTask call of their own
	}

mDNSlocal void FoundServiceInfoTXT(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer)
	{
	ServiceInfoQuery *query = (ServiceInfoQuery *)question->Context;
	if (answer->rrremainingttl == 0) return;
	if (answer->rrtype != kDNSType_TXT) return;
	if (answer->rdata->RDLength > sizeof(query->info->TXTinfo)) return;

	query->GotTXT       = 1 + (query->GotTXT || query->GotADD);
	query->info->TXTlen = answer->rdata->RDLength;
	mDNSPlatformMemCopy(answer->rdata->u.txt.c, query->info->TXTinfo, answer->rdata->RDLength);

	debugf("FoundServiceInfoTXT: %##s GotADD=%d", &query->info->name, query->GotADD);

	if (query->Callback && query->GotADD)
		query->Callback(m, query);
	}

mDNSlocal void FoundServiceInfoADD(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer)
	{
	ServiceInfoQuery *query = (ServiceInfoQuery *)question->Context;
	if (answer->rrremainingttl == 0) return;
	if (answer->rrtype != kDNSType_A) return;
	query->GotADD = mDNStrue;
	query->info->InterfaceAddr = answer->InterfaceAddr;
	query->info->ip            = answer->rdata->u.ip;

	debugf("FoundServiceInfoADD: %##s GotTXT=%d", &query->info->name, query->GotTXT);

	// If query->GotTXT is 1 that means we already got a single TXT answer but didn't
	// deliver it to the client at that time, so no further action is required.
	// If query->GotTXT is 2 that means we either got more than one TXT answer,
	// or we got a TXT answer and delivered it to the client at that time, so in either
	// of these cases we may have lost information, so we should re-issue the TXT question.
	if (query->GotTXT > 1)
		{
		mDNS_StopQuery_internal(m, &query->qTXT);
		mDNS_StartQuery_internal(m, &query->qTXT, mDNSPlatformTimeNow());
		}

	if (query->Callback && query->GotTXT)
		query->Callback(m, query);
	}

// On entry, the client must have set the name and InterfaceAddr fields of the ServiceInfo structure
// If the query is not interface-specific, then InterfaceAddr may be zero
// Each time the Callback is invoked, the remainder of the fields will have been filled in
// In addition, InterfaceAddr will be updated to give the interface address corresponding to that reply
mDNSexport mStatus mDNS_StartResolveService(mDNS *const m,
	ServiceInfoQuery *query, ServiceInfo *info, ServiceInfoQueryCallback *Callback, void *Context)
	{
	mStatus status;
	const mDNSs32 timenow = mDNS_Lock(m);
	
	query->qSRV.InterfaceAddr = info->InterfaceAddr;
	query->qSRV.name          = info->name;
	query->qSRV.rrtype        = kDNSType_SRV;
	query->qSRV.rrclass       = kDNSClass_IN;
	query->qSRV.Callback      = FoundServiceInfoSRV;
	query->qSRV.Context       = query;

	query->qTXT.InterfaceAddr = info->InterfaceAddr;
	query->qTXT.name          = info->name;
	query->qTXT.rrtype        = kDNSType_TXT;
	query->qTXT.rrclass       = kDNSClass_IN;
	query->qTXT.Callback      = FoundServiceInfoTXT;
	query->qTXT.Context       = query;

	query->qADD.InterfaceAddr = info->InterfaceAddr;
	query->qADD.name.c[0]     = 0;
	query->qADD.rrtype        = kDNSType_A;
	query->qADD.rrclass       = kDNSClass_IN;
	query->qADD.Callback      = FoundServiceInfoADD;
	query->qADD.Context       = query;

	query->GotSRV             = mDNSfalse;
	query->GotTXT             = mDNSfalse;
	query->GotADD             = mDNSfalse;

	query->info               = info;
	query->Callback           = Callback;
	query->Context            = Context;

//	info->name      = Must already be set up by client
//	info->interface = Must already be set up by client
	info->ip        = zeroIPAddr;
	info->port      = zeroIPPort;
	info->TXTlen    = 0;

	status = mDNS_StartQuery_internal(m, &query->qSRV, timenow);
	if (status == mStatus_NoError) status = mDNS_StartQuery_internal(m, &query->qTXT, timenow);
	if (status != mStatus_NoError) mDNS_StopResolveService(m, query);

	mDNS_Unlock(m);
	return(status);
	}

mDNSexport void    mDNS_StopResolveService (mDNS *const m, ServiceInfoQuery *query)
	{
	mDNS_Lock(m);
	if (query->qSRV.ThisQInterval) mDNS_StopQuery_internal(m, &query->qSRV);
	if (query->qTXT.ThisQInterval) mDNS_StopQuery_internal(m, &query->qTXT);
	if (query->qADD.ThisQInterval) mDNS_StopQuery_internal(m, &query->qADD);
	mDNS_Unlock(m);
	}

mDNSexport mStatus mDNS_GetDomains(mDNS *const m, DNSQuestion *const question, mDNSu8 DomainType,
	const mDNSIPAddr InterfaceAddr, mDNSQuestionCallback *Callback, void *Context)
	{
	question->InterfaceAddr = InterfaceAddr;
	ConvertCStringToDomainName(mDNS_DomainTypeNames[DomainType], &question->name);
	question->rrtype    = kDNSType_PTR;
	question->rrclass   = kDNSClass_IN;
	question->Callback  = Callback;
	question->Context   = Context;
	return(mDNS_StartQuery(m, question));
	}

// ***************************************************************************
#if 0
#pragma mark -
#pragma mark - Responder Functions
#endif

// Set up a ResourceRecord with sensible default values.
// These defaults may be overwritten with new values before mDNS_Register is called
mDNSexport void mDNS_SetupResourceRecord(ResourceRecord *rr, RData *RDataStorage, mDNSIPAddr InterfaceAddr,
	mDNSu16 rrtype, mDNSu32 ttl, mDNSu8 RecordType, mDNSRecordCallback Callback, void *Context)
	{
	// Don't try to store a TTL bigger than we can represent in platform time units
	if (ttl > 0x7FFFFFFFUL / mDNSPlatformOneSecond)
		ttl = 0x7FFFFFFFUL / mDNSPlatformOneSecond;
	else if (ttl == 0)		// And Zero TTL is illegal
		ttl = 1;

	// Field Group 1: Persistent metadata for Authoritative Records
	rr->Additional1       = mDNSNULL;
	rr->Additional2       = mDNSNULL;
	rr->DependentOn       = mDNSNULL;
	rr->RRSet             = mDNSNULL;
	rr->Callback          = Callback;
	rr->Context           = Context;

	rr->RecordType        = RecordType;
	rr->HostTarget        = mDNSfalse;
	
	// Field Group 2: Transient state for Authoritative Records (set in mDNS_Register_internal)
	// Field Group 3: Transient state for Cache Records         (set in mDNS_Register_internal)

	// Field Group 4: The actual information pertaining to this resource record
	rr->InterfaceAddr     = InterfaceAddr;
	rr->name.c[0]         = 0;		// MUST be set by client
	rr->rrtype            = rrtype;
	rr->rrclass           = kDNSClass_IN;
	rr->rroriginalttl     = ttl;
	rr->rrremainingttl    = ttl;
//	rr->rdlength          = MUST set by client and/or in mDNS_Register_internal
//	rr->rdestimate        = set in mDNS_Register_internal
//	rr->rdata             = MUST be set by client

	if (RDataStorage)
		rr->rdata = RDataStorage;
	else
		{
		rr->rdata = &rr->rdatastorage;
		rr->rdata->MaxRDLength = sizeof(RDataBody);
		}
	}

mDNSexport mStatus mDNS_Register(mDNS *const m, ResourceRecord *const rr)
	{
	const mDNSs32 timenow = mDNS_Lock(m);
	mStatus status = mDNS_Register_internal(m, rr, timenow);
	mDNS_Unlock(m);
	return(status);
	}

mDNSexport mStatus mDNS_Update(mDNS *const m, ResourceRecord *const rr, mDNSu32 newttl,
	RData *const newrdata, mDNSRecordUpdateCallback *Callback)
	{
	const mDNSs32 timenow = mDNS_Lock(m);

	// If we already have an update queued up which has not gone through yet,
	// give the client a chance to free that memory
	if (rr->NewRData)
		{
		RData *n = rr->NewRData;
		rr->NewRData = mDNSNULL;	// Clear the NewRData pointer ...
		if (rr->UpdateCallback) rr->UpdateCallback(m, rr, n); // ...and let the client free this memory, if necessary
		}
	
	rr->AnnounceCount    = DefaultAnnounceCountForRecordType(rr->RecordType);
	rr->NextSendTime     = timenow;
	if (rr->RecordType == kDNSRecordTypeUnique && m->SuppressProbes) rr->NextSendTime = m->SuppressProbes;
	rr->NextSendInterval = DefaultSendIntervalForRecordType(rr->RecordType);
	rr->NewRData         = newrdata;
	rr->UpdateCallback   = Callback;
	rr->rroriginalttl    = newttl;
	rr->rrremainingttl   = newttl;
	mDNS_Unlock(m);
	return(mStatus_NoError);
	}

// NOTE: mDNS_Deregister calls mDNS_Deregister_internal which can call a user callback, which may change
// the record list and/or question list.
// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
mDNSexport void mDNS_Deregister(mDNS *const m, ResourceRecord *const rr)
	{
	const mDNSs32 timenow = mDNS_Lock(m);
	mDNS_Deregister_internal(m, rr, timenow, mDNS_Dereg_normal);
	mDNS_Unlock(m);
	}

mDNSexport void mDNS_GenerateFQDN(mDNS *const m)
	{
	// Set up the Primary mDNS FQDN
	m->hostname1.c[0] = 0;
	AppendDomainLabelToName(&m->hostname1, &m->hostlabel);
	AppendStringLabelToName(&m->hostname1, "local");

	// Set up the Secondary mDNS FQDN
	m->hostname2.c[0] = 0;
	AppendDomainLabelToName(&m->hostname2, &m->hostlabel);
	AppendStringLabelToName(&m->hostname2, "local");
	AppendStringLabelToName(&m->hostname2, "arpa");

	// Make sure that any SRV records (and the like) that reference our 
	// host name in their rdata get updated to reference this new host name
	UpdateHostNameTargets(m);
	}

mDNSlocal void HostNameCallback(mDNS *const m, ResourceRecord *const rr, mStatus result)
	{
	#pragma unused(rr)
	switch (result)
		{
		case mStatus_NoError:
			debugf("HostNameCallback: %##s (%s) Name registered",   rr->name.c, DNSTypeName(rr->rrtype));
			break;
		case mStatus_NameConflict:
			debugf("HostNameCallback: %##s (%s) Name conflict",     rr->name.c, DNSTypeName(rr->rrtype));
			break;
		default:
			debugf("HostNameCallback: %##s (%s) Unknown result %d", rr->name.c, DNSTypeName(rr->rrtype), result);
			break;
		}

	if (result == mStatus_NameConflict)
		{
		NetworkInterfaceInfo *hr = mDNSNULL;
		NetworkInterfaceInfo **p = &hr;
		domainlabel oldlabel = m->hostlabel;

		// 1. Deregister all our host sets
		while (m->HostInterfaces)
			{
			NetworkInterfaceInfo *set = m->HostInterfaces;
			mDNS_DeregisterInterface(m, set);
			*p = set;
			p = &set->next;
			}
		
		// 2. Pick a new name
		// First give the client callback a chance to pick a new name
		if (m->Callback) m->Callback(m, mStatus_NameConflict);
		// If the client callback didn't do it, add (or increment) an index ourselves
		if (SameDomainLabel(m->hostlabel.c, oldlabel.c))
			IncrementLabelSuffix(&m->hostlabel, mDNSfalse);
		mDNS_GenerateFQDN(m);
		
		// 3. Re-register all our host sets
		while (hr)
			{
			NetworkInterfaceInfo *set = hr;
			hr = hr->next;
			mDNS_RegisterInterface(m, set);
			}
		}
	}

mDNSlocal NetworkInterfaceInfo *FindFirstAdvertisedInterface(mDNS *const m)
	{
	NetworkInterfaceInfo *i;
	for (i=m->HostInterfaces; i; i=i->next) if (i->Advertise) break;
	return(i);
	}

mDNSexport mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *set)
	{
	const mDNSs32 timenow = mDNS_Lock(m);
	NetworkInterfaceInfo **p = &m->HostInterfaces;
	
	while (*p && *p != set) p=&(*p)->next;
	if (*p)
		{
		debugf("Error! Tried to register a NetworkInterfaceInfo that's already in the list");
		mDNS_Unlock(m);
		return(mStatus_AlreadyRegistered);
		}

	if (set->Advertise)
		{
		char buffer[256];
		NetworkInterfaceInfo *primary = FindFirstAdvertisedInterface(m);
		if (!primary) primary = set; // If no existing advertised interface, this new NetworkInterfaceInfo becomes our new primary
		
		mDNS_SetupResourceRecord(&set->RR_A1,  mDNSNULL, set->ip, kDNSType_A,   60, kDNSRecordTypeUnique,      HostNameCallback, set);
		mDNS_SetupResourceRecord(&set->RR_A2,  mDNSNULL, set->ip, kDNSType_A,   60, kDNSRecordTypeUnique,      HostNameCallback, set);
		mDNS_SetupResourceRecord(&set->RR_PTR, mDNSNULL, set->ip, kDNSType_PTR, 60, kDNSRecordTypeKnownUnique, mDNSNULL, mDNSNULL);
	
		// 1. Set up primary Address record to map from primary host name ("foo.local.") to IP address
		set->RR_A1.name        = m->hostname1;
		set->RR_A1.rdata->u.ip = set->ip;
	
		// 2. Set up secondary Address record to map from secondary host name ("foo.local.arpa.") to IP address
		set->RR_A2.name        = m->hostname2;
		set->RR_A2.rdata->u.ip = set->ip;
	
		// 3. Set up reverse-lookup PTR record to map from our address back to our primary host name
		// Setting HostTarget tells DNS that the target of this PTR is to be automatically kept in sync if our host name changes
		// Note: This is reverse order compared to a normal dotted-decimal IP address
		mDNS_sprintf(buffer, "%d.%d.%d.%d.in-addr.arpa.", set->ip.b[3], set->ip.b[2], set->ip.b[1], set->ip.b[0]);
		ConvertCStringToDomainName(buffer, &set->RR_PTR.name);
		set->RR_PTR.HostTarget = mDNStrue;	// Tell mDNS that the target of this PTR is to be kept in sync with our host name
	
		set->RR_A1.RRSet = &primary->RR_A1;	// May refer to self
		set->RR_A2.RRSet = &primary->RR_A2;	// May refer to self
	
		mDNS_Register_internal(m, &set->RR_A1,  timenow);
		mDNS_Register_internal(m, &set->RR_A2,  timenow);
		mDNS_Register_internal(m, &set->RR_PTR, timenow);
	
		// ... Add an HINFO record, etc.?
		}

	set->next = mDNSNULL;
	*p = set;
	mDNS_Unlock(m);
	return(mStatus_NoError);
	}

mDNSlocal void mDNS_DeadvertiseInterface(mDNS *const m, NetworkInterfaceInfo *set, const mDNSs32 timenow)
	{
	NetworkInterfaceInfo *i;
	// If we still have address records referring to this one, update them
	NetworkInterfaceInfo *primary = FindFirstAdvertisedInterface(m);
	ResourceRecord *A1 = primary ? &primary->RR_A1 : mDNSNULL;
	ResourceRecord *A2 = primary ? &primary->RR_A2 : mDNSNULL;
	for (i=m->HostInterfaces; i; i=i->next)
		{
		if (i->RR_A1.RRSet == &set->RR_A1) i->RR_A1.RRSet = A1;
		if (i->RR_A2.RRSet == &set->RR_A2) i->RR_A2.RRSet = A2;
		}

	// Unregister these records
	mDNS_Deregister_internal(m, &set->RR_A1,  timenow, mDNS_Dereg_normal);
	mDNS_Deregister_internal(m, &set->RR_A2,  timenow, mDNS_Dereg_normal);
	mDNS_Deregister_internal(m, &set->RR_PTR, timenow, mDNS_Dereg_normal);
	}

// NOTE: mDNS_DeregisterInterface calls mDNS_Deregister_internal which can call a user callback, which may change
// the record list and/or question list.
// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
mDNSexport void mDNS_DeregisterInterface(mDNS *const m, NetworkInterfaceInfo *set)
	{
	NetworkInterfaceInfo **p = &m->HostInterfaces;
	const mDNSs32 timenow = mDNS_Lock(m);

	// Find this record in our list
	while (*p && *p != set) p=&(*p)->next;
	if (!*p) { debugf("mDNS_DeregisterInterface: NetworkInterfaceInfo not found in list"); return; }

	// Unlink this record from our list
	*p = (*p)->next;
	set->next = mDNSNULL;

	// Flush any cache entries we received on this interface
	FlushCacheRecords(m, set->ip, timenow);

	// If we were advertising on this interface, deregister now
	// When doing the mDNS_Close processing, we first call mDNS_DeadvertiseInterface for each interface
	// so by the time the platform support layer gets to call mDNS_DeregisterInterface,
	// the address and PTR records have already been deregistered for it
	if (set->Advertise && set->RR_A1.RecordType) mDNS_DeadvertiseInterface(m, set, timenow);

	mDNS_Unlock(m);
	}

mDNSlocal void ServiceCallback(mDNS *const m, ResourceRecord *const rr, mStatus result)
	{
	#pragma unused(m)
	ServiceRecordSet *sr = (ServiceRecordSet *)rr->Context;
	switch (result)
		{
		case mStatus_NoError:
			if (rr == &sr->RR_SRV)
				debugf("ServiceCallback: Service RR_SRV %##s Registered", rr->name.c);
			else
				debugf("ServiceCallback: %##s (%s) ERROR Should only get mStatus_NoError callback for RR_SRV",
					rr->name.c, DNSTypeName(rr->rrtype));
			break;

		case mStatus_NameConflict:
			debugf("ServiceCallback: %##s (%s) Name Conflict", rr->name.c, DNSTypeName(rr->rrtype));
			break;

		case mStatus_MemFree:
			if (rr == &sr->RR_PTR)
				debugf("ServiceCallback: Service RR_PTR %##s Memory Free", rr->name.c);
			else
				debugf("ServiceCallback: %##s (%s) ERROR Should only get mStatus_MemFree callback for RR_PTR",
					rr->name.c, DNSTypeName(rr->rrtype));
			break;

		default:
			debugf("ServiceCallback: %##s (%s) Unknown Result %d", rr->name.c, DNSTypeName(rr->rrtype), result);
			break;
		}

	// If we got a name conflict on either SRV or TXT, forcibly deregister this service, and record that we did that
	if (result == mStatus_NameConflict) { sr->Conflict = mDNStrue; mDNS_DeregisterService(m, sr); return; }
	
	// If this ServiceRecordSet was forcibly deregistered, and now it's memory is ready for reuse,
	// then we can now report the NameConflict to the client
	if (result == mStatus_MemFree && sr->Conflict) result = mStatus_NameConflict;

	if (sr->Callback) sr->Callback(m, sr, result);
	}

// Note:
// Name is first label of domain name (any dots in the name are actual dots, not label separators)
// Type is service type (e.g. "_printer._tcp.")
// Domain is fully qualified domain name (i.e. ending with a null label)
// We always register a TXT, even if it is empty (so that clients are not
// left waiting forever looking for a nonexistent record.)
mDNSexport mStatus mDNS_RegisterService(mDNS *const m, ServiceRecordSet *sr,
	const domainlabel *const name, const domainname *const type, const domainname *const domain,
	const domainname *const host, mDNSIPPort port, const mDNSu8 txtinfo[], mDNSu16 txtlen,
	mDNSServiceCallback Callback, void *Context)
	{
	mDNSs32 timenow;

	sr->Callback = Callback;
	sr->Context  = Context;
	sr->Conflict = mDNSfalse;
	if (host && host->c[0]) sr->Host = *host;
	else sr->Host.c[0] = 0;
	
	mDNS_SetupResourceRecord(&sr->RR_PTR, mDNSNULL, zeroIPAddr, kDNSType_PTR, 24*3600, kDNSRecordTypeShared, ServiceCallback, sr);
	mDNS_SetupResourceRecord(&sr->RR_SRV, mDNSNULL, zeroIPAddr, kDNSType_SRV, 60,      kDNSRecordTypeUnique, ServiceCallback, sr);
 	mDNS_SetupResourceRecord(&sr->RR_TXT, mDNSNULL, zeroIPAddr, kDNSType_TXT, 60,      kDNSRecordTypeUnique, ServiceCallback, sr);
	
	// If the client is registering an oversized TXT record,
	// it is the client's responsibility to alloate a ServiceRecordSet structure that is large enough for it
	if (sr->RR_TXT.rdata->MaxRDLength < txtlen)
		sr->RR_TXT.rdata->MaxRDLength = txtlen;

	if (ConstructServiceName(&sr->RR_PTR.name, mDNSNULL, type, domain) == mDNSNULL) return(mStatus_BadParamErr);
	if (ConstructServiceName(&sr->RR_SRV.name, name,     type, domain) == mDNSNULL) return(mStatus_BadParamErr);
	sr->RR_TXT.name = sr->RR_SRV.name;
	
	// 1. Set up the PTR record rdata to point to our service name
	// We set up two additionals, so when a client asks for this PTR we automatically send the SRV and the TXT too
	sr->RR_PTR.rdata->u.name = sr->RR_SRV.name;
	sr->RR_PTR.Additional1 = &sr->RR_SRV;
	sr->RR_PTR.Additional2 = &sr->RR_TXT;

	// 2. Set up the SRV record rdata.
	sr->RR_SRV.rdata->u.srv.priority = 0;
	sr->RR_SRV.rdata->u.srv.weight   = 0;
	sr->RR_SRV.rdata->u.srv.port     = port;

	// Setting HostTarget tells DNS that the target of this SRV is to be automatically kept in sync with our host name
	if (sr->Host.c[0]) sr->RR_SRV.rdata->u.srv.target = sr->Host;
	else sr->RR_SRV.HostTarget = mDNStrue;

	// 3. Set up the TXT record rdata,
	// and set DependentOn because we're depending on the SRV record to find and resolve conflicts for us
	if (txtinfo == mDNSNULL) sr->RR_TXT.rdata->RDLength = 0;
	else if (txtinfo != sr->RR_TXT.rdata->u.txt.c)
		{
		sr->RR_TXT.rdata->RDLength = txtlen;
		if (sr->RR_TXT.rdata->RDLength > sr->RR_TXT.rdata->MaxRDLength) return(mStatus_BadParamErr);
		mDNSPlatformMemCopy(txtinfo, sr->RR_TXT.rdata->u.txt.c, txtlen);
		}
	sr->RR_TXT.DependentOn = &sr->RR_SRV;

	// 4. We have no Extras yet
	sr->Extras = mDNSNULL;

	timenow = mDNS_Lock(m);
	mDNS_Register_internal(m, &sr->RR_SRV, timenow);
	mDNS_Register_internal(m, &sr->RR_TXT, timenow);
	// We register the RR_PTR last, because we want to be sure that in the event of a forced call to
	// mDNS_Close, the RR_PTR will be the last one to be forcibly deregistered, since that is what triggers
	// the mStatus_MemFree callback to ServiceCallback, which in turn passes on the mStatus_MemFree back to
	// the client callback, which is then at liberty to free the ServiceRecordSet memory at will. We need to
	// make sure we've deregistered all our records and done any other necessary cleanup before that happens.
	mDNS_Register_internal(m, &sr->RR_PTR, timenow);
	mDNS_Unlock(m);

	return(mStatus_NoError);
	}

mDNSexport mStatus mDNS_AddRecordToService(mDNS *const m, ServiceRecordSet *sr, ExtraResourceRecord *extra, RData *rdata, mDNSu32 ttl)
	{
	ExtraResourceRecord **e = &sr->Extras;
	while (*e) e = &(*e)->next;

	// If TTL is unspecified, make it 60 seconds, the same as the service's TXT and SRV default
	if (ttl == 0) ttl = 60;

	extra->next          = mDNSNULL;
 	mDNS_SetupResourceRecord(&extra->r, rdata, zeroIPAddr, extra->r.rrtype, ttl, kDNSRecordTypeUnique, ServiceCallback, sr);
	extra->r.name        = sr->RR_SRV.name;
	extra->r.DependentOn = &sr->RR_SRV;
	
	debugf("mDNS_AddRecordToService adding record to %##s", extra->r.name.c);
	
	*e = extra;
	return(mDNS_Register(m, &extra->r));
	}

mDNSexport mStatus mDNS_RemoveRecordFromService(mDNS *const m, ServiceRecordSet *sr, ExtraResourceRecord *extra)
	{
	ExtraResourceRecord **e = &sr->Extras;
	while (*e && *e != extra) e = &(*e)->next;
	if (!*e)
		{
		debugf("mDNS_RemoveRecordFromService failed to remove record from %##s", extra->r.name.c);
		return(mStatus_BadReferenceErr);
		}

	debugf("mDNS_RemoveRecordFromService removing record from %##s", extra->r.name.c);
	
	*e = (*e)->next;
	mDNS_Deregister(m, &extra->r);
	return(mStatus_NoError);
	}

mDNSexport mStatus mDNS_RenameAndReregisterService(mDNS *const m, ServiceRecordSet *const sr, const domainlabel *newname)
	{
	domainlabel name;
	domainname type, domain;
	domainname *host = mDNSNULL;
	ExtraResourceRecord *extras = sr->Extras;
	mStatus err;

	DeconstructServiceName(&sr->RR_SRV.name, &name, &type, &domain);
	if (!newname)
		{
		IncrementLabelSuffix(&name, mDNStrue);
		newname = &name;
		}
	debugf("Reregistering as %#s", newname->c);
	if (sr->RR_SRV.HostTarget == mDNSfalse && sr->Host.c[0]) host = &sr->Host;
	
	err = mDNS_RegisterService(m, sr, newname, &type, &domain,
		host, sr->RR_SRV.rdata->u.srv.port, sr->RR_TXT.rdata->u.txt.c, sr->RR_TXT.rdata->RDLength,
		sr->Callback, sr->Context);

	while (!err && extras)
		{
		ExtraResourceRecord *e = extras;
		extras = extras->next;
		err = mDNS_AddRecordToService(m, sr, e, e->r.rdata, e->r.rroriginalttl);
		}
	
	return(err);
	}

// NOTE: mDNS_DeregisterService calls mDNS_Deregister_internal which can call a user callback,
// which may change the record list and/or question list.
// Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
mDNSexport void mDNS_DeregisterService(mDNS *const m, ServiceRecordSet *sr)
	{
	const mDNSs32 timenow = mDNS_Lock(m);
	ExtraResourceRecord *e = sr->Extras;

	// We use mDNS_Dereg_repeat because, in the event of a collision, some or all of
	// these records could have already been automatically deregistered, and that's okay
	mDNS_Deregister_internal(m, &sr->RR_SRV, timenow, mDNS_Dereg_repeat);
	mDNS_Deregister_internal(m, &sr->RR_TXT, timenow, mDNS_Dereg_repeat);
	while (e)
		{
		mDNS_Deregister_internal(m, &e->r, timenow, mDNS_Dereg_repeat);
		e=e->next;
		}

	// Be sure to deregister the PTR last!
	// Deregistering this record is what triggers the mStatus_MemFree callback to ServiceCallback,
	// which in turn passes on the mStatus_MemFree (or mStatus_NameConflict) back to the client callback,
	// which is then at liberty to free the ServiceRecordSet memory at will. We need to make sure
	// we've deregistered all our records and done any other necessary cleanup before that happens.
	mDNS_Deregister_internal(m, &sr->RR_PTR, timenow, mDNS_Dereg_normal);

	mDNS_Unlock(m);
	}

mDNSexport mStatus mDNS_AdvertiseDomains(mDNS *const m, ResourceRecord *rr,
	mDNSu8 DomainType, const mDNSIPAddr InterfaceAddr, char *domname)
	{
	mDNS_SetupResourceRecord(rr, mDNSNULL, InterfaceAddr, kDNSType_PTR, 24*3600, kDNSRecordTypeShared, mDNSNULL, mDNSNULL);
	ConvertCStringToDomainName(mDNS_DomainTypeNames[DomainType], &rr->name);
	ConvertCStringToDomainName(domname, &rr->rdata->u.name);
	return(mDNS_Register(m, rr));
	}

// ***************************************************************************
#if 0
#pragma mark -
#pragma mark -
#pragma mark - Startup and Shutdown
#endif

mDNSexport mStatus mDNS_Init(mDNS *const m, mDNS_PlatformSupport *const p,
	ResourceRecord *rrcachestorage, mDNSu32 rrcachesize, mDNSCallback *Callback, void *Context)
	{
	mStatus result;
	mDNSu32 i;
	
	if (!rrcachestorage) rrcachesize = 0;
	
	m->p                  = p;
	m->mDNSPlatformStatus = mStatus_Waiting;
	m->Callback           = Callback;
	m->Context            = Context;

	m->mDNS_busy          = 0;

	m->lock_rrcache       = 0;
	m->lock_Questions     = 0;
	m->lock_Records       = 0;

	m->ActiveQuestions    = mDNSNULL;
	m->NewQuestions       = mDNSNULL;
	m->CurrentQuestion    = mDNSNULL;
	m->rrcache_size       = rrcachesize;
	m->rrcache_used       = 0;
	m->rrcache_report     = 10;
	m->rrcache_free       = rrcachestorage;
	if (rrcachesize)
		{
		for (i=0; i<rrcachesize; i++) rrcachestorage[i].next = &rrcachestorage[i+1];
		rrcachestorage[rrcachesize-1].next = mDNSNULL;
		}
	m->rrcache = mDNSNULL;

	m->hostlabel.c[0]     = 0;
	m->nicelabel.c[0]     = 0;
	m->ResourceRecords    = mDNSNULL;
	m->CurrentRecord      = mDNSNULL;
	m->HostInterfaces     = mDNSNULL;
	m->SuppressSending    = 0;
	m->SleepState         = mDNSfalse;
	m->NetChanged         = mDNSfalse;

	result = mDNSPlatformInit(m);
	
	return(result);
	}

extern void mDNSCoreInitComplete(mDNS *const m, mStatus result)
	{
	m->mDNSPlatformStatus = result;
	if (m->Callback) m->Callback(m, mStatus_NoError);
	mDNS_Lock(m);	// This lock/unlock causes a ScheduleNextTask(m) to get things started
	mDNS_Unlock(m);
	}

extern void mDNS_Close(mDNS *const m)
	{
	NetworkInterfaceInfo *i;
	const mDNSs32 timenow = mDNS_Lock(m);

#if DEBUGBREAKS
	ResourceRecord *rr;
	int rrcache_active = 0;
	for (rr = m->rrcache; rr; rr=rr->next) if (CacheRRActive(m, rr)) rrcache_active++;
	debugf("mDNS_Close: RR Cache now using %d records, %d active", m->rrcache_used, rrcache_active);
#endif

	m->ActiveQuestions = mDNSNULL;		// We won't be answering any more questions!
	
	for (i=m->HostInterfaces; i; i=i->next)
		if (i->Advertise)
			mDNS_DeadvertiseInterface(m, i, timenow);

	// Make sure there are nothing but deregistering records remaining in the list
	if (m->CurrentRecord) debugf("DiscardDeregistrations ERROR m->CurrentRecord already set");
	m->CurrentRecord = m->ResourceRecords;
	while (m->CurrentRecord)
		{
		ResourceRecord *rr = m->CurrentRecord;
		m->CurrentRecord = rr->next;
		if (rr->RecordType != kDNSRecordTypeDeregistering)
			{
			debugf("mDNS_Close: Record type %X still in ResourceRecords list %##s", rr->RecordType, rr->name.c);
			mDNS_Deregister_internal(m, rr, timenow, mDNS_Dereg_normal);
			}
		}

	if (m->ResourceRecords) debugf("mDNS_Close: Sending final packets for deregistering records");
	else debugf("mDNS_Close: No deregistering records remain");

	// If any deregistering records remain, send their deregistration announcements before we exit
	if (m->mDNSPlatformStatus != mStatus_NoError)
		DiscardDeregistrations(m, timenow);
	else
		while (m->ResourceRecords)
			SendResponses(m, timenow);
	
	mDNS_Unlock(m);
	debugf("mDNS_Close: mDNSPlatformClose");
	mDNSPlatformClose(m);
	debugf("mDNS_Close: done");
	}