/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.

    Change History (most recent first):
	
$Log: Tool.c,v $
Revision 1.4  2006/08/14 23:25:59  cheshire
Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0

Revision 1.3  2004/09/16 01:58:25  cheshire
Fix compiler warnings

Revision 1.2  2004/07/13 21:24:28  rpantos
Fix for <rdar://problem/3701120>.

Revision 1.1  2004/06/18 04:07:54  rpantos
Move up one level

Revision 1.12  2004/04/09 21:03:15  bradley
Changed port numbers to use network byte order for consistency with other platforms.

Revision 1.11  2004/01/30 03:04:32  bradley
Updated for latest changes to mDNSWindows.

Revision 1.10  2003/10/31 12:18:31  bradley
Added display of the resolved host name. Show separate TXT record entries on separate lines.

Revision 1.9  2003/10/22 02:00:20  bradley
Fixed proxy IP setup to be in network byte order so it works on Mac and Windows.

Revision 1.8  2003/10/04 04:47:08  bradley
Changed DNSServiceRegistrationCreate to treat the port in network byte order for end-to-end consistency.

Revision 1.7  2003/08/20 07:06:34  bradley
Update to APSL 2.0. Updated change history to match other mDNSResponder files.

Revision 1.6  2003/08/20 06:50:55  bradley
Updated to latest internal version of the mDNSCore code: Re-did everything to support
the latest DNSServices APIs (proxies, record updates, etc.); Added support for testing the platform
neutral DNSServices-based emulation layer for the Mac OS X DNSServiceDiscovery API.

*/

#if( defined( _MSC_VER ) )
	#pragma warning( disable:4068 )			// Disable "unknown pragma" warning for "pragma unused".
	#pragma warning( disable:4127 )			// Disable "conditional expression is constant" warning for debug macros.
	#pragma warning( disable:4311 )			// Disable "type cast : pointer truncation from void *const to int".
	
	// No stdint.h with Visual C++ so emulate it here.
	
	typedef signed char			int8_t;		// C99 stdint.h not supported in VC++/VS.NET yet.
	typedef unsigned char		uint8_t;	// C99 stdint.h not supported in VC++/VS.NET yet.
	typedef signed short		int16_t;	// C99 stdint.h not supported in VC++/VS.NET yet.
	typedef unsigned short		uint16_t;	// C99 stdint.h not supported in VC++/VS.NET yet.
	typedef signed long			int32_t;	// C99 stdint.h not supported in VC++/VS.NET yet.
	typedef unsigned long		uint32_t;	// C99 stdint.h not supported in VC++/VS.NET yet.
#else
	#include	<stdint.h>
#endif

#include	<stdio.h>
#include	<stdlib.h>

#if( __MACH__ )
	#include	<sys/types.h>
	#include	<sys/socket.h>
	#include	<netinet/in.h>
	
	#include	<signal.h>
	#include	<unistd.h>
	
	#include	<CoreServices/CoreServices.h>
#else
	#define	WIN32_LEAN_AND_MEAN

	#include	<winsock2.h>
	#include	<windows.h>
#endif

#include	"DNSServices.h"
#include	"DNSServiceDiscovery.h"

//===========================================================================================================================
//	Macros
//===========================================================================================================================

#if( !TARGET_OS_MAC )
	#define	require_action_string( X, LABEL, ACTION, STR )				\
		do 																\
		{																\
			if( !( X ) ) 												\
			{															\
				fprintf( stderr, "%s\n", ( STR ) );						\
				{ ACTION; }												\
				goto LABEL;												\
			}															\
		} while( 0 )

	#define	require_string( X, LABEL, STR )								\
		do 																\
		{																\
			if( !( X ) ) 												\
			{															\
				fprintf( stderr, "%s\n", ( STR ) );						\
				goto LABEL;												\
																		\
			}															\
		} while( 0 )

	#define	require_noerr_string( ERR, LABEL, STR )						\
		do 																\
		{																\
			if( ( ERR ) != 0 ) 											\
			{															\
				fprintf( stderr, "%s (%ld)\n", ( STR ), ( ERR ) );		\
				goto LABEL;												\
			}															\
		} while( 0 )
#endif

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

int 				main( int argc, char* argv[] );
static void			Usage( void );
static int 			ProcessArgs( int argc, char* argv[] );
static DNSStatus	ProcessPreset( int inPreset );

#if( __MACH__ )
	static void	SigIntHandler( int inSignalNumber );
#endif

#if( defined( WINVER ) )
	static BOOL WINAPI	ConsoleControlHandler( DWORD inControlEvent );
#endif

static void	BrowserCallBack( void *inContext, DNSBrowserRef inRef, DNSStatus inStatusCode, const DNSBrowserEvent *inEvent );
static void ResolverCallBack( void *inContext, DNSResolverRef inRef, DNSStatus inStatusCode, const DNSResolverEvent *inEvent );

static void
	RegistrationCallBack( 
		void *							inContext, 
		DNSRegistrationRef				inRef, 
		DNSStatus						inStatusCode, 
		const DNSRegistrationEvent *	inEvent );

static void
	HostRegistrationCallBack( 
		void *					inContext, 
		DNSHostRegistrationRef 	inRef, 
		DNSStatus 				inStatusCode, 
		void *					inData );

