/*
	File:    	BonjourBrowser.c
	Package: 	Apple CarPlay Communication Plug-in.
	Abstract: 	n/a 
	Version: 	410.12
	
	Disclaimer: IMPORTANT: This Apple software is supplied to you, by Apple Inc. ("Apple"), in your
	capacity as a current, and in good standing, Licensee in the MFi Licensing Program. Use of this
	Apple software is governed by and subject to the terms and conditions of your MFi License,
	including, but not limited to, the restrictions specified in the provision entitled ”Public 
	Software”, and is further subject to your agreement to the following additional terms, and your 
	agreement that the use, installation, modification or redistribution of this Apple software
	constitutes acceptance of these additional terms. If you do not agree with these additional terms,
	please do not use, install, modify or redistribute this Apple software.
	
	Subject to all of these terms and in consideration of your agreement to abide by them, Apple grants
	you, for as long as you are a current and in good-standing MFi Licensee, a personal, non-exclusive 
	license, under Apple's copyrights in this original Apple software (the "Apple Software"), to use, 
	reproduce, and modify the Apple Software in source form, and to use, reproduce, modify, and 
	redistribute the Apple Software, with or without modifications, in binary form. While you may not 
	redistribute the Apple Software in source form, should you redistribute the Apple Software in binary
	form, you must retain this notice and the following text and disclaimers in all such redistributions
	of the Apple Software. Neither the name, trademarks, service marks, or logos of Apple Inc. may be
	used to endorse or promote products derived from the Apple Software without specific prior written
	permission from Apple. Except as expressly stated in this notice, no other rights or licenses, 
	express or implied, are granted by Apple herein, including but not limited to any patent rights that
	may be infringed by your derivative works or by other works in which the Apple Software may be 
	incorporated.  
	
	Unless you explicitly state otherwise, if you provide any ideas, suggestions, recommendations, bug 
	fixes or enhancements to Apple in connection with this software (“Feedback”), you hereby grant to
	Apple a non-exclusive, fully paid-up, perpetual, irrevocable, worldwide license to make, use, 
	reproduce, incorporate, modify, display, perform, sell, make or have made derivative works of,
	distribute (directly or indirectly) and sublicense, such Feedback in connection with Apple products 
	and services. Providing this Feedback is voluntary, but if you do provide Feedback to Apple, you 
	acknowledge and agree that Apple may exercise the license granted above without the payment of 
	royalties or further consideration to Participant.
	
	The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR 
	IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY 
	AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR
	IN COMBINATION WITH YOUR PRODUCTS.
	
	IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES 
	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
	PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION 
	AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
	(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 
	POSSIBILITY OF SUCH DAMAGE.
	
	Copyright (C) 2010-2016 Apple Inc. All Rights Reserved. Not to be used or disclosed without permission from Apple.
*/

#include "BonjourBrowser.h"

#include <dns_sd.h>

#include "CFUtils.h"
#include "CommonServices.h"
#include "DebugServices.h"
#include "NetUtils.h"
#include "StringUtils.h"
#include "TickUtils.h"

#include CF_HEADER
#include CF_RUNTIME_HEADER
#include LIBDISPATCH_HEADER

#if( TARGET_OS_POSIX )
	#include <net/if.h>
#endif

//===========================================================================================================================
//	Types
//===========================================================================================================================

#if( TARGET_OS_DARWIN )
	#define BONJOUR_USE_GCD		1
#else
	#define BONJOUR_USE_GCD		0
#endif

typedef struct BonjourDevice *		BonjourDeviceRef;
typedef struct BonjourRData *		BonjourRDataRef;
typedef struct BonjourService *		BonjourServiceRef;

// DNSServiceContext

#if( !BONJOUR_USE_GCD )
typedef struct
{
	BonjourBrowserRef		browser;
	int						dnsFD;
	DNSServiceRef			dnsService;
	dispatch_source_t		dnsSource;
	
}	DNSServiceContext;
#endif

// BonjourBrowser

struct BonjourBrowser
{
	CFRuntimeBase						base;				// CF type info. Must be first.
	dispatch_queue_t					internalQueue;		// Dispatch queue to run internal operations.
#if( BONJOUR_USE_GCD )
	DNSServiceRef						connectionRef;		// DNS-SD service for the browser connection.
#else
	DNSServiceContext *					connectionCtx;		// Context for managing the DNSServiceRef and file descriptor.
#endif
	DNSServiceRef						browseRef;			// Browser for PTRs. May be a threshold-only browser.
	DNSServiceRef						detailedBrowseRef;	// Ignored browser hack until <radar:13613078> is fixed.
	BonjourServiceRef					services;			// List of individual service's we've discovered.
	BonjourDeviceRef					devices;			// List of logical devices we've discovered.
	Boolean								started;			// True if user has started the browser.
	uint64_t							startTicks;			// Time when Bonjour was first started.
	dispatch_source_t					retryTimer;			// Timer to retry on Bonjour failures (e.g. discoveryd crashing).
	
	// Configuration
	
	char *								serviceType;		// Bonjour service we've been started to search for (e.g. "_http._tcp.").
	char *								domain;				// DNS domain we've been started to search in. Empty means all domains.
	char *								ifname;				// Network interface we've started to search on. NULL means all interfaces.
	uint64_t							flags;				// Flags to control browsing.
	dispatch_queue_t					eventQueue;			// Dispatch queue to invoke callbacks on.
	BonjourBrowserEventHandlerFunc		eventHandler_f;		// Function to call when events occur.
	void *								eventHandler_ctx;	// User-supplied context to pass to eventHandler_f.
};

// BonjourDevice

struct BonjourDevice
{
	BonjourDeviceRef			next;
	BonjourBrowserRef			browser;
	char						deviceID[ 64 ];
	CFDictionaryRef				deviceInfo;
	BonjourServiceRef			services;
	int							autoStopTXT; // Stop TXT queries after we get the first one. -1 means "don't know yet".
};

// BonjourRData

struct BonjourRData
{
	BonjourRDataRef			next;
	size_t					rdlen;
	uint8_t					rdata[ 1 ]; // Variable length.
};

// BonjourService

struct BonjourService
{
	BonjourServiceRef		nextForBrowser;
	BonjourServiceRef		nextForDevice;
	BonjourBrowserRef		browser;
	char *					name;
	char *					type;
	char *					domain;
	uint32_t				ifindex;
	char					ifname[ IF_NAMESIZE + 1 ];
	Boolean					isP2P;
	Boolean					isWiFi;
	NetTransportType		transportType;
	DNSServiceRef			txtQueryRef;
	BonjourRDataRef			txtRDataList;
	BonjourDeviceRef		device;
};

//===========================================================================================================================
//	Prototypes
//===========================================================================================================================

static void _BonjourBrowserGetTypeID( void *inContext );
static void	_BonjourBrowser_Finalize( CFTypeRef inCF );
static void	_BonjourBrowser_Start( void *inArg );
static void	_BonjourBrowser_Stop( void *inArg );
static void	_BonjourBrowser_EnsureStarted( BonjourBrowserRef inBrowser );
static void	_BonjourBrowser_EnsureStopped( BonjourBrowserRef inBrowser );
static void	_BonjourBrowser_CopyDevices( void *inArg );
static void	_BonjourBrowser_ReconfirmDevice( void *inArg );
#if( !BONJOUR_USE_GCD )
	static void	_BonjourBrowserReadHandler( void *inArg );
	static void	_BonjourBrowserCancelHandler( void *inArg );
#endif
static void DNSSD_API
	_BonjourBrowser_BrowseHandler(
		DNSServiceRef			inRef,
		DNSServiceFlags			inFlags,
		uint32_t				inIfIndex,
		DNSServiceErrorType		inError,
		const char *			inName,  
		const char *			inType,    
		const char *			inDomain,  
		void *					inContext );
static void DNSSD_API
	_BonjourBrowser_IgnoredBrowseHandler(
		DNSServiceRef			inRef,
		DNSServiceFlags			inFlags,
		uint32_t				inIfIndex,
		DNSServiceErrorType		inError,
		const char *			inName,  
		const char *			inType,    
		const char *			inDomain,  
		void *					inContext );
static OSStatus
	_BonjourBrowser_PostEvent( 
		BonjourBrowserRef		inBrowser, 
		BonjourBrowserEventType	inEventType, 
		BonjourDeviceRef		inDevice, 
		BonjourServiceRef		inRemovedService );
static void	_BonjourBrowser_PostEventOnEventQueue( void *inArg );
static void	_BonjourBrowser_RemoveService( BonjourBrowserRef inBrowser, BonjourServiceRef inService, Boolean inUpdateTXT );
static void	_BonjourBrowser_HandleError( BonjourBrowserRef me, OSStatus inError );
static void	_BonjourBrowser_RetryTimerFired( void *inArg );
static void	_BonjourBrowser_RetryTimerCanceled( void *inArg );

// BonjourDevice

static CFDictionaryRef		_BonjourDevice_CreateDictionary( BonjourDeviceRef inDevice, OSStatus *outErr );
static void					_BonjourDevice_Free( BonjourDeviceRef inDevice );
static BonjourServiceRef	_BonjourDevice_GetBestService( BonjourDeviceRef inDevice );
static OSStatus				_BonjourDevice_UpdateTXTQueries( BonjourDeviceRef inDevice );

// BonjourService

static OSStatus
	_BonjourService_Create( 
		BonjourBrowserRef	inManager, 
		const char *		inName, 
		const char *		inType, 
		const char *		inDomain, 
		uint32_t			inIfIndex, 
		BonjourServiceRef *	outService );
static void		_BonjourService_Free( BonjourServiceRef inService );
static OSStatus	_BonjourService_StartTXTQuery( BonjourServiceRef inService );
static void DNSSD_API
	_BonjourService_TXTHandler(
		DNSServiceRef		inRef,
		DNSServiceFlags		inFlags,
		uint32_t			inIfIndex,
		DNSServiceErrorType	inError,
		const char *		inFullName,    
		uint16_t			inRRType,
		uint16_t			inRRClass,
		uint16_t			inRDataSize,
		const void *		inRData,
		uint32_t			inTTL,
		void *				inContext );
static OSStatus				_BonjourService_AddRData( BonjourServiceRef inService, const void *inRData, size_t inRDLen );
static void					_BonjourService_RemoveRData( BonjourServiceRef inService, const void *inRData, size_t inRDLen );
static CFDictionaryRef		_BonjourService_CreateDictionary( BonjourServiceRef inService, OSStatus *outErr );
static OSStatus				_BonjourService_GetDeviceID( BonjourServiceRef inService, char *inKeyBuf, size_t inKeyMaxLen );
static const char *			_BonjourService_GetName( BonjourServiceRef inService, size_t *outNameLen );
static Boolean				_BonjourService_IsAutoStopTXT( BonjourServiceRef inService );

//===========================================================================================================================
//	Globals
//===========================================================================================================================

static const CFRuntimeClass		kBonjourBrowserClass = 
{
	0,							// version
	"BonjourBrowser",			// className
	NULL,						// init
	NULL,						// copy
	_BonjourBrowser_Finalize,	// finalize
	NULL,						// equal -- NULL means pointer equality.
	NULL,						// hash  -- NULL means pointer hash.
	NULL,						// copyFormattingDesc
	NULL,						// copyDebugDesc
	NULL,						// reclaim
	NULL						// refcount
};

static dispatch_once_t		gBonjourBrowserInitOnce	= 0;
static CFTypeID				gBonjourBrowserTypeID	= _kCFRuntimeNotATypeID;

ulog_define( BonjourBrowser, kLogLevelNotice, kLogFlags_Default, "BonjourBrowser", NULL );
#define bb_ulog( LEVEL, ... )		ulog( &log_category_from_name( BonjourBrowser ), (LEVEL), __VA_ARGS__ )

ulog_define( BonjourIssues, kLogLevelNotice, kLogFlags_Default, "BonjourIssues", NULL );
#define bb_issues_ucat()					&log_category_from_name( BonjourIssues )
#define bb_issues_ulog( LEVEL, ... )		ulog( &log_category_from_name( BonjourIssues ), (LEVEL), __VA_ARGS__ )

#if 0
#pragma mark -
#pragma mark == BonjourBrowser ==
#endif

//===========================================================================================================================
//	BonjourBrowserGetTypeID
//===========================================================================================================================

CFTypeID	BonjourBrowserGetTypeID( void )
{
	dispatch_once_f( &gBonjourBrowserInitOnce, NULL, _BonjourBrowserGetTypeID );
	return( gBonjourBrowserTypeID );
}

static void _BonjourBrowserGetTypeID( void *inContext )
{
	(void) inContext;
	
	gBonjourBrowserTypeID = _CFRuntimeRegisterClass( &kBonjourBrowserClass );
	check( gBonjourBrowserTypeID != _kCFRuntimeNotATypeID );
}

//===========================================================================================================================
//	BonjourBrowser_Create
//===========================================================================================================================

OSStatus	BonjourBrowser_Create( BonjourBrowserRef *outBrowser, const char *inLabel )
{
	OSStatus				err;
	BonjourBrowserRef		obj;
	size_t					extraLen;
	
	extraLen = sizeof( *obj ) - sizeof( obj->base );
	obj = (BonjourBrowserRef) _CFRuntimeCreateInstance( NULL, BonjourBrowserGetTypeID(), (CFIndex) extraLen, NULL );
	require_action( obj, exit, err = kNoMemoryErr );
	memset( ( (uint8_t *) obj ) + sizeof( obj->base ), 0, extraLen );
	
	obj->internalQueue = dispatch_queue_create( inLabel ? inLabel : "BonjourBrowser", NULL );
	require_action( obj->internalQueue, exit, err = kUnknownErr );
	
	obj->eventQueue = dispatch_get_main_queue();
	arc_safe_dispatch_retain( obj->eventQueue );
	
	*outBrowser = obj;
	obj = NULL;
	err = kNoErr;
	
exit:
	CFReleaseNullSafe( obj );
	return( err );
}

static void	_BonjourBrowser_Finalize( CFTypeRef inCF )
{
	BonjourBrowserRef const		browser = (BonjourBrowserRef) inCF;
	
	dispatch_forget( &browser->internalQueue );
	dispatch_forget( &browser->eventQueue );
#if( BONJOUR_USE_GCD )
	check( !browser->connectionRef );
#else
	check( !browser->connectionCtx );
#endif
	check( !browser->browseRef );
	check( !browser->detailedBrowseRef );
	check( !browser->services );
	check( !browser->devices );
	check( !browser->serviceType );
	check( !browser->domain );
	check( !browser->ifname );
	bb_ulog( kLogLevelVerbose, "Finalized browser\n" );
}

//===========================================================================================================================
//	BonjourBrowser_Delete
//===========================================================================================================================

// Just here for older CoreUtils clients that link to the old name. When all clients are gone, this can be removed.
void	BonjourBrowser_Delete( BonjourBrowserRef inBrowser );
void	BonjourBrowser_Delete( BonjourBrowserRef inBrowser )
{
	CFRelease( inBrowser );
}

//===========================================================================================================================
//	BonjourBrowser_SetDispatchQueue
//===========================================================================================================================

void	BonjourBrowser_SetDispatchQueue( BonjourBrowserRef inBrowser, dispatch_queue_t inQueue )
{
	ReplaceDispatchQueue( &inBrowser->eventQueue, inQueue );
}

//===========================================================================================================================
//	BonjourBrowser_SetEventHandler
//===========================================================================================================================

void	BonjourBrowser_SetEventHandler( BonjourBrowserRef inBrowser, BonjourBrowserEventHandlerFunc inFunc, void *inContext )
{
	inBrowser->eventHandler_f	= inFunc;
	inBrowser->eventHandler_ctx	= inContext;
}

//===========================================================================================================================
//	BonjourBrowser_Start
//===========================================================================================================================

typedef struct
{
	BonjourBrowserRef		browser;
	char *					serviceType;
	char *					domain;
	char *					ifname;
	uint64_t				flags;
	
}	BonjourBrowser_StartParams;

OSStatus
	BonjourBrowser_Start( 
		BonjourBrowserRef	inBrowser, 
		const char *		inServiceType, 
		const char *		inDomain, 
		const char *		inIfName, 
		uint64_t			inFlags )
{
	OSStatus							err;
	BonjourBrowser_StartParams *		params;
	
	params = (BonjourBrowser_StartParams *) calloc( 1, sizeof( *params ) );
	require_action( params, exit, err = kNoMemoryErr );
	
	params->browser = inBrowser;
	params->flags = inFlags;
	
	params->serviceType = strdup( inServiceType );
	require_action( params->serviceType, exit, err = kNoMemoryErr );
	
	params->domain = strdup( inDomain ? inDomain : "" );
	require_action( params->domain, exit, err = kNoMemoryErr );
	
	if( inIfName && ( *inIfName != '\0' ) )
	{
		params->ifname = strdup( inIfName );
		require_action( params->ifname, exit, err = kNoMemoryErr );
	}
	
	CFRetain( inBrowser );
	dispatch_async_f( inBrowser->internalQueue, params, _BonjourBrowser_Start );
	params = NULL;
	err = kNoErr;
	
exit:
	if( params )
	{
		FreeNullSafe( params->serviceType );
		FreeNullSafe( params->domain );
		FreeNullSafe( params->ifname );
		free( params );
	}
	return( err );
}

//===========================================================================================================================
//	_BonjourBrowser_Start
//===========================================================================================================================

static void	_BonjourBrowser_Start( void *inArg )
{
	BonjourBrowser_StartParams * const		params	= (BonjourBrowser_StartParams *) inArg;
	BonjourBrowserRef const					browser	= params->browser;
	
	FreeNullSafe( browser->serviceType );
	browser->serviceType = params->serviceType;
	
	FreeNullSafe( browser->domain );
	browser->domain = params->domain;
	
	FreeNullSafe( browser->ifname );
	browser->ifname = params->ifname;
	
	browser->flags = params->flags;
	
	if( !browser->started )
	{
		CFRetain( browser );
		browser->started = true;
		browser->startTicks	= UpTicks();
	}
	_BonjourBrowser_EnsureStarted( browser );
	
	free( params );
	CFRelease( browser );
}

//===========================================================================================================================
//	BonjourBrowser_Stop
//===========================================================================================================================

void	BonjourBrowser_Stop( BonjourBrowserRef inBrowser )
{
	CFRetain( inBrowser );
	dispatch_async_f( inBrowser->internalQueue, inBrowser, _BonjourBrowser_Stop );
}

//===========================================================================================================================
//	_BonjourBrowser_Stop
//===========================================================================================================================

static void	_BonjourBrowser_Stop( void *inArg )
{
	BonjourBrowserRef const		browser = (BonjourBrowserRef) inArg;
	Boolean						wasStarted;
	
	wasStarted = browser->started;
	browser->started = false;
	if( wasStarted ) bb_ulog( kLogLevelTrace, "Stopping browse for %s\n", browser->serviceType );
	
	dispatch_source_forget( &browser->retryTimer );
	_BonjourBrowser_EnsureStopped( browser );
	if( wasStarted )
	{
		_BonjourBrowser_PostEvent( browser, kBonjourBrowserEventType_Stop, NULL, NULL );
		bb_ulog( kLogLevelTrace, "Stopped browse for %s\n", browser->serviceType );
		CFRelease( browser ); // Undo retain from _BonjourBrowser_Start on first start.
	}
	ForgetMem( &browser->serviceType );
	ForgetMem( &browser->domain );
	ForgetMem( &browser->ifname );
	CFRelease( browser ); // Undo retain from BonjourBrowser_Stop.
}

//===========================================================================================================================
//	_BonjourBrowser_EnsureStarted
//===========================================================================================================================

static void	_BonjourBrowser_EnsureStarted( BonjourBrowserRef inBrowser )
{
	uint64_t				flags64		= inBrowser->flags;
	uint32_t				flags32		= (uint32_t)( flags64 & 0xFFFFFFFFU );
	Boolean const			traffic		= ( flags64 & kBonjourBrowserFlag_Traffic )    ? true : false;
	Boolean const			background	= ( flags64 & kBonjourBrowserFlag_Background ) ? true : false;
#if( !BONJOUR_USE_GCD )
	DNSServiceContext *		ctx			= NULL;
#endif
	OSStatus				err;
	DNSServiceRef *			connectionRef;
	DNSServiceRef			service;
	uint32_t				ifindex;
	
	// Set up a shared connection for DNS-SD operations.
	
#if( BONJOUR_USE_GCD )
	connectionRef = &inBrowser->connectionRef;
#else
	ctx = inBrowser->connectionCtx; 
	if( !ctx )
	{
		ctx = (DNSServiceContext *) calloc( 1, sizeof( *ctx ) );
		require_action( ctx, exit, err = kNoMemoryErr );
		ctx->browser = inBrowser;
	}
	connectionRef = &ctx->dnsService;
#endif
	if( !*connectionRef )
	{
		bb_ulog( kLogLevelTrace, "Creating shared connection to browse for %s\n", inBrowser->serviceType );
		err = DNSServiceCreateConnection( connectionRef );
		require_noerr_quiet( err, exit );
		
		#if( BONJOUR_USE_GCD )
			DNSServiceSetDispatchQueue( *connectionRef, inBrowser->internalQueue );
		#else
			ctx->dnsFD = DNSServiceRefSockFD( *connectionRef );
			require_action( IsValidSocket( ctx->dnsFD ), exit, err = kUnknownErr );
			
			ctx->dnsSource = dispatch_source_create( DISPATCH_SOURCE_TYPE_READ, ctx->dnsFD, 0, inBrowser->internalQueue );
			require_action( ctx->dnsSource, exit, err = kUnknownErr );
			CFRetain( ctx->browser );
			inBrowser->connectionCtx = ctx;
			dispatch_set_context( ctx->dnsSource, ctx );
			dispatch_source_set_event_handler_f(  ctx->dnsSource, _BonjourBrowserReadHandler );
			dispatch_source_set_cancel_handler_f( ctx->dnsSource, _BonjourBrowserCancelHandler );
			dispatch_resume( ctx->dnsSource );
		#endif
	}
	
	// Start browsing.
	
	if( !inBrowser->browseRef )
	{
		uint32_t		flags32Alt;
		
		flags32Alt = flags32;
		if( traffic )
		{
			flags32Alt |= kDNSServiceFlagsThresholdOne_compat;
		}
		ifindex = inBrowser->ifname ? if_nametoindex( inBrowser->ifname ) : kDNSServiceInterfaceIndexAny;
		service = *connectionRef;
		bb_ulog( kLogLevelTrace, "Starting browse for %s on if %s, flags 0x%X\n", 
			inBrowser->serviceType, inBrowser->ifname ? inBrowser->ifname : "<any>", flags32Alt );
		err = DNSServiceBrowse( &service, kDNSServiceFlagsShareConnection | flags32Alt, ifindex, 
			inBrowser->serviceType, inBrowser->domain, _BonjourBrowser_BrowseHandler, inBrowser );
		require_noerr( err, exit );
		inBrowser->browseRef = service;
	}
	
	// If we're using the traffic reduction scheme and not in background mode, start a separate full browse.
	// This hack causes two browses to be active. The one above is a present-only browse that is always active 
	// as long as we're started. The one below is a detailed browse where we ignore the results, but having
	// this browse is enough to cause the present-only browse to get all the results. This is needed to avoid
	// needing to fully tear down the browse when switching modes. Once <radar:13613078> is implemented in 
	// Bonjour, we can remove this second browse and just change the existing one as needed.
	
	if( background )
	{
		if( inBrowser->detailedBrowseRef )
		{
			bb_ulog( kLogLevelInfo, "Stopping detail browse for %s, if %s, flags 0x%X\n", 
				inBrowser->serviceType, inBrowser->ifname ? inBrowser->ifname : "<any>", flags32 );
			DNSServiceForget( &inBrowser->detailedBrowseRef );
			
		}
	}
	else if( traffic && !inBrowser->detailedBrowseRef )
	{
		flags32 &= ~kDNSServiceFlagsThresholdOne_compat;
		ifindex = inBrowser->ifname ? if_nametoindex( inBrowser->ifname ) : kDNSServiceInterfaceIndexAny;
		service = *connectionRef;
		bb_ulog( kLogLevelInfo, "Starting detail browse for %s, if %s, flags 0x%X\n", 
			inBrowser->serviceType, inBrowser->ifname ? inBrowser->ifname : "<any>", flags32 );
		err = DNSServiceBrowse( &service, kDNSServiceFlagsShareConnection | flags32, ifindex, 
			inBrowser->serviceType, inBrowser->domain, _BonjourBrowser_IgnoredBrowseHandler, inBrowser );
		check_noerr( err );
		if( !err ) inBrowser->detailedBrowseRef = service;
	}
	
#if( !BONJOUR_USE_GCD )
	ctx = NULL;
#endif
	err = kNoErr;
	
exit:
	if( err )
	{
		bb_ulog( kLogLevelWarning, "### Start browse for %s on if %s, flags 0x%llX failed: %#m\n", 
			inBrowser->serviceType, inBrowser->ifname ? inBrowser->ifname : "<any>", flags64, err );
		_BonjourBrowser_EnsureStopped( inBrowser );
	}
	_BonjourBrowser_HandleError( inBrowser, err );
#if( !BONJOUR_USE_GCD )
	if( ctx )
	{
		DNSServiceForget( &ctx->dnsService );
		free( ctx );
	}
#endif
}

//===========================================================================================================================
//	_BonjourBrowser_EnsureStopped
//===========================================================================================================================