static void
	EmulatedBrowserCallBack(
		DNSServiceBrowserReplyResultType	inResult, 
		const char *						inName,
		const char *						inType,
		const char *						inDomain,
		DNSServiceDiscoveryReplyFlags		inFlags,
		void *								inContext );

static void
	EmulatedDomainEnumerationCallBack(
		DNSServiceDomainEnumerationReplyResultType	inResult, 
		const char *								inDomain,
		DNSServiceDiscoveryReplyFlags				inFlags,
		void *										inContext );

static void
	EmulatedResolverCallBack(
		struct sockaddr *				inInterfaceAddr, 
		struct sockaddr *				inAddr,
		const char *					inTextRecord,
		DNSServiceDiscoveryReplyFlags	inFlags, 
		void *							inContext );

static void	EmulatedRegistrationCallBack( DNSServiceRegistrationReplyErrorType inResult, void *inContext );

static char *	IPv4ToString( DNSOpaque32 inIP, char *outString );

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

#if( defined( WINVER ) )
	static volatile int		gQuit = 0;
#endif

static int					gPrintTXTRecords = 1;

// Presets

typedef struct	PresetData	PresetData;
struct	PresetData
{
	int			argc;
	char *		argv[ 16 ];
};

#if 0
#pragma mark == Presets ==
#endif

static const PresetData		gPresets[] = 
{
	/* 01 */	{ 2, { "DNSServiceTest", "-bbd" } },
	/* 02 */	{ 4, { "DNSServiceTest", "-bs",  "_airport._tcp", 		"local."  } }, 
	/* 03 */	{ 4, { "DNSServiceTest", "-bs",  "_xserveraid._tcp", 	"local."  } }, 
	/* 04 */	{ 3, { "DNSServiceTest", "-rdb", "apple.com" } }, 
	/* 05 */	{ 7, { "DNSServiceTest", "-rs",  "My Fake AirPort", 	"_airport._tcp", 	"local.", 	"1234", "My Fake Info"  } }, 
	/* 06 */	{ 7, { "DNSServiceTest", "-rs",  "My Fake Xserve RAID", "_xserveraid._tcp", "local.", 	"1234", "My Fake Info"  } }, 
	/* 07 */	{ 7, { "DNSServiceTest", "-rs",  "My Fake Web Server", 	"_http._tcp", 		"local.",	"8080", "index.html"  } }, 
	/* 08 */	{ 9, { "DNSServiceTest", "-rps", "www.apple.com", "17.254.0.91", "Apple Web Server", "_http._tcp", "local.", "80", "index.html"  } }, 
};

const int 					gPresetsCount = sizeof( gPresets ) / sizeof( gPresets[ 0 ] );

#if 0
#pragma mark -
#endif

//===========================================================================================================================
//	main
//===========================================================================================================================

int main( int argc, char* argv[] )
{	
	DNSStatus		err;
	
	// Set up DNS Services and install a Console Control Handler to handle things like control-c signals.
	
	err = DNSServicesInitialize( kDNSFlagAdvertise, 0 );
	require_noerr_string( err, exit, "could not initialize DNSServiceTest" );

#if( __MACH__ )
	signal( SIGINT, SigIntHandler );
#endif

#if( defined( WINVER ) )
	SetConsoleCtrlHandler( ConsoleControlHandler, TRUE );
#endif

	ProcessArgs( argc, argv );
		
exit:
	DNSServicesFinalize();
	return( err );
}

//===========================================================================================================================
//	Usage
//===========================================================================================================================

static void	Usage( void )
{
	fprintf( stderr, "\n" );
	fprintf( stderr, "DNSServiceTest - DNS-SD Test Tool 1.0d1\n" );
	fprintf( stderr, "\n" );
	fprintf( stderr, "  -bbd                                                    'b'rowse for 'b'rowsing 'd'omains\n" );
	fprintf( stderr, "  -brd                                                    'b'rowse for 'r'egistration 'd'omains\n" );
	fprintf( stderr, "  -bs <type> <domain>                                     'b'rowse for 's'ervices\n" );
	fprintf( stderr, "  -lsi <name> <type> <domain>                             'l'ookup 's'ervice 'i'nstance\n" );
	fprintf( stderr, "  -rdb[d] <domain>                                        'r'egister 'd'omain for 'b'rowsing ['d'efault]\n" );
	fprintf( stderr, "  -rdr[d] <domain>                                        'r'egister 'd'omain for 'r'egistration ['d'efault]\n" );
	fprintf( stderr, "  -rs <name> <type> <domain> <port> <txt>                 'r'egister 's'ervice\n" );
	fprintf( stderr, "  -rps <host> <ip> <name> <type> <domain> <port> <txt>    'r'egister 'p'roxy 's'ervice\n" );
	fprintf( stderr, "  -rnss <name> <type> <domain>                            'r'egister 'n'o 's'uch 's'ervice\n" );
	
	fprintf( stderr, "  -ebs <type> <domain>                                    'e'mulated 'b'rowse for 's'ervices\n" );
	fprintf( stderr, "  -ebd <registration/browse>                              'e'mulated 'b'rowse for 'd'omains\n" );
	fprintf( stderr, "  -elsi <name> <type> <domain>                            'e'mulated 'l'ookup 's'ervice 'i'nstance\n" );
	fprintf( stderr, "  -ers <name> <type> <domain> <port> <txt>                'e'mulated 'r'egister 's'ervice\n" );
	
	fprintf( stderr, "  -h[elp]                                                 'h'elp\n" );
	fprintf( stderr, "\n" );
	
	fprintf( stderr, "  -1 Preset 1 (browse for browsing domains)    DNSServiceTest -bbd\n" );
	fprintf( stderr, "  -2 Preset 2 (browse for AirPort)             DNSServiceTest -bs \"_airport._tcp\" \"local.\"\n" );
	fprintf( stderr, "  -3 Preset 3 (browse for Xserve RAID)         DNSServiceTest -bs \"_xserveraid._tcp\" \"local.\"\n" );
	fprintf( stderr, "  -4 Preset 4 (register apple.com domain)      DNSServiceTest -rdb \"apple.com\"\n" );
	fprintf( stderr, "  -5 Preset 5 (register fake AirPort)          DNSServiceTest -rs \"My Fake AirPort\" \"_airport._tcp\" \"local.\" 1234 \"My Fake Info\"\n" );
	fprintf( stderr, "  -6 Preset 6 (register fake Xserve RAID)      DNSServiceTest -rs \"My Fake Xserve RAID\" \"_xserveraid._tcp\" \"local.\" 1234 \"My Fake Info\"\n" );	
	fprintf( stderr, "  -7 Preset 7 (register fake web server)       DNSServiceTest -rs \"My Fake Web Server\" \"_http._tcp\" \"local.\" 8080 \"index.html\"\n" );
	fprintf( stderr, "\n" );
}

//===========================================================================================================================
//	ProcessArgs
//===========================================================================================================================

static int ProcessArgs( int argc, char* argv[] )
{	
	DNSStatus						err;
	int								i;
	const char *					name;
	const char *					type;
	const char *					domain;
	uint16_t						port;
	const char *					text;
	size_t							textSize;
	DNSBrowserRef					browser;
	DNSResolverFlags				resolverFlags;
	DNSDomainRegistrationType		domainType;
	const char *					label;
	const char *					host;
	const char *					ip;
	unsigned int					b[ 4 ];
	DNSNetworkAddress				addr;
	dns_service_discovery_ref		emulatedRef;
	
	// Parse the command line arguments (ignore first argument since it's just the program name).
	
	require_action_string( argc >= 2, exit, err = kDNSBadParamErr, "no arguments specified" );
	
	for( i = 1; i < argc; ++i )
	{
		if( strcmp( argv[ i ], "-bbd" ) == 0 )
		{
			// 'b'rowse for 'b'rowsing 'd'omains
			
			fprintf( stdout, "browsing for browsing domains\n" );
			
			err = DNSBrowserCreate( 0, BrowserCallBack, NULL, &browser );
			require_noerr_string( err, exit, "create browser failed" );
			
			err = DNSBrowserStartDomainSearch( browser, 0 );
			require_noerr_string( err, exit, "start domain search failed" );
		}
		else if( strcmp( argv[ i ], "-brd" ) == 0 )
		{
			// 'b'rowse for 'r'egistration 'd'omains
			
			fprintf( stdout, "browsing for registration domains\n" );
			
			err = DNSBrowserCreate( 0, BrowserCallBack, NULL, &browser );
			require_noerr_string( err, exit, "create browser failed" );
			
			err = DNSBrowserStartDomainSearch( browser, kDNSBrowserFlagRegistrationDomainsOnly );
			require_noerr_string( err, exit, "start domain search failed" );
		}
		else if( strcmp( argv[ i ], "-bs" ) == 0 )
		{
			// 'b'rowse for 's'ervices <type> <domain>
						
			require_action_string( argc > ( i + 2 ), exit, err = kDNSBadParamErr, "missing arguments" );
			++i;
			type 	= argv[ i++ ];
			domain 	= argv[ i ];
			if( ( domain[ 0 ] == '\0' ) || ( ( domain[ 0 ] == '.' ) && ( domain[ 1 ] == '\0' ) ) )
			{
				domain = "local.";
			}
			fprintf( stdout, "browsing for \"%s.%s\"\n", type, domain );
			
			err = DNSBrowserCreate( 0, BrowserCallBack, NULL, &browser );
			require_noerr_string( err, exit, "create browser failed" );
			
			err = DNSBrowserStartServiceSearch( browser, kDNSBrowserFlagAutoResolve, type, domain );
			require_noerr_string( err, exit, "start service search failed" );
		}
		else if( strcmp( argv[ i ], "-lsi" ) == 0 )
		{
			// 'l'ookup 's'ervice 'i'nstance <name> <type> <domain>
			
			require_action_string( argc > ( i + 3 ), exit, err = kDNSBadParamErr, "missing arguments" );
			++i;
			name 	= argv[ i++ ];
			type 	= argv[ i++ ];
			domain 	= argv[ i ];
			if( ( domain[ 0 ] == '\0' ) || ( ( domain[ 0 ] == '.' ) && ( domain[ 1 ] == '\0' ) ) )
			{
				domain = "local.";
			}
			fprintf( stdout, "resolving \"%s.%s.%s\"\n", name, type, domain );
			
			resolverFlags = kDNSResolverFlagOnlyIfUnique | 
							kDNSResolverFlagAutoReleaseByName;
			err = DNSResolverCreate( resolverFlags, name, type, domain, ResolverCallBack, 0, NULL, NULL );
			require_noerr_string( err, exit, "create resolver failed" );
		}
		else if( ( strcmp( argv[ i ], "-rdb" ) == 0 ) || ( strcmp( argv[ i ], "-rdbd" ) == 0 ) )
		{
			// 'r'egister 'd'omain for 'b'rowsing ['d'efault] <domain>
						
			require_action_string( argc > ( i + 1 ), exit, err = kDNSBadParamErr, "missing arguments" );
			if( strcmp( argv[ i ], "-rdb" ) == 0 )
			{
				domainType = kDNSDomainRegistrationTypeBrowse;
				label = "";
			}
			else
			{
				domainType = kDNSDomainRegistrationTypeBrowseDefault;
				label = "default ";
			}
			++i;
			domain = argv[ i ];
			if( ( domain[ 0 ] == '\0' ) || ( ( domain[ 0 ] == '.' ) && ( domain[ 1 ] == '\0' ) ) )
			{
				domain = "local.";
			}
			fprintf( stdout, "registering \"%s\" as %sbrowse domain\n", domain, label );
			
			err = DNSDomainRegistrationCreate( 0, domain, domainType, NULL );
			require_noerr_string( err, exit, "create domain registration failed" );
		}
		else if( ( strcmp( argv[ i ], "-rdr" ) == 0 ) || ( strcmp( argv[ i ], "-rdrd" ) == 0 ) )
		{
			// 'r'egister 'd'omain for 'r'egistration ['d'efault] <domain>
			
			require_action_string( argc > ( i + 1 ), exit, err = kDNSBadParamErr, "missing arguments" );
			if( strcmp( argv[ i ], "-rdr" ) == 0 )
			{
				domainType = kDNSDomainRegistrationTypeRegistration;
				label = "";
			}
			else
			{
				domainType = kDNSDomainRegistrationTypeRegistrationDefault;
				label = "default ";
			}
			++i;
			domain = argv[ i ];
			if( ( domain[ 0 ] == '\0' ) || ( ( domain[ 0 ] == '.' ) && ( domain[ 1 ] == '\0' ) ) )
			{
				domain = "local.";
			}
			fprintf( stdout, "registering \"%s\" as %sregistration domain\n", domain, label );
			
			err = DNSDomainRegistrationCreate( 0, domain, domainType, NULL );
			require_noerr_string( err, exit, "create domain registration failed" );
		}
		else if( strcmp( argv[ i ], "-rs" ) == 0 )
		{
			// 'r'egister 's'ervice <name> <type> <domain> <port> <txt>
						
			require_action_string( argc > ( i + 5 ), exit, err = kDNSBadParamErr, "missing arguments" );
			++i;
			name 		= argv[ i++ ];
			type 		= argv[ i++ ];
			domain	 	= argv[ i++ ];
			port 		= (uint16_t) atoi( argv[ i++ ] );
			text 		= argv[ i ];
			textSize	= strlen( text );
			if( ( domain[ 0 ] == '\0' ) || ( ( domain[ 0 ] == '.' ) && ( domain[ 1 ] == '\0' ) ) )
			{
				domain = "local.";
			}
			fprintf( stdout, "registering service \"%s.%s.%s\" port %d text \"%s\"\n", name, type, domain, port, text );
			
			err = DNSRegistrationCreate( 0, name, type, domain, (DNSPort) port, text, (DNSCount) textSize, NULL, NULL, 
										 RegistrationCallBack, NULL, NULL );
			require_noerr_string( err, exit, "create registration failed" );
		}
		else if( strcmp( argv[ i ], "-rps" ) == 0 )
		{
			DNSHostRegistrationFlags		hostFlags;
			
			// 'r'egister 'p'roxy 's'ervice <host> <ip> <name> <type> <domain> <port> <txt>
						
			require_action_string( argc > ( i + 7 ), exit, err = kDNSBadParamErr, "missing arguments" );
			++i;
			host		= argv[ i++ ];
			ip			= argv[ i++ ];
			name 		= argv[ i++ ];
			type 		= argv[ i++ ];
			domain	 	= argv[ i++ ];
			port 		= (uint16_t) atoi( argv[ i++ ] );
			text 		= argv[ i ];
			textSize	= strlen( text );
			if( ( domain[ 0 ] == '\0' ) || ( ( domain[ 0 ] == '.' ) && ( domain[ 1 ] == '\0' ) ) )
			{
				domain = "local.";
			}
			
			sscanf( ip, "%u.%u.%u.%u", &b[ 0 ], &b[ 1 ], &b[ 2 ], &b[ 3 ] );
			addr.addressType 			= kDNSNetworkAddressTypeIPv4;
			addr.u.ipv4.addr.v8[ 0 ] 	= (DNSUInt8) b[ 0 ];
			addr.u.ipv4.addr.v8[ 1 ] 	= (DNSUInt8) b[ 1 ];
			addr.u.ipv4.addr.v8[ 2 ] 	= (DNSUInt8) b[ 2 ];
			addr.u.ipv4.addr.v8[ 3 ] 	= (DNSUInt8) b[ 3 ];
			
			fprintf( stdout, "registering proxy service \"%s.%s.%s\" port %d text \"%s\"\n", name, type, domain, port, text );
			
			hostFlags = kDNSHostRegistrationFlagOnlyIfNotFound | kDNSHostRegistrationFlagAutoRenameOnConflict;
			err = DNSHostRegistrationCreate( hostFlags, host, domain, &addr, NULL, 
											 HostRegistrationCallBack, NULL, NULL );
			require_noerr_string( err, exit, "create host registration failed" );
			
			err = DNSRegistrationCreate( 0, name, type, domain, (DNSPort) port, text, (DNSCount) textSize, host, NULL, 
										 RegistrationCallBack, NULL, NULL );
			require_noerr_string( err, exit, "create registration failed" );			
		}
		else if( strcmp( argv[ i ], "-rnss" ) == 0 )
		{
			// 'r'egister 'n'o 's'uch 's'ervice <name> <type> <domain>
						
			require_action_string( argc > ( i + 3 ), exit, err = kDNSBadParamErr, "missing arguments" );
			++i;
			name 		= argv[ i++ ];
			type 		= argv[ i++ ];
			domain	 	= argv[ i++ ];
			if( ( domain[ 0 ] == '\0' ) || ( ( domain[ 0 ] == '.' ) && ( domain[ 1 ] == '\0' ) ) )
			{
				domain = "local.";
			}
			fprintf( stdout, "registering no-such-service \"%s.%s.%s\"\n", name, type, domain );
			
			err = DNSNoSuchServiceRegistrationCreate( 0, name, type, domain, NULL, RegistrationCallBack, NULL, NULL );
			require_noerr_string( err, exit, "create no-such-service registration failed" );
		}
		else if( strcmp( argv[ i ], "-ebs" ) == 0 )
		{
			// 'e'mulated 'b'rowse for 's'ervices <type> <domain>
						
			require_action_string( argc > ( i + 2 ), exit, err = kDNSBadParamErr, "missing arguments" );
			++i;
			type 	= argv[ i++ ];
			domain 	= argv[ i ];
			if( ( domain[ 0 ] == '\0' ) || ( ( domain[ 0 ] == '.' ) && ( domain[ 1 ] == '\0' ) ) )
			{
				domain = "local.";
			}
			fprintf( stdout, "emulated browsing for \"%s.%s\"\n", type, domain );
			
			emulatedRef = DNSServiceBrowserCreate( type, domain, EmulatedBrowserCallBack, NULL );
			require_action_string( emulatedRef, exit, err = kDNSUnknownErr, "create emulated browser failed" );
		}
		else if( strcmp( argv[ i ], "-ebd" ) == 0 )
		{
			int		registrationOnly;
			
			// 'e'mulated 'b'rowse for 'd'omains <registration/browse>
			
			require_action_string( argc > ( i + 1 ), exit, err = kDNSBadParamErr, "missing arguments" );
			++i;
			type = argv[ i++ ];
			if( strcmp( type, "registration" ) == 0 )
			{
				registrationOnly = 1;
			}
			else if( strcmp( type, "browse" ) == 0 )
			{
				registrationOnly = 0;
			}
			else
			{
				require_action_string( 0, exit, err = kDNSBadParamErr, "invalid browse type" );
			}
			fprintf( stdout, "emulated browsing for %s domains\n", type );
			
			emulatedRef = DNSServiceDomainEnumerationCreate( registrationOnly, EmulatedDomainEnumerationCallBack, NULL );
			require_action_string( emulatedRef, exit, err = kDNSUnknownErr, "create emulated domain browser failed" );
		}
		else if( strcmp( argv[ i ], "-elsi" ) == 0 )
		{
			// 'e'mulated 'l'ookup 's'ervice 'i'nstance <name> <type> <domain>
			
			require_action_string( argc > ( i + 3 ), exit, err = kDNSBadParamErr, "missing arguments" );
			++i;
			name 	= argv[ i++ ];
			type 	= argv[ i++ ];
			domain 	= argv[ i ];
			if( ( domain[ 0 ] == '\0' ) || ( ( domain[ 0 ] == '.' ) && ( domain[ 1 ] == '\0' ) ) )
			{
				domain = "local.";
			}
			fprintf( stdout, "emulated resolving \"%s.%s.%s\"\n", name, type, domain );
			
			emulatedRef = DNSServiceResolverResolve( name, type, domain, EmulatedResolverCallBack, NULL );
			require_action_string( emulatedRef, exit, err = kDNSUnknownErr, "create emulated resolver failed" );
		}
		else if( strcmp( argv[ i ], "-ers" ) == 0 )
		{
			// 'e'mulated 'r'egister 's'ervice <name> <type> <domain> <port> <txt>
						
			require_action_string( argc > ( i + 5 ), exit, err = kDNSBadParamErr, "missing arguments" );
			++i;
			name 		= argv[ i++ ];
			type 		= argv[ i++ ];
			domain	 	= argv[ i++ ];
			port 		= (uint16_t) atoi( argv[ i++ ] );
			text 		= argv[ i ];
			textSize	= strlen( text );
			if( ( domain[ 0 ] == '\0' ) || ( ( domain[ 0 ] == '.' ) && ( domain[ 1 ] == '\0' ) ) )
			{
				domain = "local.";
			}
			fprintf( stdout, "registering service \"%s.%s.%s\" port %d text \"%s\"\n", name, type, domain, port, text );
			
			emulatedRef = DNSServiceRegistrationCreate( name, type, domain, htons( port ), text, 
														EmulatedRegistrationCallBack, NULL );
			require_action_string( emulatedRef, exit, err = kDNSUnknownErr, "create emulated registration failed" );
		}
		else if( ( argv[ i ][ 0 ] == '-' ) && isdigit( argv[ i ][ 1 ] ) )
		{
			// Preset
			
			ProcessPreset( atoi( &argv[ i ][ 1 ] ) );
			err = 0;
			goto exit;
		}
		else if( strcmp( argv[ i ], "-q" ) == 0 )
		{
			// Quiet (no text records)
			
			gPrintTXTRecords = 0;
		}
		else if( ( strcmp( argv[ i ], "-help" ) == 0 ) || ( strcmp( argv[ i ], "-h" ) == 0 ) )
		{
			// Help
			
			Usage();
			err = 0;
			goto exit;
		}
		else
		{
			// Unknown parameter.
			
			require_action_string( 0, exit, err = kDNSBadParamErr, "unknown parameter" );
			goto exit;
		}
	}
	
	// Run until control-C'd.
	
	#if( __MACH__ )
		CFRunLoopRun();
	#endif
	
	#if( defined( WINVER ) )
		while( !gQuit )
		{
			Sleep( 200 );
		}
	#endif
	
	err = kDNSNoErr;
	
exit:
	if( err )
	{
		Usage();
	}
	return( err );
}