static void	_BonjourBrowser_EnsureStopped( BonjourBrowserRef inBrowser )
{
	BonjourDeviceRef		device;
	BonjourServiceRef		service;
	
	// When GCD is supported directly by DNS-SD, just deallocate the service ref and we're done (it will cancel GCD).
	// Note: deallocating the parent connection DNSServiceRef also releases all child DNSServiceRef's so invalidate them.
	
#if( BONJOUR_USE_GCD )
	DNSServiceForget( &inBrowser->connectionRef );
#else
	if( inBrowser->connectionCtx )
	{
		dispatch_source_forget( &inBrowser->connectionCtx->dnsSource );
		inBrowser->connectionCtx = NULL;
	}
#endif
	inBrowser->browseRef = NULL;
	inBrowser->detailedBrowseRef = NULL;
	while( ( device = inBrowser->devices ) != NULL )
	{
		inBrowser->devices = device->next;
		_BonjourDevice_Free( device );
	}
	while( ( service = inBrowser->services ) != NULL )
	{
		inBrowser->services = service->nextForBrowser;
		service->txtQueryRef = NULL; // Invalidated by DNSServiceRefDeallocate of parent connection DNSServiceRef.
		_BonjourService_Free( service );
	}
}

//===========================================================================================================================
//	BonjourBrowser_CopyDevices
//===========================================================================================================================

typedef struct
{
	BonjourBrowserRef		browser;
	CFMutableArrayRef		array;
	OSStatus				error;
	
}	BonjourBrowser_CopyDevicesParams;

CFArrayRef	BonjourBrowser_CopyDevices( BonjourBrowserRef inBrowser, OSStatus *outErr )
{
	BonjourBrowser_CopyDevicesParams		params = { inBrowser, NULL, kInProgressErr };
	
	dispatch_sync_f( inBrowser->internalQueue, &params, _BonjourBrowser_CopyDevices );
	if( outErr ) *outErr = params.error;
	return( params.array );
}

static void	_BonjourBrowser_CopyDevices( void *inArg )
{
	BonjourBrowser_CopyDevicesParams * const		params = (BonjourBrowser_CopyDevicesParams *) inArg;
	OSStatus										err;
	CFMutableArrayRef								array;
	BonjourDeviceRef								device;
	CFDictionaryRef									deviceInfo;
	
	array = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
	require_action( array, exit, err = kNoMemoryErr );
	
	for( device = params->browser->devices; device; device = device->next )
	{
		deviceInfo = _BonjourDevice_CreateDictionary( device, &err );
		if( deviceInfo )
		{
			CFArrayAppendValue( array, deviceInfo );
			CFRelease( deviceInfo );
		}
	}
	
	params->array = array;
	array = NULL;
	err = kNoErr;
	
exit:
	CFReleaseNullSafe( array );
	params->error = err;
}

//===========================================================================================================================
//	BonjourBrowser_ReconfirmDevice
//===========================================================================================================================

typedef struct
{
	BonjourBrowserRef		browser;
	CFDictionaryRef			deviceInfo;
	
}	BonjourBrowser_ReconfirmDeviceParams;

void	BonjourBrowser_ReconfirmDevice( BonjourBrowserRef inBrowser, CFDictionaryRef inDeviceInfo )
{
	BonjourBrowser_ReconfirmDeviceParams *		params;
	
	params = (BonjourBrowser_ReconfirmDeviceParams *) malloc( sizeof( *params ) );
	require( params, exit );
	params->browser = inBrowser;
	params->deviceInfo = inDeviceInfo;
	CFRetain( inDeviceInfo );
	
	CFRetain( inBrowser );
	dispatch_async_f( inBrowser->internalQueue, params, _BonjourBrowser_ReconfirmDevice );
	
exit:
	return;
}

static void	_BonjourBrowser_ReconfirmDevice( void *inArg )
{
	BonjourBrowser_ReconfirmDeviceParams * const		params  = (BonjourBrowser_ReconfirmDeviceParams *) inArg;
	BonjourBrowserRef const								browser = params->browser;
	OSStatus											err;
	char												deviceID[ 64 ];
	BonjourDeviceRef									device;
	BonjourServiceRef									service;
	uint8_t												rdata[ 256 ];
	uint8_t *											ptr;
	char												name[ kDNSServiceMaxDomainName ];
	
	// Find the device.
	
	*deviceID = '\0';
	CFDictionaryGetCString( params->deviceInfo, CFSTR( kBonjourDeviceKey_DeviceID ), deviceID, sizeof( deviceID ), NULL );
	require( *deviceID != '\0', exit );
	
	for( device = browser->devices; device; device = device->next )
	{
		if( stricmp( device->deviceID, deviceID ) == 0 )
		{
			break;
		}
	}
	require_quiet( device, exit );
	
	// Reconfirm each PTR record of the device.
	
	for( service = device->services; service; service = service->nextForDevice )
	{
		err = DNSServiceConstructFullName( name, service->name, service->type, service->domain );
		check_noerr( err );
		if( err ) continue;
		
		ptr = MakeDomainNameFromDNSNameString( rdata, name );
		check( ptr );
		if( !ptr ) continue;
		
		snprintf( name, sizeof( name ), "%s%s", service->type, service->domain );
		bb_ulog( kLogLevelNotice, "Reconfirming PTR for %s.%s%s on %s\n", service->name, service->type, service->domain, 
			service->ifname );
		err = DNSServiceReconfirmRecord( 0, service->ifindex, name, kDNSServiceType_PTR, kDNSServiceClass_IN, 
			(uint16_t)( ptr - rdata ), rdata );
		check_noerr( err );
	}
	
exit:
	CFRelease( params->deviceInfo );
	free( params );
	CFRelease( browser );
}

#if 0
#pragma mark -
#endif

#if( !BONJOUR_USE_GCD )
//===========================================================================================================================
//	_BonjourBrowserReadHandler
//===========================================================================================================================

static void	_BonjourBrowserReadHandler( void *inArg )
{
	DNSServiceContext * const		ctx = (DNSServiceContext *) inArg;
	OSStatus						err;
	
	err = DNSServiceProcessResult( ctx->dnsService );
	if( err )
	{
		bb_ulog( kLogLevelNotice, "### Bonjour server crashed for %s: %#m\n", ctx->browser->serviceType, err );
		ctx->browser->connectionCtx = NULL;
		dispatch_source_forget( &ctx->dnsSource );
		_BonjourBrowser_HandleError( ctx->browser, err );
	}
}

//===========================================================================================================================
//	_BonjourBrowserCancelHandler
//===========================================================================================================================

static void	_BonjourBrowserCancelHandler( void *inArg )
{
	DNSServiceContext * const		ctx = (DNSServiceContext *) inArg;
	
	DNSServiceForget( &ctx->dnsService );
	CFRelease( ctx->browser );
	free( ctx );
}
#endif

//===========================================================================================================================
//	_BonjourBrowser_BrowseHandler
//===========================================================================================================================

static void DNSSD_API
	_BonjourBrowser_BrowseHandler(
		DNSServiceRef			inRef,
		DNSServiceFlags			inFlags,
		uint32_t				inIfIndex,
		DNSServiceErrorType		inError,
		const char *			inName,  
		const char *			inType,    
		const char *			inDomain,  
		void *					inContext )
{
	BonjourBrowserRef const		browser = (BonjourBrowserRef) inContext;
	BonjourServiceRef *			nextService;
	BonjourServiceRef			service;
	OSStatus					err;
	Boolean						add;
	
	(void) inRef;
	
	if( inError == kDNSServiceErr_ServiceNotRunning )
	{
		bb_ulog( kLogLevelWarning, "### Browser for %s server crashed\n", browser->serviceType );
		_BonjourBrowser_HandleError( browser, inError );
		goto exit;
	}
	else if( inError )
	{
		bb_ulog( kLogLevelWarning, "### Browser for %s browse error: %#m\n", browser->serviceType, inError );
		goto exit;
	}
	require_action( browser->started, exit, bb_ulog( kLogLevelWarning, "### Browse response after stop\n" ) );
	
	add = ( inFlags & kDNSServiceFlagsAdd ) ? true : false;
	bb_ulog( kLogLevelVerbose, "Bonjour PTR %s %s.%s%s on %u\n", add ? "Add" : "Rmv", inName, inType, inDomain, inIfIndex );
	
	for( nextService = &browser->services; ( service = *nextService ) != NULL; nextService = &service->nextForBrowser )
	{
		if( service->ifindex != inIfIndex )				continue; // Interface must match (zero/all is not allowed here).
		if( stricmp( service->name,   inName   ) != 0 )	continue; // Name must match.
		if( stricmp( service->type,   inType   ) != 0 )	continue; // Type must match.
		if( stricmp( service->domain, inDomain ) != 0 )	continue; // Domain must match.
		break;
	}
	if( add )
	{
		require_quiet( service == NULL, exit );
		err = _BonjourService_Create( browser, inName, inType, inDomain, inIfIndex, &service );
		require_noerr( err, exit );
		
		err = _BonjourService_StartTXTQuery( service );
		if( err ) _BonjourService_Free( service );
		require_noerr( err, exit );
		
		*nextService = service;
	}
	else
	{
		require_quiet( service, exit );
		*nextService = service->nextForBrowser;
		_BonjourBrowser_RemoveService( browser, service, true );
		_BonjourService_Free( service );
	}
	
exit:
	return;
}

//===========================================================================================================================
//	_BonjourBrowser_IgnoredBrowseHandler
//===========================================================================================================================

static void DNSSD_API
	_BonjourBrowser_IgnoredBrowseHandler(
		DNSServiceRef			inRef,
		DNSServiceFlags			inFlags,
		uint32_t				inIfIndex,
		DNSServiceErrorType		inError,
		const char *			inName,  
		const char *			inType,    
		const char *			inDomain,  
		void *					inContext )
{
	BonjourBrowserRef const		browser = (BonjourBrowserRef) inContext;
	
	require_quiet( browser->flags & kBonjourBrowserFlag_Traffic, exit );
	
	_BonjourBrowser_BrowseHandler( inRef, inFlags, inIfIndex, inError, inName, inType, inDomain, inContext );
	
exit:
	return;
}

//===========================================================================================================================
//	_BonjourBrowser_PostEvent
//===========================================================================================================================

typedef struct
{
	BonjourBrowserEventHandlerFunc		eventHandler_f;
	void *								eventHandler_ctx;
	BonjourBrowserEventType				eventType;
	CFDictionaryRef						deviceInfo;
	
}	BonjourBrowser_PostEventParams;

static OSStatus
	_BonjourBrowser_PostEvent( 
		BonjourBrowserRef		inBrowser, 
		BonjourBrowserEventType	inEventType,
		BonjourDeviceRef		inDevice, 
		BonjourServiceRef		inRemovedService )
{
	OSStatus					err;
	CFDictionaryRef				deviceInfo;
	CFMutableDictionaryRef		mutableDeviceInfo = NULL;
	
	require_action_quiet( inBrowser->eventHandler_f, exit, err = kNoErr );
	
	if( inDevice )
	{
		deviceInfo = _BonjourDevice_CreateDictionary( inDevice, &err );
		require_noerr( err, exit );
		
		CFReleaseNullSafe( inDevice->deviceInfo );
		inDevice->deviceInfo = deviceInfo;
		
		if( inRemovedService )
		{
			CFDictionaryRef		serviceDict;
			const void *		objects[ 1 ];
			CFArrayRef			removedServices;
			
			mutableDeviceInfo = CFDictionaryCreateMutableCopy( NULL, 0, deviceInfo );
			require_action( mutableDeviceInfo, exit, err = kNoMemoryErr );
			
			serviceDict = _BonjourService_CreateDictionary( inRemovedService, &err );
			require_noerr( err, exit );
			
			objects[ 0 ] = serviceDict;
			removedServices = CFArrayCreate( NULL, objects, 1, &kCFTypeArrayCallBacks );
			CFRelease( serviceDict );
			require_action( removedServices, exit, err = kNoMemoryErr );
			
			CFDictionarySetValue( mutableDeviceInfo, CFSTR( kBonjourDeviceKey_RemovedServices ), removedServices );
			CFRelease( removedServices );
			deviceInfo = mutableDeviceInfo;
		}
	}
	else
	{
		deviceInfo = NULL;
	}
	
	BonjourBrowser_PostEventParams *		params;
	
	params = (BonjourBrowser_PostEventParams *) calloc( 1, sizeof( *params ) );
	require_action( params, exit, err = kNoMemoryErr );
	params->eventType			= inEventType;
	params->eventHandler_f		= inBrowser->eventHandler_f;
	params->eventHandler_ctx	= inBrowser->eventHandler_ctx;
	params->deviceInfo			= deviceInfo;
	CFRetainNullSafe( deviceInfo );
	
	dispatch_async_f( inBrowser->eventQueue, params, _BonjourBrowser_PostEventOnEventQueue );
	
	err = kNoErr;
	
exit:
	CFReleaseNullSafe( mutableDeviceInfo );
	return( err );
}