//===========================================================================================================================
//	ProcessPreset
//===========================================================================================================================

static DNSStatus	ProcessPreset( int inPreset )
{
	DNSStatus		err;
	
	require_action_string( ( inPreset > 0 ) && ( inPreset <= gPresetsCount ), exit, err = kDNSBadParamErr, "invalid preset" );
	
	err = ProcessArgs( gPresets[ inPreset - 1 ].argc, (char **) gPresets[ inPreset - 1 ].argv );
	
exit:
	return( err );
}

#if( __MACH__ )
//===========================================================================================================================
//	SigIntHandler
//===========================================================================================================================

static void	SigIntHandler( int inSignalNumber )
{
	DNS_UNUSED( inSignalNumber );
	
	signal( SIGINT, SIG_DFL );
	CFRunLoopStop( CFRunLoopGetCurrent() );
}
#endif

#if( defined( WINVER ) )
//===========================================================================================================================
//	ConsoleControlHandler
//===========================================================================================================================

static BOOL WINAPI	ConsoleControlHandler( DWORD inControlEvent )
{
	BOOL		handled;
	
	handled = 0;
	switch( inControlEvent )
	{
		case CTRL_C_EVENT:
		case CTRL_BREAK_EVENT:
		case CTRL_CLOSE_EVENT:
		case CTRL_LOGOFF_EVENT:
		case CTRL_SHUTDOWN_EVENT:
			gQuit = 1;
			handled = 1;
			break;
		
		default:
			break;
	}
	return( handled );
}
#endif