//===========================================================================================================================
//	_BonjourBrowser_PostEventOnEventQueue
//===========================================================================================================================

static void	_BonjourBrowser_PostEventOnEventQueue( void *inArg )
{
	BonjourBrowser_PostEventParams * const		params = (BonjourBrowser_PostEventParams *) inArg;
	
	params->eventHandler_f( params->eventType, params->deviceInfo, params->eventHandler_ctx );
	CFReleaseNullSafe( params->deviceInfo );
	free( params );
}

//===========================================================================================================================
//	_BonjourBrowser_RemoveService
//===========================================================================================================================

static void	_BonjourBrowser_RemoveService( BonjourBrowserRef inBrowser, BonjourServiceRef inService, Boolean inUpdateTXT )
{
	BonjourDeviceRef		device;
	BonjourDeviceRef		deviceCurr;
	BonjourDeviceRef *		deviceNext;
	BonjourServiceRef *		serviceNext;
	BonjourServiceRef		service;
	
	device = inService->device;
	if( !device ) goto exit;
	
	// Remove the service from the device.
	
	for( serviceNext = &device->services; ( service = *serviceNext ) != NULL; serviceNext = &service->nextForDevice )
	{
		if( service == inService )
		{
			*serviceNext = service->nextForDevice;
			break;
		}
	}
	check( service );
	
	// If there are no other services associated with this device, remove the device too.
	
	if( device->services == NULL )
	{
		for( deviceNext = &inBrowser->devices; ( deviceCurr = *deviceNext ) != NULL; deviceNext = &deviceCurr->next )
		{
			if( deviceCurr == device )
			{
				*deviceNext = device->next;
				break;
			}
		}
		check( deviceCurr );
		
		_BonjourBrowser_PostEvent( inBrowser, kBonjourBrowserEventType_RemoveDevice, device, inService );
		_BonjourDevice_Free( device );
	}
	else
	{
		_BonjourBrowser_PostEvent( inBrowser, kBonjourBrowserEventType_AddOrUpdateDevice, device, inService );
		
		// Re-evaluate TXT queries for this device since we may need to restart an inactive one if the service
		// that was just removed was the only active TXT query (e.g. local service goes away, leaving only BTMM).
		
		if( inUpdateTXT ) _BonjourDevice_UpdateTXTQueries( device );
	}
	
exit:
	return;
}

//===========================================================================================================================
//	_BonjourBrowser_HandleError
//===========================================================================================================================

static void	_BonjourBrowser_HandleError( BonjourBrowserRef me, OSStatus inError )
{
	BonjourServiceRef		service;
	uint64_t				ms;
	
	if( !inError )
	{
		dispatch_source_forget( &me->retryTimer );
		goto exit;
	}
	
	// Remove all the services and send remove events for each one.
	
	if( !me->retryTimer ) _BonjourBrowser_PostEvent( me, kBonjourBrowserEventType_Restarted, NULL, NULL );
	while( ( service = me->services ) != NULL )
	{
		me->services = service->nextForBrowser;
		_BonjourBrowser_RemoveService( me, service, false );
		_BonjourService_Free( service );
	}
	_BonjourBrowser_EnsureStopped( me );
	
	// Start a timer to retry later.
	
	require_quiet( !me->retryTimer, exit );
	
	ms = UpTicksToMilliseconds( UpTicks() - me->startTicks );
	ms = ( ms < 10513 ) ? ( 10513 - ms ) : 100; // Use 10513 to avoid being syntonic with 10 second launchd re-launching.
	bb_ulog( kLogLevelNotice, "### Browse for %s failed, retrying in %llu ms: %#m\n", me->serviceType, ms, inError );
	
	me->retryTimer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->internalQueue );
	require_action( me->retryTimer, exit, bb_ulog( kLogLevelWarning, "### Bonjour retry timer failed\n" ) );
	CFRetain( me );
	dispatch_set_context( me->retryTimer, me );
	dispatch_source_set_event_handler_f( me->retryTimer, _BonjourBrowser_RetryTimerFired );
	dispatch_source_set_cancel_handler_f( me->retryTimer, _BonjourBrowser_RetryTimerCanceled );
	dispatch_source_set_timer( me->retryTimer, dispatch_time_milliseconds( ms ), DISPATCH_TIME_FOREVER, kNanosecondsPerSecond );
	dispatch_resume( me->retryTimer );
	
exit:
	return;
}


//===========================================================================================================================
//	_BonjourBrowser_RetryTimerFired
//===========================================================================================================================

static void	_BonjourBrowser_RetryTimerFired( void *inArg )
{
	BonjourBrowserRef const		me = (BonjourBrowserRef) inArg;
	
	bb_ulog( kLogLevelNotice, "Retrying Bonjour start for %s after failure\n", me->serviceType );
	dispatch_source_forget( &me->retryTimer );
	me->startTicks = UpTicks();
	_BonjourBrowser_EnsureStarted( me );
}

//===========================================================================================================================
//	_BonjourBrowser_RetryTimerCanceled
//===========================================================================================================================

static void	_BonjourBrowser_RetryTimerCanceled( void *inArg )
{
	BonjourBrowserRef const		me = (BonjourBrowserRef) inArg;
	
	CFRelease( me );
}

#if 0
#pragma mark -
#endif

//===========================================================================================================================
//	BonjourDevice_Reconfirm
//===========================================================================================================================

void	BonjourDevice_Reconfirm( CFDictionaryRef inDeviceInfo )
{
	OSStatus			err;
	CFArrayRef			services;
	CFIndex				i, n;
	CFDictionaryRef		serviceInfo;
	char				rawName[ 128 ];
	char				type[ 128 ];
	char				domain[ kDNSServiceMaxDomainName ];
	char				name[ kDNSServiceMaxDomainName ];
	uint32_t			ifindex;
	uint8_t				rdata[ 256 ];
	uint8_t *			ptr;
	
	*type = '\0';
	CFDictionaryGetCString( inDeviceInfo, CFSTR( kBonjourDeviceKey_ServiceType ), type, sizeof( type ), NULL );
		
	services = CFDictionaryGetCFArray( inDeviceInfo, CFSTR( kBonjourDeviceKey_Services ), NULL );
	n = services ? CFArrayGetCount( services ) : 0;
	for( i = 0; i < n; ++i )
	{
		serviceInfo = CFArrayGetCFDictionaryAtIndex( services, i, NULL );
		check( serviceInfo );
		if( !serviceInfo ) continue;
		
		*rawName = '\0';
		CFDictionaryGetCString( serviceInfo, CFSTR( kBonjourDeviceKey_RawName ), rawName, sizeof( rawName ), &err );
		check_noerr( err );
		if( err ) continue;
		
		*domain = '\0';
		CFDictionaryGetCString( serviceInfo, CFSTR( kBonjourDeviceKey_Domain ), domain, sizeof( domain ), NULL );
		check_noerr( err );
		if( err ) continue;
		
		ifindex = (uint32_t) CFDictionaryGetInt64( serviceInfo, CFSTR( kBonjourDeviceKey_InterfaceIndex ), NULL );
		
		err = DNSServiceConstructFullName( name, rawName, type, domain );
		check_noerr( err );
		if( err ) continue;
		
		ptr = MakeDomainNameFromDNSNameString( rdata, name );
		check( ptr );
		if( !ptr ) continue;
		
		snprintf( name, sizeof( name ), "%s%s", type, domain );
		bb_ulog( kLogLevelNotice, "Reconfirming PTR for %s.%s%s on interface %u\n", rawName, type, domain, ifindex );
		err = DNSServiceReconfirmRecord( 0, ifindex, name, kDNSServiceType_PTR, kDNSServiceClass_IN, 
			(uint16_t)( ptr - rdata ), rdata );
		check_noerr( err );
	}
}

//===========================================================================================================================
//	_BonjourDevice_CreateDictionary
//===========================================================================================================================

static CFDictionaryRef	_BonjourDevice_CreateDictionary( BonjourDeviceRef inDevice, OSStatus *outErr )
{
	CFMutableDictionaryRef		result = NULL;
	OSStatus					err;
	BonjourServiceRef			bestService, service;
	const uint8_t *				txtPtr;
	size_t						txtLen;
	CFMutableDictionaryRef		newDeviceInfo	= NULL;
	CFMutableArrayRef			serviceArray	= NULL;
	CFDictionaryRef				serviceDict;
	const char *				namePtr;
	size_t						nameLen;
	char						fullName[ kDNSServiceMaxDomainName + 64 ];
	size_t						len;
	Boolean						p2pOnly;
	
	// If there's no TXT RData (e.g. TXT records removed), but there is a previous deviceInfo then use that.
	// This is so we can post a remove event in the case where the TXT remove comes before the PTR remove.
	
	bestService = _BonjourDevice_GetBestService( inDevice );
	if( !bestService || !bestService->txtRDataList )
	{
		require_action_quiet( inDevice->deviceInfo, exit, err = kNotPreparedErr );
		result = CFDictionaryCreateMutableCopy( NULL, 0, inDevice->deviceInfo );
		require_action( result, exit, err = kNoMemoryErr );
		CFDictionaryRemoveValue( result, CFSTR( kBonjourDeviceKey_Services ) );
		err = kNoErr;
		goto exit;
	}
	txtPtr = bestService->txtRDataList->rdata;
	txtLen = bestService->txtRDataList->rdlen;
	
	newDeviceInfo = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
	require_action( newDeviceInfo, exit, err = kNoMemoryErr );
	
	// Name
	
	namePtr = _BonjourService_GetName( bestService, &nameLen );
	CFDictionarySetCString( newDeviceInfo, CFSTR( kBonjourDeviceKey_Name ), namePtr, nameLen );
	
	// DeviceID
	
	CFDictionarySetCString( newDeviceInfo, CFSTR( kBonjourDeviceKey_DeviceID ), inDevice->deviceID, kSizeCString );
	
	// DNSName
	
	err = DNSServiceConstructFullNameEx( fullName, bestService->name, bestService->type, bestService->domain );
	require_noerr( err, exit );
	len = strlen( fullName );
	snprintf( &fullName[ len ], sizeof( fullName ) - len, "%%%u", bestService->ifindex );
	CFDictionarySetCString( newDeviceInfo, CFSTR( kBonjourDeviceKey_DNSName ), fullName, kSizeCString );
	
	// ServiceType
	
	CFDictionarySetCString( newDeviceInfo, CFSTR( kBonjourDeviceKey_ServiceType ), bestService->type, kSizeCString );
	
	// TXT
	
	if( stricmp( bestService->type, "_airport._tcp." ) == 0 )
	{
		TXTRecordRef		txtRec;
		uint8_t				txtBuf[ 256 ];
		const char *		src;
		const char *		end;
		char				keyStr[ 8 ];
		size_t				keyLen;
		char				valueStr[ 256 ];
		size_t				valueLen;
		
		// Convert the AirPort comma-separated TXT record into a normal TXT record.
		
		require_action( txtLen > 0, exit, err = kSizeErr );
		TXTRecordCreate( &txtRec, (uint16_t) sizeof( txtBuf ), txtBuf );
		src = ( (const char *) txtPtr ) + 1;
		end = src + ( txtLen - 1 );
		while( ParseCommaSeparatedNameValuePair( src, end, keyStr, sizeof( keyStr ) - 1, &keyLen, NULL, 
			valueStr, sizeof( valueStr ), &valueLen, NULL, &src ) == kNoErr )
		{
			keyStr[ keyLen ] = '\0';
			TXTRecordSetValue( &txtRec, keyStr, (uint8_t) valueLen, valueStr );
		}
		
		CFDictionarySetData( newDeviceInfo, CFSTR( kBonjourDeviceKey_TXT ), 
			TXTRecordGetBytesPtr( &txtRec ), TXTRecordGetLength( &txtRec ) );
		TXTRecordDeallocate( &txtRec );
	}
	else
	{
		CFDictionarySetData( newDeviceInfo, CFSTR( kBonjourDeviceKey_TXT ), txtPtr, txtLen );
	}
	
	// Add per-service info.
	
	p2pOnly = true;
	for( service = inDevice->services; service; service = service->nextForDevice )
	{
		if( !service->isP2P ) p2pOnly = false;
		
		if( !serviceArray )
		{
			serviceArray = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
			require_action( serviceArray, exit, err = kNoMemoryErr );
		}
		serviceDict = _BonjourService_CreateDictionary( service, &err );
		require_noerr( err, exit );
		CFArrayAppendValue( serviceArray, serviceDict );
		CFRelease( serviceDict );
	}
	if( serviceArray )
	{
		CFDictionarySetValue( newDeviceInfo, CFSTR( kBonjourDeviceKey_Services ), serviceArray );
		CFRelease( serviceArray );
		serviceArray = NULL;
	}
	if( p2pOnly ) CFDictionarySetValue( newDeviceInfo, CFSTR( kBonjourDeviceKey_P2POnly ), kCFBooleanTrue );
	
	result = newDeviceInfo;
	newDeviceInfo = NULL;
	
exit:
	CFReleaseNullSafe( serviceArray );
	CFReleaseNullSafe( newDeviceInfo );
	if( outErr ) *outErr = err;
	return( result );
}