//===========================================================================================================================
//	BrowserCallBack
//===========================================================================================================================

static void BrowserCallBack( void *inContext, DNSBrowserRef inRef, DNSStatus inStatusCode, const DNSBrowserEvent *inEvent )
{
	char		ifIP[ 32 ];
	char		ip[ 32 ];

	DNS_UNUSED( inContext );
	DNS_UNUSED( inRef );
	DNS_UNUSED( inStatusCode );
	
	switch( inEvent->type )
	{
		case kDNSBrowserEventTypeRelease:
			break;
			
		case kDNSBrowserEventTypeAddDomain:			
			fprintf( stdout, "domain         \"%s\" added on interface 0x%p (%s)\n", 
					 inEvent->data.addDomain.domain, 
					 (int) inEvent->data.addDomain.interfaceID, 
					 IPv4ToString( inEvent->data.addDomain.interfaceIP.u.ipv4.addr, ifIP ) );
			break;
		
		case kDNSBrowserEventTypeAddDefaultDomain:
			fprintf( stdout, "default domain \"%s\" added on interface 0x%p (%s)\n", 
					 inEvent->data.addDefaultDomain.domain, 
					 (int) inEvent->data.addDefaultDomain.interfaceID, 
					 IPv4ToString( inEvent->data.addDefaultDomain.interfaceIP.u.ipv4.addr, ifIP ) );
			break;
		
		case kDNSBrowserEventTypeRemoveDomain:
			fprintf( stdout, "domain         \"%s\" removed on interface 0x%p (%s)\n", 
					 inEvent->data.removeDomain.domain, 
					 (int) inEvent->data.removeDomain.interfaceID, 
					 IPv4ToString( inEvent->data.removeDomain.interfaceIP.u.ipv4.addr, ifIP ) );
			break;
		
		case kDNSBrowserEventTypeAddService:
			fprintf( stdout, "service        \"%s.%s%s\" added on interface 0x%p (%s)\n", 
					 inEvent->data.addService.name, 
					 inEvent->data.addService.type, 
					 inEvent->data.addService.domain, 
					 (int) inEvent->data.addService.interfaceID, 
					 IPv4ToString( inEvent->data.addService.interfaceIP.u.ipv4.addr, ifIP ) );
			break;
		
		case kDNSBrowserEventTypeRemoveService:
			fprintf( stdout, "service        \"%s.%s%s\" removed on interface 0x%p (%s)\n", 
					 inEvent->data.removeService.name, 
					 inEvent->data.removeService.type, 
					 inEvent->data.removeService.domain, 
					 (int) inEvent->data.removeService.interfaceID, 
					 IPv4ToString( inEvent->data.removeService.interfaceIP.u.ipv4.addr, ifIP ) );
			break;
		
		case kDNSBrowserEventTypeResolved:
		{
			const uint8_t *		p;
			const uint8_t *		end;
			int					i;
			
			fprintf( stdout, "resolved       \"%s.%s%s\" to \"%s\" (%s:%u) on interface 0x%p (%s)%s\n", 
					 inEvent->data.resolved->name, 
					 inEvent->data.resolved->type, 
					 inEvent->data.resolved->domain, 
					 inEvent->data.resolved->hostName, 
					 IPv4ToString( inEvent->data.resolved->address.u.ipv4.addr, ip ), 
					 ( inEvent->data.resolved->address.u.ipv4.port.v8[ 0 ] << 8 ) | 
					   inEvent->data.resolved->address.u.ipv4.port.v8[ 1 ], 
					 (int) inEvent->data.resolved->interfaceID, 
					 IPv4ToString( inEvent->data.resolved->interfaceIP.u.ipv4.addr, ifIP ), 
					 ( inEvent->data.resolved->textRecordRawSize > 0 ) ? " with text:" : "" );
			
			p 	= (const uint8_t *) inEvent->data.resolved->textRecordRaw;
			end = p + inEvent->data.resolved->textRecordRawSize;
			i 	= 0;
			
			if( gPrintTXTRecords )
			{
				while( p < end )
				{
					uint8_t		size;
						
					size = *p++;
					if( ( p + size ) > end )
					{
						fprintf( stdout, "\n### MALFORMED TXT RECORD (length byte too big for record)\n\n" );
						break;
					}
					fprintf( stdout, "%5d (%3d bytes): \"%.*s\"\n", i, size, size, p );
					p += size;
					++i;
				}
				fprintf( stdout, "\n" );
			}
			break;
		}
		
		default:
			break;
	}
}