//===========================================================================================================================
//	_BonjourDevice_Free
//===========================================================================================================================

static void	_BonjourDevice_Free( BonjourDeviceRef inDevice )
{
	ForgetCF( &inDevice->deviceInfo );
	free( inDevice );
}

//===========================================================================================================================
//	_BonjourDevice_GetBestService
//===========================================================================================================================

static BonjourServiceRef	_BonjourDevice_GetBestService( BonjourDeviceRef inDevice )
{
	BonjourServiceRef		firstNonP2P, firstLocal, service;
	
	// Pick a service to use for TXT queries. All the services should have the same info so we don't need to keep 
	// redundant queries active. This prefers the local service because it should be faster and more reliable
	// than having to go across the Internet to talk to a Wide-Area server.
	
	firstNonP2P = NULL;
	firstLocal  = NULL;
	for( service = inDevice->services; service; service = service->nextForDevice )
	{
		if( strcmp( service->domain, "local." ) == 0 )
		{
			if( !service->isWiFi )					return( service ); // Prefer wired interfaces over all others.
			if( !firstNonP2P && !service->isP2P )	firstNonP2P = service;
			if( !firstLocal )						firstLocal = service;
		}
	}
	if( firstNonP2P ) return( firstNonP2P );
	if( firstLocal )  return( firstLocal );
	return( inDevice->services );
}

//===========================================================================================================================
//	_BonjourDevice_UpdateTXTQueries
//===========================================================================================================================

static OSStatus	_BonjourDevice_UpdateTXTQueries( BonjourDeviceRef inDevice )
{
	OSStatus				err;
	BonjourServiceRef		bestService;
	BonjourServiceRef		service;
	
	bestService = _BonjourDevice_GetBestService( inDevice );
	require_action( bestService, exit, err = kNotFoundErr );
	
	if( ( inDevice->autoStopTXT < 0 ) && bestService->txtRDataList )
	{
		inDevice->autoStopTXT = _BonjourService_IsAutoStopTXT( bestService );
	}
	
	// Stop TXT queries we no longer need.
	
	for( service = inDevice->services; service; service = service->nextForDevice )
	{
		if( ( inDevice->autoStopTXT > 0 ) || ( service != bestService ) )
		{
			DNSServiceForget( &service->txtQueryRef );
		}
	}
	
	// Make sure we have a TXT query active for the best service if needed.
	
	if( ( inDevice->autoStopTXT <= 0 ) && !bestService->txtQueryRef )
	{
		err = _BonjourService_StartTXTQuery( bestService );
		require_noerr( err, exit );
	}
	err = kNoErr;
	
exit:
	return( err );
}

#if 0
#pragma mark -
#endif

//===========================================================================================================================
//	_BonjourService_Create
//===========================================================================================================================

static OSStatus
	_BonjourService_Create( 
		BonjourBrowserRef	inBrowser, 
		const char *		inName, 
		const char *		inType, 
		const char *		inDomain, 
		uint32_t			inIfIndex, 
		BonjourServiceRef *	outService )
{
	OSStatus				err;
	BonjourServiceRef		obj;
	
	obj = (BonjourServiceRef) calloc( 1, sizeof( *obj ) );
	require_action( obj, exit, err = kNoMemoryErr );
	
	obj->browser = inBrowser;
	
	obj->name = strdup( inName );
	require_action( obj->name, exit, err = kDNSServiceErr_NoMemory );
	
	obj->type = strdup( inType );
	require_action( obj->type, exit, err = kDNSServiceErr_NoMemory );
	
	obj->domain = strdup( inDomain );
	require_action( obj->domain, exit, err = kDNSServiceErr_NoMemory );
	
	obj->ifindex = inIfIndex;
	if_indextoname( inIfIndex, obj->ifname );
	SocketGetInterfaceInfo( kInvalidSocketRef, obj->ifname, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &obj->transportType );
	if( NetTransportTypeIsP2P( obj->transportType ) )		{ obj->isP2P  = true; obj->isWiFi = true; }
	else if( obj->transportType == kNetTransportType_WiFi )	  obj->isWiFi = true;
	
	*outService = obj;
	obj = NULL;
	err = kNoErr;
	
exit:
	if( obj ) _BonjourService_Free( obj );
	return( err );
}

//===========================================================================================================================
//	_BonjourService_Free
//===========================================================================================================================

static void	_BonjourService_Free( BonjourServiceRef inService )
{
	BonjourRDataRef		rdata;
	
	ForgetMem( &inService->name );
	ForgetMem( &inService->type );
	ForgetMem( &inService->domain );
	DNSServiceForget( &inService->txtQueryRef );
	while( ( rdata = inService->txtRDataList ) != NULL )
	{
		inService->txtRDataList = rdata->next;
		free( rdata );
	}
	free( inService );
}

//===========================================================================================================================
//	_BonjourService_StartTXTQuery
//===========================================================================================================================

static OSStatus	_BonjourService_StartTXTQuery( BonjourServiceRef inService )
{
	OSStatus			err;
	char				fullName[ kDNSServiceMaxDomainName ];
	DNSServiceRef		service;
	
	err = DNSServiceConstructFullName( fullName, inService->name, inService->type, inService->domain );
	require_noerr( err, exit );
	
#if( BONJOUR_USE_GCD )
	service = inService->browser->connectionRef;
#else
	require_action( inService->browser->connectionCtx, exit, err = kNotPreparedErr );
	service = inService->browser->connectionCtx->dnsService;
#endif
	err = DNSServiceQueryRecord( &service, kDNSServiceFlagsShareConnection | kDNSServiceFlagsUnicastResponse_compat, 
		inService->ifindex, fullName, kDNSServiceType_TXT, kDNSServiceClass_IN, _BonjourService_TXTHandler, inService );
	require_noerr( err, exit );
	inService->txtQueryRef = service;
	
exit:
	return( err );
}

//===========================================================================================================================
//	_BonjourService_TXTHandler
//===========================================================================================================================

static void DNSSD_API
	_BonjourService_TXTHandler(
		DNSServiceRef		inRef,
		DNSServiceFlags		inFlags,
		uint32_t			inIfIndex,
		DNSServiceErrorType	inError,
		const char *		inFullName,    
		uint16_t			inRRType,
		uint16_t			inRRClass,
		uint16_t			inRDataSize,
		const void *		inRData,
		uint32_t			inTTL,
		void *				inContext )
{
	BonjourServiceRef const		service = (BonjourServiceRef) inContext;
	BonjourBrowserRef const		browser = service->browser;
	OSStatus					err;
	Boolean						add;
	char						deviceID[ 64 ];
	BonjourDeviceRef			device;
	
	(void) inRef;
	(void) inRRType;
	(void) inRRClass;
	(void) inTTL;
	
	require_noerr_action_quiet( inError, exit, // This probably means discoveryd crashed. Ignore here. PTR handler will retry.
		bb_ulog( kLogLevelWarning, "### Browser for %s TXT error: %#m\n", browser->serviceType, inError ) );
	require_action( browser->started, exit, bb_ulog( kLogLevelWarning, "### TXT response after stop\n" ) );
	
	add = ( inFlags & kDNSServiceFlagsAdd ) ? true : false;
	bb_ulog( kLogLevelVerbose, "Bonjour TXT %s %s on %u\n", add ? "Add" : "Rmv", inFullName, inIfIndex );
	
	// Due to <radar:7193656>, when there is a name collision, we may end up getting an add for the new, good data
	// then get adds/removes of the bad data. Eventually, the bad data(s) will be removed, but we won't get an another
	// add of the good data (since it already delivered an add) so we have to track all the records that have been
	// added/removed so we still have that one good record later when all the bad ones have finally been removed.
	
	if( add )	_BonjourService_AddRData(    service, inRData, inRDataSize );
	else		_BonjourService_RemoveRData( service, inRData, inRDataSize );
	if( service->txtRDataList )
	{
		// Due to <radar:10037073>, mDNSResponder may deliver a TXT record for a different device and then later 
		// update again with the correct info. To work around this without clients needing to deal with device
		// identities changing underneath them, if the unique deviceID changes, remove and then re-add the device.
		
		err = _BonjourService_GetDeviceID( service, deviceID, sizeof( deviceID ) );
		require_noerr( err, exit );
		
		device = service->device;
		if( device && ( stricmp( device->deviceID, deviceID ) != 0 ) )
		{
			bb_issues_ulog( kLogLevelInfo, "Removing and re-adding %s.%s%s for stale TXT record update (%s -> %s)\n", 
				service->name, service->type, service->domain, device->deviceID, deviceID );
			
			_BonjourBrowser_RemoveService( browser, service, true );
			service->device = NULL;
		}
		
		// If the service isn't associated with a device then find a matching one or create a new one.
		
		if( !service->device )
		{
			for( device = browser->devices; device; device = device->next )
			{
				if( stricmp( device->deviceID, deviceID ) == 0 )
				{
					break;
				}
			}
			if( !device )
			{
				device = (BonjourDeviceRef) calloc( 1, sizeof( *device ) );
				require_action( device, exit, err = kNoMemoryErr );
				device->autoStopTXT = -1; // -1 means we don't know yet.
				
				device->browser = browser;
				strlcpy( device->deviceID, deviceID, sizeof( device->deviceID ) );
				
				device->next = browser->devices;
				browser->devices = device;
			}
			
			service->device = device;
			service->nextForDevice = device->services;
			device->services = service;
		}
		
		_BonjourDevice_UpdateTXTQueries( device );
		_BonjourBrowser_PostEvent( browser, kBonjourBrowserEventType_AddOrUpdateDevice, device, NULL );
	}
	
exit:
	return;
}

//===========================================================================================================================
//	_BonjourService_AddRData
//===========================================================================================================================

static OSStatus	_BonjourService_AddRData( BonjourServiceRef inService, const void *inRData, size_t inRDLen )
{
	OSStatus			err;
	BonjourRDataRef		obj;
	
	if( inService->txtRDataList && log_category_enabled( bb_issues_ucat(), kLogLevelInfo ) )
	{
		int		i;
		
		bb_issues_ulog( kLogLevelInfo, "### Add without remove new:   %s.%s%s%%%u: %#{txt}\n", 
			inService->name, inService->type, inService->domain, inService->ifindex, inRData, inRDLen );
		i = 1;
		for( obj = inService->txtRDataList; obj; obj = obj->next )
		{
			bb_issues_ulog( kLogLevelInfo, "### Add without remove old %d: %s.%s%s%%%u: %#{txt}\n", 
				i, inService->name, inService->type, inService->domain, inService->ifindex, obj->rdata, obj->rdlen );
			++i;
		}
	}
	
	for( obj = inService->txtRDataList; obj; obj = obj->next )
	{
		if( MemEqual( inRData, inRDLen, obj->rdata, obj->rdlen ) )
		{
			err = kDuplicateErr;
			goto exit;
		}
	}
	
	obj = (BonjourRDataRef) calloc( 1, offsetof( struct BonjourRData, rdata ) + inRDLen );
	require_action( obj, exit, err = kNoMemoryErr );
	
	obj->next  = inService->txtRDataList;
	obj->rdlen = inRDLen;
	memcpy( obj->rdata, inRData, inRDLen );
	inService->txtRDataList = obj;
	err = kNoErr;
	
exit:
	return( err );
}

//===========================================================================================================================
//	_BonjourService_RemoveRData
//===========================================================================================================================

static void	_BonjourService_RemoveRData( BonjourServiceRef inService, const void *inRData, size_t inRDLen )
{
	BonjourRDataRef *		next;
	BonjourRDataRef			curr;
	
	for( next = &inService->txtRDataList; ( curr = *next ) != NULL; next = &curr->next )
	{
		if( MemEqual( inRData, inRDLen, curr->rdata, curr->rdlen ) )
		{
			*next = curr->next;
			free( curr );
			break;
		}
	}
	if( !curr )
	{
		bb_ulog( kLogLevelInfo, "### Removed RData missing for %s.%s%s %%%u\n%1.1H\n", 
			inService->name, inService->type, inService->domain, inService->ifindex, 
			inRData, (int) inRDLen, (int) inRDLen );
	}
}

//===========================================================================================================================
//	_BonjourService_CreateDictionary
//===========================================================================================================================

static CFDictionaryRef	_BonjourService_CreateDictionary( BonjourServiceRef inService, OSStatus *outErr )
{
	CFDictionaryRef				result = NULL;
	OSStatus					err;
	CFMutableDictionaryRef		serviceDict = NULL;
	char						fullName[ kDNSServiceMaxDomainName + 64 ];
	size_t						len;
	
	serviceDict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
	require_action( serviceDict, exit, err = kNoMemoryErr );
		
	// DNSName
	
	err = DNSServiceConstructFullNameEx( fullName, inService->name, inService->type, inService->domain );
	require_noerr( err, exit );
	len = strlen( fullName );
	snprintf( &fullName[ len ], sizeof( fullName ) - len, "%%%u", inService->ifindex );
	CFDictionarySetCString( serviceDict, CFSTR( kBonjourDeviceKey_DNSName ), fullName, kSizeCString );
	
	// Domain
	
	CFDictionarySetCString( serviceDict, CFSTR( kBonjourDeviceKey_Domain ), inService->domain, kSizeCString );
	
	// InterfaceIndex
	
	CFDictionarySetInt64( serviceDict, CFSTR( kBonjourDeviceKey_InterfaceIndex ), inService->ifindex );
	
	// InterfaceName
	
	CFDictionarySetCString( serviceDict, CFSTR( kBonjourDeviceKey_InterfaceName ), inService->ifname, kSizeCString );
	
	// P2P
	
	if( inService->isP2P ) CFDictionarySetValue( serviceDict, CFSTR( kBonjourDeviceKey_P2P ), kCFBooleanTrue );
	
	// RawName
	
	CFDictionarySetCString( serviceDict, CFSTR( kBonjourDeviceKey_RawName ), inService->name, kSizeCString );
	
	// TransportType
	
	CFDictionarySetInt64( serviceDict, CFSTR( kBonjourDeviceKey_TransportType ), inService->transportType );
	
	// WiFi
	
	if( inService->isWiFi ) CFDictionarySetValue( serviceDict, CFSTR( kBonjourDeviceKey_WiFi ), kCFBooleanTrue );
	
	result = serviceDict;
	serviceDict = NULL;
	
exit:
	CFReleaseNullSafe( serviceDict );
	if( outErr ) *outErr = err;
	return( result );
}

//===========================================================================================================================
//	_BonjourService_GetDeviceID
//===========================================================================================================================

static OSStatus	_BonjourService_GetDeviceID( BonjourServiceRef inService, char *inKeyBuf, size_t inKeyMaxLen )
{
	OSStatus			err;
	const uint8_t *		txtPtr;
	uint16_t			txtLen;
	const char *		keyPtr;
	uint8_t				keyLen;
	char				valueStr[ 256 ];
	uint8_t				deviceID[ 6 ];
	
	txtPtr = inService->txtRDataList->rdata;
	txtLen = (uint16_t) inService->txtRDataList->rdlen;
	
	if( ( stricmp( inService->type, "_airplay._tcp." )    == 0 ) ||
		( stricmp( inService->type, "_mfi-config._tcp." ) == 0 ) )
	{
		// This key should already be in AA:BB:CC:DD:EE:FF format, but validate and normalize to handle case issues, etc.
		
		keyPtr = (const char *) TXTRecordGetValuePtr( txtLen, txtPtr, "deviceid", &keyLen );
		require_action( keyPtr, exit, err = kNotFoundErr );
		
		err = TextToMACAddress( keyPtr, keyLen, deviceID );
		require_noerr( err, exit );
		
		keyPtr = MACAddressToCString( deviceID, valueStr );
		keyLen = (uint8_t) strlen( keyPtr );
	}
	else if( ( inService->browser->flags & kBonjourBrowserFlag_StandardID ) || 
			 ( stricmp( inService->type, "_hap._tcp." ) == 0 ) )
	{
		// This key should already be in AA:BB:CC:DD:EE:FF format, but validate and normalize to handle case issues, etc.
		
		keyPtr = (const char *) TXTRecordGetValuePtr( txtLen, txtPtr, "id", &keyLen );
		require_action( keyPtr, exit, err = kNotFoundErr );
		
		err = TextToMACAddress( keyPtr, keyLen, deviceID );
		require_noerr( err, exit );
		
		keyPtr = MACAddressToCString( deviceID, valueStr );
		keyLen = (uint8_t) strlen( keyPtr );
	}
	else if( stricmp( inService->type, "_airport._tcp." ) == 0 )
	{
		const char *		src;
		const char *		end;
		char				nameStr[ 8 ];
		size_t				nameLen;
		size_t				valueLen;
		
		// AirPort uses a comma-separated string for its TXT record with "waMA=XX-XX-XX-XX-XX-XX" as the device ID.
		
		require_action( txtLen > 0, exit, err = kSizeErr );
		keyPtr = NULL;
		keyLen = 0;
		src = ( (const char *) txtPtr ) + 1;
		end = src + ( txtLen - 1 );
		while( ParseCommaSeparatedNameValuePair( src, end, nameStr, sizeof( nameStr ), &nameLen, NULL, 
			valueStr, sizeof( valueStr ), &valueLen, NULL, &src ) == kNoErr )
		{
			if( strnicmpx( nameStr, nameLen, "waMA" ) == 0 )
			{
				keyPtr = valueStr;
				keyLen = (uint8_t) valueLen;
				break;
			}
		}
		require_action( keyPtr, exit, err = kNotFoundErr );
		
		// Normalize to AA:BB:CC:DD:EE:FF format.
		
		err = TextToMACAddress( keyPtr, keyLen, deviceID );
		require_noerr( err, exit );
		
		keyPtr = MACAddressToCString( deviceID, valueStr );
		keyLen = (uint8_t) strlen( keyPtr );
	}
	else if( stricmp( inService->type, "_raop._tcp." ) == 0 )
	{
		// These are formatted as AABBCCDDEEFF@<name> so parse the device ID from the prefix.
		
		keyPtr = strchr( inService->name, '@' );
		require_action( keyPtr, exit, err = kMalformedErr );
		keyLen = (uint8_t)( keyPtr - inService->name );
		keyPtr = inService->name;
		
		// Normalize to AA:BB:CC:DD:EE:FF format.
		
		err = TextToMACAddress( keyPtr, keyLen, deviceID );
		require_noerr( err, exit );
		
		keyPtr = MACAddressToCString( deviceID, valueStr );
		keyLen = (uint8_t) strlen( keyPtr );
	}
	else
	{
		dlogassert( "Unsupported service type '%s'", inService->type );
		err = kUnsupportedErr;
		goto exit;
	}
	
	require_action( keyLen < inKeyMaxLen, exit, err = kSizeErr );
	memcpy( inKeyBuf, keyPtr, keyLen );
	inKeyBuf[ keyLen ] = '\0';
	err = kNoErr;
	
exit:
	return( err );
}

//===========================================================================================================================
//	_BonjourService_GetName
//===========================================================================================================================

static const char *	_BonjourService_GetName( BonjourServiceRef inService, size_t *outNameLen )
{
	const char *		namePtr;
	size_t				nameLen;
	
	if( stricmp( inService->type, "_raop._tcp." ) == 0 )
	{
		// These services prefixes the name with the deviceID so strip off the prefix to get the name.
		
		namePtr = strchr( inService->name, '@' );
		namePtr = namePtr ? ( namePtr + 1 ) : inService->name;
		nameLen = strlen( namePtr );
	}
	else
	{
		namePtr = inService->name;
		nameLen = strlen( namePtr );
	}
	*outNameLen = nameLen;
	return( namePtr );
}

//===========================================================================================================================
//	_BonjourService_IsAutoStopTXT
//===========================================================================================================================

static Boolean	_BonjourService_IsAutoStopTXT( BonjourServiceRef inService )
{
	const uint8_t *		txtPtr;
	uint16_t			txtLen;
	const char *		valuePtr;
	uint8_t				valueLen;
	int					n;
	uint64_t			u64;
	
	if( stricmp( inService->type, "_raop._tcp." ) == 0 )
	{
		txtPtr = inService->txtRDataList->rdata;
		txtLen = (uint16_t) inService->txtRDataList->rdlen;
		
		// Check for a "Unified Bonjour" feature flag to indicate it's using both _airplay._tcp and _raop._tcp.
		// This means _airplay._tcp contains everything _raop._tcp has so we can stop TXT queries for _raop._tcp.
		
		valuePtr = (const char *) TXTRecordGetValuePtr( txtLen, txtPtr, "ft", &valueLen );
		if( valuePtr )
		{
			n = SNScanF( valuePtr, valueLen, "%llx", &u64 );
			if( ( n == 1 ) && ( u64 & 0x40000000 ) ) // kAirPlayFeature_UnifiedBonjour
			{
				return( true );
			}
		}
		
		// For devices built before the feature flag was introduced, fall back to checking for an AppleTV.
		// AppleTV has always used both _airplay._tcp and _raop._tcp so we can stop _raop._tcp queries to AppleTV.
		
		valuePtr = (const char *) TXTRecordGetValuePtr( txtLen, txtPtr, "am", &valueLen );
		if( valuePtr && ( strnicmp_prefix( valuePtr, valueLen, "AppleTV" ) == 0 ) )
		{
			return( true );
		}
	}
	return( false );
}

#if 0
#pragma mark -
#pragma mark == Accessors ==
#endif

//===========================================================================================================================
//	BonjourDevice_CopyCFString
//===========================================================================================================================

CFStringRef	BonjourDevice_CopyCFString( CFDictionaryRef inDeviceInfo, const char *inTXTKey, OSStatus *outErr )
{
	CFStringRef			cfstr;
	CFDataRef			txt;
	const uint8_t *		txtPtr;
	uint16_t			txtLen;
	const uint8_t *		valuePtr;
	uint8_t				valueLen;
	OSStatus			err;
	
	cfstr = NULL;
	
	txt = (CFDataRef) CFDictionaryGetValue( inDeviceInfo, CFSTR( kBonjourDeviceKey_TXT ) );
	require_action( txt, exit, err = kInternalErr );
	txtPtr = CFDataGetBytePtr( txt );
	txtLen = (uint16_t) CFDataGetLength( txt );
	
	valuePtr = (const uint8_t *) TXTRecordGetValuePtr( txtLen, txtPtr, inTXTKey, &valueLen );
	require_action_quiet( valuePtr, exit, err = kNotFoundErr );
	valueLen = (uint8_t) strnlen( (const char *) valuePtr, valueLen );
	
	cfstr = CFStringCreateWithBytes( NULL, valuePtr, valueLen, kCFStringEncodingUTF8, false );
	require_action( cfstr, exit, err = kMalformedErr );
	err = kNoErr;
	
exit:
	if( outErr ) *outErr = err;
	return( cfstr );
}

//===========================================================================================================================
//	BonjourDevice_GetBitListValue
//===========================================================================================================================

uint32_t	BonjourDevice_GetBitListValue( CFDictionaryRef inDeviceInfo, const char *inTXTKey, OSStatus *outErr )
{
	uint32_t			value;
	CFDataRef			txt;
	const uint8_t *		txtPtr;
	uint16_t			txtLen;
	const char *		valuePtr;
	uint8_t				valueLen;
	OSStatus			err;
	
	value = 0;
	
	txt = (CFDataRef) CFDictionaryGetValue( inDeviceInfo, CFSTR( kBonjourDeviceKey_TXT ) );
	require_action( txt, exit, err = kInternalErr );
	txtPtr = CFDataGetBytePtr( txt );
	txtLen = (uint16_t) CFDataGetLength( txt );
	
	valuePtr = (const char *) TXTRecordGetValuePtr( txtLen, txtPtr, inTXTKey, &valueLen );
	require_action_quiet( valuePtr, exit, err = kNotFoundErr );
	
	err = BitListString_Parse( valuePtr, valueLen, &value );
	require_noerr( err, exit );
	
exit:
	if( outErr ) *outErr = err;
	return( value );
}

//===========================================================================================================================
//	BonjourDevice_GetDeviceID
//===========================================================================================================================

uint64_t	BonjourDevice_GetDeviceID( CFDictionaryRef inDeviceInfo, uint8_t outDeviceID[ 6 ], OSStatus *outErr )
{
	uint64_t			value;
	CFStringRef			tempCFStr;
	char				tempCStr[ 64 ];
	uint8_t				tempBuf[ 6 ];
	Boolean				good;
	OSStatus			err;
	
	value = 0;
	
	tempCFStr = (CFStringRef) CFDictionaryGetValue( inDeviceInfo, CFSTR( kBonjourDeviceKey_DeviceID ) );
	require_action( tempCFStr, exit, err = kInternalErr );
	
	good = CFStringGetCString( tempCFStr, tempCStr, (CFIndex) sizeof( tempCStr ), kCFStringEncodingASCII );
	require_action( good, exit, err = kMalformedErr );
	
	if( !outDeviceID ) outDeviceID = tempBuf;
	err = TextToMACAddress( tempCStr, kSizeCString, outDeviceID );
	require_noerr( err, exit );
	value = ReadBig48( outDeviceID );
	
exit:
	if( outErr ) *outErr = err;
	return( value );
}

//===========================================================================================================================
//	BonjourDevice_GetDNSName
//===========================================================================================================================