//===========================================================================================================================
//	ResolverCallBack
//===========================================================================================================================

static void ResolverCallBack( void *inContext, DNSResolverRef inRef, DNSStatus inStatusCode, const DNSResolverEvent *inEvent )
{
	char		ifIP[ 32 ];
	char		ip[ 32 ];

	DNS_UNUSED( inContext );
	DNS_UNUSED( inRef );
	DNS_UNUSED( inStatusCode );

	switch( inEvent->type )
	{
		case kDNSResolverEventTypeResolved:
		{
			const uint8_t *		p;
			const uint8_t *		end;
			int					i;
			
			fprintf( stdout, "resolved       \"%s.%s%s\" to \"%s\" (%s:%u) on interface 0x%p (%s)%s\n", 
					 inEvent->data.resolved.name, 
					 inEvent->data.resolved.type, 
					 inEvent->data.resolved.domain, 
					 inEvent->data.resolved.hostName, 
					 IPv4ToString( inEvent->data.resolved.address.u.ipv4.addr, ip ), 
					 ( inEvent->data.resolved.address.u.ipv4.port.v8[ 0 ] << 8 ) | 
					   inEvent->data.resolved.address.u.ipv4.port.v8[ 1 ], 
					 (int) inEvent->data.resolved.interfaceID, 
					 IPv4ToString( inEvent->data.resolved.interfaceIP.u.ipv4.addr, ifIP ), 
					 ( inEvent->data.resolved.textRecordRawSize > 0 ) ? " with text:" : "" );
			
			p 	= (const uint8_t *) inEvent->data.resolved.textRecordRaw;
			end = p + inEvent->data.resolved.textRecordRawSize;
			i 	= 0;
			
			if( gPrintTXTRecords )
			{
				while( p < end )
				{
					uint8_t		size;
					
					size = *p++;
					if( ( p + size ) > end )
					{
						fprintf( stdout, "\n### MALFORMED TXT RECORD (length byte too big for record)\n\n" );
						break;
					}
					fprintf( stdout, "%5d (%3d bytes): \"%.*s\"\n", i, size, size, p );
					p += size;
					++i;
				}
				fprintf( stdout, "\n" );
			}
			break;
		}

		case kDNSResolverEventTypeRelease:
			break;
		
		default:
			break;
	}
}

//===========================================================================================================================
//	RegistrationCallBack
//===========================================================================================================================

static void
	RegistrationCallBack( 
		void *							inContext, 
		DNSRegistrationRef				inRef, 
		DNSStatus						inStatusCode, 
		const DNSRegistrationEvent *	inEvent )
{
	DNS_UNUSED( inContext );
	DNS_UNUSED( inRef );
	DNS_UNUSED( inStatusCode );
	
	switch( inEvent->type )
	{
		case kDNSRegistrationEventTypeRelease:	
			break;
		
		case kDNSRegistrationEventTypeRegistered:
			fprintf( stdout, "name registered and active\n" );
			break;

		case kDNSRegistrationEventTypeNameCollision:
			fprintf( stdout, "name in use, please choose another name\n" );
			break;
		
		default:
			break;
	}
}

//===========================================================================================================================
//	HostRegistrationCallBack
//===========================================================================================================================

static void
	HostRegistrationCallBack( 
		void *					inContext, 
		DNSHostRegistrationRef 	inRef, 
		DNSStatus 				inStatusCode, 
		void *					inData )
{
	DNS_UNUSED( inContext );
	DNS_UNUSED( inRef );
	DNS_UNUSED( inData );
	
	if( inStatusCode == kDNSNoErr )
	{
		fprintf( stdout, "host name registered and active\n" );
	}
	else if( inStatusCode == kDNSNameConflictErr )
	{
		fprintf( stdout, "host name in use, please choose another name\n" );
	}
	else
	{
		fprintf( stdout, "unknown host registration status (%ld)\n", inStatusCode );
	}
}