OSStatus	BonjourDevice_GetDNSName( CFDictionaryRef inDeviceInfo, uint64_t inFlags, char *inBuf, size_t inMaxLen )
{
	Boolean const		wantsP2P = (Boolean)( ( inFlags & kBonjourBrowserFlag_P2P ) != 0 );
	OSStatus			err;
	CFArrayRef			services;
	CFIndex				i, n;
	CFDictionaryRef		firstWiredInfo, firstLocalInfo, firstInfo, bestInfo, info;
	Boolean				b;
	char				tempStr[ 256 ];
	
	firstWiredInfo	= NULL;
	firstLocalInfo	= NULL;
	firstInfo		= NULL;
	services = (CFArrayRef) CFDictionaryGetValue( inDeviceInfo, CFSTR( kBonjourDeviceKey_Services ) );
	n = services ? CFArrayGetCount( services ) : 0;
	for( i = 0; i < n; ++i )
	{
		info = (CFDictionaryRef) CFArrayGetValueAtIndex( services, i );
		
		b = CFDictionaryGetBoolean( info, CFSTR( kBonjourDeviceKey_P2P ), NULL );
		if( b != wantsP2P ) continue;
		
		*tempStr = '\0';
		CFDictionaryGetCString( info, CFSTR( kBonjourDeviceKey_Domain ), tempStr, sizeof( tempStr ), NULL );
		if( strcmp( tempStr, "local." ) == 0 )
		{
			if( !firstWiredInfo && !CFDictionaryGetBoolean( info, CFSTR( kBonjourDeviceKey_WiFi ), NULL ) )
			{
				firstWiredInfo = info;
			}
			if( !firstLocalInfo ) firstLocalInfo = info;
		}
		if( !firstInfo ) firstInfo = info;
	}
	if(      firstWiredInfo )	bestInfo = firstWiredInfo;
	else if( firstLocalInfo )	bestInfo = firstLocalInfo;
	else						bestInfo = firstInfo;
	require_action_quiet( bestInfo, exit, err = kNotFoundErr );
	
	if( inBuf )
	{
		CFDictionaryGetCString( bestInfo, CFSTR( kBonjourDeviceKey_DNSName ), inBuf, inMaxLen, &err );
		require_noerr( err, exit );
	}
	err = kNoErr;
	
exit:
	return( err );
}

//===========================================================================================================================
//	BonjourDevice_GetInt64
//===========================================================================================================================

int64_t	BonjourDevice_GetInt64( CFDictionaryRef inDeviceInfo, const char *inTXTKey, int inBase, OSStatus *outErr )
{
	Value64				value, value2;
	CFDataRef			txt;
	const uint8_t *		txtPtr;
	uint16_t			txtLen;
	const char *		valuePtr;
	uint8_t				valueLen;
	char				tempStr[ 64 ];
	int					n;
	OSStatus			err;
	
	value.s64 = 0;
	
	txt = (CFDataRef) CFDictionaryGetValue( inDeviceInfo, CFSTR( kBonjourDeviceKey_TXT ) );
	require_action( txt, exit, err = kInternalErr );
	txtPtr = CFDataGetBytePtr( txt );
	txtLen = (uint16_t) CFDataGetLength( txt );
	
	valuePtr = (const char *) TXTRecordGetValuePtr( txtLen, txtPtr, inTXTKey, &valueLen );
	require_action_quiet( valuePtr, exit, err = kNotFoundErr );
	require_action( valueLen < sizeof( tempStr ), exit, err = kSizeErr );
	memcpy( tempStr, valuePtr, valueLen );
	tempStr[ valueLen ] = '\0';
	
	if(      IsTrueString( valuePtr,  valueLen ) ) { value.s64 = 1; n = 1; }
	else if( IsFalseString( valuePtr, valueLen ) ) { value.s64 = 0; n = 1; }
	else if( inBase ==  0 ) n = SNScanF( valuePtr, valueLen, "%lli", &value.s64 );
	else if( inBase == 16 ) n = SNScanF( valuePtr, valueLen, "%llx", &value.u64 );
	else if( inBase == 10 ) n = SNScanF( valuePtr, valueLen, "%lld", &value.s64 );
	else if( inBase ==  8 ) n = SNScanF( valuePtr, valueLen, "%llo", &value.u64 );
	else { dlogassert( "Unsupported base (%d)", inBase ); err = kParamErr; goto exit; }
	require_action( n == 1, exit, err = kMalformedErr );
	
	valuePtr = strchr( tempStr, ',' );
	if( valuePtr )
	{
		value2.s64 = 0;
		++valuePtr;
		if(      IsTrueString( valuePtr,  SIZE_MAX ) ) { value2.s64 = 1; n = 1; }
		else if( IsFalseString( valuePtr, SIZE_MAX ) ) { value2.s64 = 0; n = 1; }
		else if( inBase ==  0 ) n = SNScanF( valuePtr, SIZE_MAX, "%lli", &value2.s64 );
		else if( inBase == 16 ) n = SNScanF( valuePtr, SIZE_MAX, "%llx", &value2.u64 );
		else if( inBase == 10 ) n = SNScanF( valuePtr, SIZE_MAX, "%lld", &value2.s64 );
		else if( inBase ==  8 ) n = SNScanF( valuePtr, SIZE_MAX, "%llo", &value2.u64 );
		else { dlogassert( "Unsupported base (%d)", inBase ); err = kParamErr; goto exit; }
		require_action( n == 1, exit, err = kMalformedErr );
		
		value.u64 |= ( value2.u64 << 32 );
	}
	err = kNoErr;
	
exit:
	if( outErr ) *outErr = err;
	return( value.s64 );
}

//===========================================================================================================================
//	BonjourDevice_MergeInfo
//===========================================================================================================================

OSStatus	BonjourDevice_MergeInfo( CFDictionaryRef *ioDeviceInfo, CFDictionaryRef inNewDeviceInfo )
{
	CFDictionaryRef const		oldDeviceInfo	= *ioDeviceInfo;
	OSStatus					err;
	CFMutableDictionaryRef		mergedInfo		= NULL;
	CFMutableArrayRef			mergedServices	= NULL;
	CFArrayRef					oldServices		= NULL;
	CFArrayRef					newServices;
	CFIndex						oldIndex, oldCount, newIndex, newCount;
	CFDictionaryRef				oldServiceDict, newServiceDict;
	CFArrayRef					removedServices;
	CFIndex						removedIndex, removedCount;
	CFDictionaryRef				removedServiceDict;
	CFTypeRef					oldDomain, oldIfName, newDomain, newIfName, removedDomain, removedIfName;
	Boolean						p2pOnly;
	
	if( !oldDeviceInfo )
	{
		CFRetain( inNewDeviceInfo );
		*ioDeviceInfo = inNewDeviceInfo;
		err = kNoErr;
		goto exit;
	}
	
	// Create the new info, but save off the old services since the merge of the new info may replace the services array.
	
	oldServices = CFDictionaryGetCFArray( oldDeviceInfo, CFSTR( kBonjourDeviceKey_Services ), NULL );
	CFRetainNullSafe( oldServices );
	
	mergedInfo = CFDictionaryCreateMutableCopy( NULL, 0, oldDeviceInfo );
	require_action( mergedInfo, exit, err = kNoMemoryErr );
	
	CFDictionaryMergeDictionary( mergedInfo, inNewDeviceInfo );
	CFDictionaryRemoveValue( mergedInfo, CFSTR( kBonjourDeviceKey_RemovedServices ) );
	
	newServices = CFDictionaryGetCFArray( inNewDeviceInfo, CFSTR( kBonjourDeviceKey_Services ), NULL );
	if( newServices )	mergedServices = CFArrayCreateMutableCopy( NULL, 0, newServices );
	else				mergedServices = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
	require_action( mergedServices, exit, err = kNoMemoryErr );
	
	// Add each old service that's not in the removed list and that's not already in the new list.
	
	removedServices	= CFDictionaryGetCFArray( inNewDeviceInfo, CFSTR( kBonjourDeviceKey_RemovedServices ), NULL );
	removedCount	= removedServices ? CFArrayGetCount( removedServices ) : 0;
	newCount		= newServices ? CFArrayGetCount( newServices ) : 0;
	oldCount		= oldServices ? CFArrayGetCount( oldServices ) : 0;
	for( oldIndex = 0; oldIndex < oldCount; ++oldIndex )
	{
		oldServiceDict = CFArrayGetCFDictionaryAtIndex( oldServices, oldIndex, NULL );
		if( !oldServiceDict ) continue;
		
		oldDomain = CFDictionaryGetValue( oldServiceDict, CFSTR( kBonjourDeviceKey_Domain ) );
		oldIfName = CFDictionaryGetValue( oldServiceDict, CFSTR( kBonjourDeviceKey_InterfaceName ) );
		
		// Skip services that are already there.
		
		for( newIndex = 0; newIndex < newCount; ++newIndex )
		{
			newServiceDict = CFArrayGetCFDictionaryAtIndex( newServices, newIndex, NULL );
			if( !newServiceDict ) continue;
			
			newDomain = CFDictionaryGetValue( newServiceDict, CFSTR( kBonjourDeviceKey_Domain ) );
			newIfName = CFDictionaryGetValue( newServiceDict, CFSTR( kBonjourDeviceKey_InterfaceName ) );
			if( CFEqualNullSafe( oldDomain, newDomain ) && CFEqualNullSafe( oldIfName, newIfName ) )
			{
				break;
			}
		}
		if( newIndex < newCount ) continue;
		
		// Skip removed services.
		
		for( removedIndex = 0; removedIndex < removedCount; ++removedIndex )
		{
			removedServiceDict = CFArrayGetCFDictionaryAtIndex( removedServices, removedIndex, NULL );
			if( !removedServiceDict ) continue;
			
			removedDomain = CFDictionaryGetValue( removedServiceDict, CFSTR( kBonjourDeviceKey_Domain ) );
			removedIfName = CFDictionaryGetValue( removedServiceDict, CFSTR( kBonjourDeviceKey_InterfaceName ) );
			if( CFEqualNullSafe( oldDomain, removedDomain ) && CFEqualNullSafe( oldIfName, removedIfName ) )
			{
				break;
			}
		}
		if( removedIndex < removedCount ) continue;
		
		CFArrayAppendValue( mergedServices, oldServiceDict );
	}
	CFDictionarySetValue( mergedInfo, CFSTR( kBonjourDeviceKey_Services ), mergedServices );
	
	// Update P2POnly. $$$ TO DO: This should really be removed and callers should manage this differently.
	
	newCount = CFArrayGetCount( mergedServices );
	p2pOnly = ( newCount > 0 ) ? true : false;
	for( newIndex = 0; newIndex < newCount; ++newIndex )
	{
		newServiceDict = CFArrayGetCFDictionaryAtIndex( mergedServices, newIndex, NULL );
		if( !newServiceDict ) continue;
		
		if( !CFDictionaryGetBoolean( newServiceDict, CFSTR( kBonjourDeviceKey_P2P ), NULL ) )
		{
			p2pOnly = false;
			break;
		}
	}
	if( p2pOnly )	CFDictionarySetValue( mergedInfo, CFSTR( kBonjourDeviceKey_P2POnly ), kCFBooleanTrue );
	else			CFDictionaryRemoveValue( mergedInfo, CFSTR( kBonjourDeviceKey_P2POnly ) );
	
	if( oldDeviceInfo ) CFRelease( oldDeviceInfo );
	*ioDeviceInfo = mergedInfo;
	mergedInfo = NULL;
	err = kNoErr;
	
exit:
	CFReleaseNullSafe( mergedServices );
	CFReleaseNullSafe( mergedInfo );
	CFReleaseNullSafe( oldServices );
	return( err );
}

//===========================================================================================================================
//	BonjourDevice_RemoveInterfaceInfo
//===========================================================================================================================

OSStatus	BonjourDevice_RemoveInterfaceInfo( CFDictionaryRef *ioDeviceInfo, const char *inIfName, Boolean inRemoveOthers )
{
	OSStatus					err;
	CFMutableDictionaryRef		newInfo		= NULL;
	CFMutableArrayRef			newServices = NULL;
	CFArrayRef					oldServices;
	CFIndex						n;
	CFDictionaryRef				serviceDict;
	char						ifname[ IF_NAMESIZE + 1 ];
	
	require_action_quiet( *ioDeviceInfo, exit, err = kNoErr );
	oldServices = CFDictionaryGetCFArray( *ioDeviceInfo, CFSTR( kBonjourDeviceKey_Services ), NULL );
	n = oldServices ? CFArrayGetCount( oldServices ) : 0;
	require_action_quiet( n > 0, exit, err = kNoErr );
	
	newInfo = CFDictionaryCreateMutableCopy( NULL, 0, *ioDeviceInfo );
	require_action( newInfo, exit, err = kNoMemoryErr );
	
	newServices = CFArrayCreateMutableCopy( NULL, 0, oldServices );
	require_action( newServices, exit, err = kNoMemoryErr );
	
	while( n-- > 0 )
	{
		serviceDict = CFArrayGetCFDictionaryAtIndex( newServices, n, NULL );
		check( serviceDict );
		if( !serviceDict ) continue;
		
		*ifname = '\0';
		CFDictionaryGetCString( serviceDict, CFSTR( kBonjourDeviceKey_InterfaceName ), ifname, sizeof( ifname ), NULL );
		if( ( strcmp( ifname, inIfName ) == 0 ) != !inRemoveOthers ) continue;
		
		CFArrayRemoveValueAtIndex( newServices, n );
	}
	n = CFArrayGetCount( newServices );
	if( n > 0 )	CFDictionarySetValue( newInfo, CFSTR( kBonjourDeviceKey_Services ), newServices );
	else		CFDictionaryRemoveValue( newInfo, CFSTR( kBonjourDeviceKey_Services ) );
	
	CFRelease( *ioDeviceInfo );
	*ioDeviceInfo = newInfo;
	newInfo = NULL;
	err = kNoErr;
	
exit:
	CFReleaseNullSafe( newServices );
	CFReleaseNullSafe( newInfo );
	return( err );
}