//===========================================================================================================================
//	EmulatedBrowserCallBack
//===========================================================================================================================

static void
	EmulatedBrowserCallBack(
		DNSServiceBrowserReplyResultType	inResult, 
		const char *						inName,
		const char *						inType,
		const char *						inDomain,
		DNSServiceDiscoveryReplyFlags		inFlags,
		void *								inContext )
{
	DNS_UNUSED( inFlags );
	DNS_UNUSED( inContext );
	
	if( inResult == DNSServiceBrowserReplyAddInstance )
	{
		fprintf( stdout, "\"%s.%s%s\" service added emulated\n", inName, inType, inDomain );
	}
	else if( inResult == DNSServiceBrowserReplyRemoveInstance )
	{
		fprintf( stdout, "\"%s.%s%s\" service removed emulated\n", inName, inType, inDomain );
	}
	else
	{
		fprintf( stdout, "### unknown emulated browser callback result (%d)\n", inResult );
	}
}

//===========================================================================================================================
//	EmulatedDomainEnumerationCallBack
//===========================================================================================================================

static void
	EmulatedDomainEnumerationCallBack(
		DNSServiceDomainEnumerationReplyResultType	inResult, 
		const char *								inDomain,
		DNSServiceDiscoveryReplyFlags				inFlags,
		void *										inContext )
{
	DNS_UNUSED( inFlags );
	DNS_UNUSED( inContext );
	
	if( inResult == DNSServiceDomainEnumerationReplyAddDomain )
	{
		fprintf( stdout, "\"%s\" domain added emulated\n", inDomain );
	}
	else if( inResult == DNSServiceDomainEnumerationReplyAddDomainDefault )
	{
		fprintf( stdout, "\"%s\" default domain added emulated\n", inDomain );
	}
	else if( inResult == DNSServiceDomainEnumerationReplyRemoveDomain )
	{
		fprintf( stdout, "\"%s\" domain removed emulated\n", inDomain );
	}
	else
	{
		fprintf( stdout, "### unknown emulated domain enumeration callback result (%d)\n", inResult );
	}
}

//===========================================================================================================================
//	EmulatedResolverCallBack
//===========================================================================================================================

static void
	EmulatedResolverCallBack(
		struct sockaddr *				inInterfaceAddr, 
		struct sockaddr *				inAddr,
		const char *					inTextRecord,
		DNSServiceDiscoveryReplyFlags	inFlags, 
		void *							inContext )
{
	struct sockaddr_in *		ifSin4;
	struct sockaddr_in *		sin4;
	char						ifIP[ 64 ];
	char						ip[ 64 ];
	
	DNS_UNUSED( inFlags );
	DNS_UNUSED( inContext );
	
	ifSin4 	= (struct sockaddr_in *) inInterfaceAddr;
	sin4 	= (struct sockaddr_in *) inAddr;

	fprintf( stdout, "service resolved to %s:%d on interface %s with text \"%s\"\n", 
			 IPv4ToString( *( (DNSOpaque32 *) &sin4->sin_addr.s_addr ), ip ), 
			 ntohs( sin4->sin_port ), 
			 IPv4ToString( *( (DNSOpaque32 *) &ifSin4->sin_addr.s_addr ), ifIP ), 
			 inTextRecord ? inTextRecord : "" );
}

//===========================================================================================================================
//	EmulatedResolverCallBack
//===========================================================================================================================

static void	EmulatedRegistrationCallBack( DNSServiceRegistrationReplyErrorType inResult, void *inContext )
{
	DNS_UNUSED( inContext );
	
	if( inResult == kDNSServiceDiscoveryNoError )
	{
		fprintf( stdout, "service name registered successfully\n" );
	}
	else
	{
		fprintf( stdout, "service registration failed( %d)\n", inResult );
	}
}

//===========================================================================================================================
//	IPv4ToString
//===========================================================================================================================

static char *	IPv4ToString( DNSOpaque32 inIP, char *outString )
{
	sprintf( outString, "%u.%u.%u.%u", inIP.v8[ 0 ], inIP.v8[ 1 ], inIP.v8[ 2 ], inIP.v8[ 3 ] );
	return( outString );
}