#if 0
#pragma mark -
#pragma mark == Debugging ==
#endif

#if( !EXCLUDE_UNIT_TESTS )

typedef struct
{
	dispatch_semaphore_t		doneSem;
	
}	BonjourBrowser_TestContext;

void			BonjourBrowser_TestHandler( BonjourBrowserEventType inEventType, CFDictionaryRef inEventInfo, void *inContext );
OSStatus		BonjourBrowser_TestHelpers( void );
CFDictionaryRef	BonjourDevice_GetServiceByInterfaceName( CFDictionaryRef inDeviceInfo, const char *inIfName, OSStatus *outErr );

//===========================================================================================================================
//	BonjourBrowser_Test
//===========================================================================================================================

OSStatus	BonjourBrowser_Test( void )
{
	OSStatus						err;
	CFMutableDictionaryRef			info		= NULL;
	BonjourBrowser_TestContext		context		= { NULL };
	BonjourBrowser_TestContext *	contextPtr;
	dispatch_queue_t				queue		= NULL;
	BonjourBrowserRef				browser		= NULL;
	CFMutableDictionaryRef			dict		= NULL;
	int64_t							s64;

	// Bonjour Device Accessors.
	
	dict = (CFMutableDictionaryRef) CFCreateF( &err, 
		"{"
			"%kO=%D"
		"}", 
		CFSTR( kBonjourDeviceKey_TXT ), "\x07" "x=12345", 8 );
	require_noerr( err, exit );
	s64 = BonjourDevice_GetInt64( dict, "x", 0, &err );
	require_action( s64 == 12345, exit, err = kResponseErr );
	s64 = BonjourDevice_GetInt64( dict, "x", 10, &err );
	require_action( s64 == 12345, exit, err = kResponseErr );
	ForgetCF( &dict );
	
	dict = (CFMutableDictionaryRef) CFCreateF( &err, 
		"{"
			"%kO=%D"
		"}", 
		CFSTR( kBonjourDeviceKey_TXT ), "\x09" "x=0x12345", 10 );
	require_noerr( err, exit );
	s64 = BonjourDevice_GetInt64( dict, "x", 0, &err );
	require_action( s64 == 0x12345, exit, err = kResponseErr );
	s64 = BonjourDevice_GetInt64( dict, "x", 16, &err );
	require_action( s64 == 0x12345, exit, err = kResponseErr );
	ForgetCF( &dict );
	
	dict = (CFMutableDictionaryRef) CFCreateF( &err, 
		"{"
			"%kO=%D"
		"}", 
		CFSTR( kBonjourDeviceKey_TXT ), "\x0B" "x=12345,678", 12 );
	require_noerr( err, exit );
	s64 = BonjourDevice_GetInt64( dict, "x", 0, &err );
	require_action( s64 == ( ( INT64_C( 678 ) << 32 ) | 12345 ), exit, err = kResponseErr );
	s64 = BonjourDevice_GetInt64( dict, "x", 10, &err );
	require_action( s64 == ( ( INT64_C( 678 ) << 32 ) | 12345 ), exit, err = kResponseErr );
	ForgetCF( &dict );
	
	dict = (CFMutableDictionaryRef) CFCreateF( &err, 
		"{"
			"%kO=%D"
		"}", 
		CFSTR( kBonjourDeviceKey_TXT ), "\x0F" "x=0x12345,0x678", 16 );
	require_noerr( err, exit );
	s64 = BonjourDevice_GetInt64( dict, "x", 0, &err );
	require_action( s64 == ( ( INT64_C( 0x678 ) << 32 ) | 0x12345 ), exit, err = kResponseErr );
	s64 = BonjourDevice_GetInt64( dict, "x", 16, &err );
	require_action( s64 == ( ( INT64_C( 0x678 ) << 32 ) | 0x12345 ), exit, err = kResponseErr );
	ForgetCF( &dict );
	
	// Browse test.
	
	queue = dispatch_queue_create( __ROUTINE__, NULL );
	require_action( queue, exit, err = -1 );
	
	context.doneSem = dispatch_semaphore_create( 0 );
	require_action( context.doneSem, exit, err = -1 );
	
	err = BonjourBrowser_Create( &browser, "Test" );
	require_noerr( err, exit );
	BonjourBrowser_SetDispatchQueue( browser, queue );
	
	contextPtr = &context;
	BonjourBrowser_SetEventHandler( browser, BonjourBrowser_TestHandler, contextPtr );
	
	dlog( kLogLevelVerbose, "Starting...\n" );
	err = BonjourBrowser_Start( browser, "_raop._tcp", NULL, kDNSServiceInterfaceIndexAny, 0 );
	require_noerr( err, exit );
	
	sleep( 2 );
	
	dlog( kLogLevelVerbose, "Stopping...\n" );
	BonjourBrowser_Stop( browser );
	dispatch_semaphore_wait( context.doneSem, DISPATCH_TIME_FOREVER );
	dlog( kLogLevelVerbose, "Stopped\n" );
	
	dlog( kLogLevelVerbose, "Releasing...\n" );
	BonjourBrowser_Forget( &browser );
	browser = NULL;
	usleep( 200 * 1000 );
	
exit:
	ForgetCF( &info );
	ForgetCF( &dict );
	BonjourBrowser_Forget( &browser );
	if( context.doneSem )	arc_safe_dispatch_release( context.doneSem );
	if( queue )				arc_safe_dispatch_release( queue );
	printf( "BonjourBrowser_Test: %s\n", !err ? "PASSED" : "FAILED" );
	return( err );
}

//===========================================================================================================================
//	BonjourBrowser_TestHandler
//===========================================================================================================================

void	BonjourBrowser_TestHandler( BonjourBrowserEventType inEventType, CFDictionaryRef inEventInfo, void *inContext )
{
	BonjourBrowser_TestContext * const		context = (BonjourBrowser_TestContext *) inContext;
	
	DEBUG_USE_ONLY( inEventInfo );
	
	switch( inEventType )
	{
		case kBonjourBrowserEventType_AddOrUpdateDevice:
			dlog( kLogLevelVerbose, "ADD/UPDATE:\n%1@\n", inEventInfo );
			break;
		
		case kBonjourBrowserEventType_RemoveDevice:
			dlog( kLogLevelVerbose, "REMOVE:\n%1@\n", inEventInfo );
			break;
		
		case kBonjourBrowserEventType_Stop:
			dlog( kLogLevelVerbose, "STOP\n" );
			dispatch_semaphore_signal( context->doneSem );
			break;
		
		default:
			break;
	}
}

//===========================================================================================================================
//	BonjourBrowser_TestHelpers
//===========================================================================================================================

OSStatus	BonjourBrowser_TestHelpers( void )
{
	OSStatus					err;
	CFMutableDictionaryRef		plist;
	CFDictionaryRef				deviceInfo = NULL;
	
	err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist, 
		"{"
			"%kO=%O"	// name
			"%kO="		// services
			"["
				"{"
					"%kO=%O"	// domain
					"%kO=%i"	// ifindex
					"%kO=%O"	// ifname
				"}"
				"{"
					"%kO=%O"	// domain
					"%kO=%i"	// ifindex
					"%kO=%O"	// ifname
				"}"
				"{"
					"%kO=%O"	// domain
					"%kO=%i"	// ifindex
					"%kO=%O"	// ifname
				"}"
				"{"
					"%kO=%O"	// domain
					"%kO=%i"	// ifindex
					"%kO=%O"	// ifname
				"}"
			"]"
		"}", 
		CFSTR( kBonjourDeviceKey_Name ), CFSTR( "MyDevice" ), 
		CFSTR( kBonjourDeviceKey_Services ), 
			CFSTR( kBonjourDeviceKey_Domain ),			CFSTR( "local." ), 
			CFSTR( kBonjourDeviceKey_InterfaceIndex ),	4, 
			CFSTR( kBonjourDeviceKey_InterfaceName ),	CFSTR( "en0" ), 
			
			CFSTR( kBonjourDeviceKey_Domain ),			CFSTR( "local." ), 
			CFSTR( kBonjourDeviceKey_InterfaceIndex ),	5, 
			CFSTR( kBonjourDeviceKey_InterfaceName ),	CFSTR( "en1" ), 
			
			CFSTR( kBonjourDeviceKey_Domain ),			CFSTR( "local." ), 
			CFSTR( kBonjourDeviceKey_InterfaceIndex ),	6, 
			CFSTR( kBonjourDeviceKey_InterfaceName ),	CFSTR( "en2" ), 
			
			CFSTR( kBonjourDeviceKey_Domain ),			CFSTR( "local." ), 
			CFSTR( kBonjourDeviceKey_InterfaceIndex ),	7, 
			CFSTR( kBonjourDeviceKey_InterfaceName ),	CFSTR( "en3" )
	);
	require_noerr( err, exit );
	deviceInfo = plist;
	
	err = BonjourDevice_RemoveInterfaceInfo( &deviceInfo, "en1", false );
	require_noerr( err, exit );
	require_action( BonjourDevice_GetServiceByInterfaceName( deviceInfo, "en0", NULL ), exit, err = -1 );
	require_action( !BonjourDevice_GetServiceByInterfaceName( deviceInfo, "en1", NULL ), exit, err = -1 );
	require_action( BonjourDevice_GetServiceByInterfaceName( deviceInfo, "en2", NULL ), exit, err = -1 );
	require_action( BonjourDevice_GetServiceByInterfaceName( deviceInfo, "en3", NULL ), exit, err = -1 );
	
	err = BonjourDevice_RemoveInterfaceInfo( &deviceInfo, "en3", true );
	require_noerr( err, exit );
	require_action( !BonjourDevice_GetServiceByInterfaceName( deviceInfo, "en0", NULL ), exit, err = -1 );
	require_action( !BonjourDevice_GetServiceByInterfaceName( deviceInfo, "en1", NULL ), exit, err = -1 );
	require_action( !BonjourDevice_GetServiceByInterfaceName( deviceInfo, "en2", NULL ), exit, err = -1 );
	require_action( BonjourDevice_GetServiceByInterfaceName( deviceInfo, "en3", NULL ), exit, err = -1 );
	
	err = BonjourDevice_RemoveInterfaceInfo( &deviceInfo, "en3", false );
	require_noerr( err, exit );
	require_action( !CFDictionaryGetValue( deviceInfo, CFSTR( kBonjourDeviceKey_Services ) ), exit, err = -1 );
	
exit:
	CFReleaseNullSafe( deviceInfo );
	printf( "BonjourBrowser_TestHelpers: %s\n", !err ? "PASSED" : "FAILED" );
	return( err );
}

//===========================================================================================================================
//	BonjourDevice_GetServiceByInterfaceName
//===========================================================================================================================

CFDictionaryRef	BonjourDevice_GetServiceByInterfaceName( CFDictionaryRef inDeviceInfo, const char *inIfName, OSStatus *outErr )
{
	CFDictionaryRef		result = NULL;
	CFArrayRef			services;
	CFIndex				i, n;
	CFDictionaryRef		serviceDict;
	char				ifname[ IF_NAMESIZE + 1 ];
	
	services = CFDictionaryGetCFArray( inDeviceInfo, CFSTR( kBonjourDeviceKey_Services ), NULL );
	require_quiet( services, exit );
	
	n = CFArrayGetCount( services );
	for( i = 0; i < n; ++i )
	{
		serviceDict = CFArrayGetCFDictionaryAtIndex( services, i, NULL );
		if( !serviceDict ) continue;
		
		*ifname = '\0';
		CFDictionaryGetCString( serviceDict, CFSTR( kBonjourDeviceKey_InterfaceName ), ifname, sizeof( ifname ), NULL );
		if( strcmp( ifname, inIfName ) != 0 ) continue;
		
		result = serviceDict;
		break;
	}
	
exit:
	if( outErr ) *outErr = result ? kNoErr : kNotFoundErr;
	return( result );
}

#endif // !EXCLUDE_UNIT_TESTS
