text: /*
  	Copyright (c) 2016-2017 Apple Inc. All rights reserved.
  	
  	dnssdutil is a command-line utility for testing the DNS-SD API.
  */
  
  #include <CoreUtils/CommonServices.h>	// Include early.
  #include <CoreUtils/AsyncConnection.h>
  #include <CoreUtils/CommandLineUtils.h>
  #include <CoreUtils/DataBufferUtils.h>
  #include <CoreUtils/DebugServices.h>
  #include <CoreUtils/MiscUtils.h>
  #include <CoreUtils/NetUtils.h>
  #include <CoreUtils/PrintFUtils.h>
  #include <CoreUtils/RandomNumberUtils.h>
  #include <CoreUtils/StringUtils.h>
  #include <CoreUtils/TickUtils.h>
  #include <dns_sd.h>
  #include <dns_sd_private.h>
  
  #if( TARGET_OS_DARWIN )
  	#include <dnsinfo.h>
  	#include <libproc.h>
  	#include <sys/proc_info.h>
  #endif
  
  #if( TARGET_OS_POSIX )
  	#include <sys/resource.h>
  #endif
  
  #if( DNSSDUTIL_INCLUDE_DNSCRYPT )
  	#include "tweetnacl.h"	// TweetNaCl from <https://tweetnacl.cr.yp.to/software.html>.
  #endif
  
  //===========================================================================================================================
  //	Global Constants
  //===========================================================================================================================
  
  // Versioning
  
  #define kDNSSDUtilNumVersion	NumVersionBuild( 2, 0, 0, kVersionStageBeta, 0 )
  
  #if( !MDNSRESPONDER_PROJECT && !defined( DNSSDUTIL_SOURCE_VERSION ) )
  	#define DNSSDUTIL_SOURCE_VERSION	"0.0.0"
  #endif
  
  // DNS-SD API flag descriptors
  
  #define kDNSServiceFlagsDescriptors		\
  	"\x00" "AutoTrigger\0"				\
  	"\x01" "Add\0"						\
  	"\x02" "Default\0"					\
  	"\x03" "NoAutoRename\0"				\
  	"\x04" "Shared\0"					\
  	"\x05" "Unique\0"					\
  	"\x06" "BrowseDomains\0"			\
  	"\x07" "RegistrationDomains\0"		\
  	"\x08" "LongLivedQuery\0"			\
  	"\x09" "AllowRemoteQuery\0"			\
  	"\x0A" "ForceMulticast\0"			\
  	"\x0B" "KnownUnique\0"				\
  	"\x0C" "ReturnIntermediates\0"		\
  	"\x0D" "NonBrowsable\0"				\
  	"\x0E" "ShareConnection\0"			\
  	"\x0F" "SuppressUnusable\0"			\
  	"\x10" "Timeout\0"					\
  	"\x11" "IncludeP2P\0"				\
  	"\x12" "WakeOnResolve\0"			\
  	"\x13" "BackgroundTrafficClass\0"	\
  	"\x14" "IncludeAWDL\0"				\
  	"\x15" "Validate\0"					\
  	"\x16" "UnicastResponse\0"			\
  	"\x17" "ValidateOptional\0"			\
  	"\x18" "WakeOnlyService\0"			\
  	"\x19" "ThresholdOne\0"				\
  	"\x1A" "ThresholdFinder\0"			\
  	"\x1B" "DenyCellular\0"				\
  	"\x1C" "ServiceIndex\0"				\
  	"\x1D" "DenyExpensive\0"			\
  	"\x1E" "PathEvaluationDone\0"		\
  	"\x00"
  
  #define kDNSServiceProtocolDescriptors	\
  	"\x00" "IPv4\0"						\
  	"\x01" "IPv6\0"						\
  	"\x00"
  
  // (m)DNS
  
  #define kDNSHeaderFlag_Response					( 1 << 15 )
  #define kDNSHeaderFlag_AuthAnswer				( 1 << 10 )
  #define kDNSHeaderFlag_Truncation				( 1 <<  9 )
  #define kDNSHeaderFlag_RecursionDesired			( 1 <<  8 )
  #define kDNSHeaderFlag_RecursionAvailable		( 1 <<  7 )
  
  #define kDNSOpCode_Query			0
  #define kDNSOpCode_InverseQuery		1
  #define kDNSOpCode_Status			2
  #define kDNSOpCode_Notify			4
  #define kDNSOpCode_Update			5
  
  #define kDNSRCode_NoError				0
  #define kDNSRCode_FormatError			1
  #define kDNSRCode_ServerFailure			2
  #define kDNSRCode_NXDomain				3
  #define kDNSRCode_NotImplemented		4
  #define kDNSRCode_Refused				5
  
  #define kQClassUnicastResponseBit	( 1U << 15 )
  #define kRRClassCacheFlushBit		( 1U << 15 )
  
  #define kDomainLabelLengthMax		63
  #define kDomainNameLengthMax		256
  
  //===========================================================================================================================
  //	Gerneral Command Options
  //===========================================================================================================================
  
  // Command option macros
  
  #define Command( NAME, CALLBACK, SUB_OPTIONS, SHORT_HELP, IS_NOTCOMMON )											\
  	CLI_COMMAND_EX( NAME, CALLBACK, SUB_OPTIONS, (IS_NOTCOMMON) ? kCLIOptionFlags_NotCommon : kCLIOptionFlags_None,	\
  		(SHORT_HELP), NULL )
  
  #define kRequiredOptionSuffix		" [REQUIRED]"
  
  #define MultiStringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, LONG_HELP )	\
  	CLI_OPTION_MULTI_STRING_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP,									\
  		(IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP,														\
  		(IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, LONG_HELP )
  
  #define MultiStringOption( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \
  		MultiStringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, NULL )
  
  #define IntegerOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED )	\
  	CLI_OPTION_INTEGER_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP,						\
  		(IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP,						\
  		(IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, NULL )
  
  #define BooleanOption( SHORT_CHAR, LONG_NAME, VAL_PTR, SHORT_HELP ) \
  	CLI_OPTION_BOOLEAN( (SHORT_CHAR), (LONG_NAME), (VAL_PTR), (SHORT_HELP), NULL )
  
  #define StringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, LONG_HELP )	\
  	CLI_OPTION_STRING_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP,										\
  		(IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP,									\
  		(IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, LONG_HELP )
  
  #define StringOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \
  	StringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, NULL )
  
  // DNS-SD API flag options
  
  static int		gDNSSDFlags						= 0;
  static int		gDNSSDFlag_BrowseDomains		= false;
  static int		gDNSSDFlag_DenyCellular			= false;
  static int		gDNSSDFlag_DenyExpensive		= false;
  static int		gDNSSDFlag_ForceMulticast		= false;
  static int		gDNSSDFlag_IncludeAWDL			= false;
  static int		gDNSSDFlag_NoAutoRename			= false;
  static int		gDNSSDFlag_PathEvaluationDone	= false;
  static int		gDNSSDFlag_RegistrationDomains	= false;
  static int		gDNSSDFlag_ReturnIntermediates	= false;
  static int		gDNSSDFlag_Shared				= false;
  static int		gDNSSDFlag_SuppressUnusable		= false;
  static int		gDNSSDFlag_Timeout				= false;
  static int		gDNSSDFlag_UnicastResponse		= false;
  static int		gDNSSDFlag_Unique				= false;
  
  #define DNSSDFlagsOption()								\
  	IntegerOption( 'f', "flags", &gDNSSDFlags, "flags",	\
  		"DNSServiceFlags to use. This value is bitwise ORed with other single flag options.", false )
  
  #define DNSSDFlagOption( SHORT_CHAR, FLAG_NAME )									\
  	BooleanOption( SHORT_CHAR, Stringify( FLAG_NAME ), &gDNSSDFlag_ ## FLAG_NAME,	\
  		"Use kDNSServiceFlags" Stringify( FLAG_NAME ) "." )
  
  #define DNSSDFlagsOption_DenyCellular()			DNSSDFlagOption( 'C', DenyCellular )
  #define DNSSDFlagsOption_DenyExpensive()		DNSSDFlagOption( 'E', DenyExpensive )
  #define DNSSDFlagsOption_ForceMulticast()		DNSSDFlagOption( 'M', ForceMulticast )
  #define DNSSDFlagsOption_IncludeAWDL()			DNSSDFlagOption( 'A', IncludeAWDL )
  #define DNSSDFlagsOption_NoAutoRename()			DNSSDFlagOption( 'N', NoAutoRename )
  #define DNSSDFlagsOption_PathEvalDone()			DNSSDFlagOption( 'P', PathEvaluationDone )
  #define DNSSDFlagsOption_ReturnIntermediates()	DNSSDFlagOption( 'I', ReturnIntermediates )
  #define DNSSDFlagsOption_Shared()				DNSSDFlagOption( 'S', Shared )
  #define DNSSDFlagsOption_SuppressUnusable()		DNSSDFlagOption( 'S', SuppressUnusable )
  #define DNSSDFlagsOption_Timeout()				DNSSDFlagOption( 'T', Timeout )
  #define DNSSDFlagsOption_UnicastResponse()		DNSSDFlagOption( 'U', UnicastResponse )
  #define DNSSDFlagsOption_Unique()				DNSSDFlagOption( 'U', Unique )
  
  // Interface option
  
  static const char *		gInterface = NULL;
  
  #define InterfaceOption()										\
  	StringOption( 'i', "interface", &gInterface, "interface",	\
  		"Network interface by name or index. Use index -1 for local-only.", false )
  
  // Connection options
  
  #define kConnectionArg_Normal			""
  #define kConnectionArgPrefix_PID		"pid:"
  #define kConnectionArgPrefix_UUID		"uuid:"
  
  static const char *		gConnectionOpt = kConnectionArg_Normal;
  
  #define ConnectionOptions()																						\
  	{ kCLIOptionType_String, 0, "connection", &gConnectionOpt, NULL, (intptr_t) kConnectionArg_Normal, "type",	\
  		kCLIOptionFlags_OptionalArgument, NULL, NULL, NULL, NULL,												\
  		"Specifies the type of main connection to use. See " kConnectionSection_Name " below.", NULL }
  
  #define kConnectionSection_Name		"Connection Option"
  #define kConnectionSection_Text																							\
  	"The default behavior is to create a main connection with DNSServiceCreateConnection() and perform operations on\n"	\
  	"the main connection using the kDNSServiceFlagsShareConnection flag. This behavior can be explicitly invoked by\n"	\
  	"specifying the connection option without an argument, i.e.,\n"														\
  	"\n"																												\
  	"    --connection\n"																								\
  	"\n"																												\
  	"To instead use a delegate connection created with DNSServiceCreateDelegateConnection(), use\n"						\
  	"\n"																												\
  	"    --connection=pid:<PID>\n"																						\
  	"\n"																												\
  	"to specify the delegator by PID, or use\n"																			\
  	"\n"																												\
  	"    --connection=uuid:<UUID>\n"																					\
  	"\n"																												\
  	"to specify the delegator by UUID.\n"																				\
  	"\n"																												\
  	"To not use a main connection at all, but instead perform operations on their own connections, use\n"				\
  	"\n"																												\
  	"    --no-connection\n"
  
  #define ConnectionSection()		CLI_SECTION( kConnectionSection_Name, kConnectionSection_Text )
  
  // Help text for record data options
  
  #define kRDataArgPrefix_File			"file:"
  #define kRDataArgPrefix_HexString		"hex:"
  #define kRDataArgPrefix_String			"string:"
  #define kRDataArgPrefix_TXT				"txt:"
  
  #define kRecordDataSection_Name		"Record Data Arguments"
  #define kRecordDataSection_Text																							\
  	"A record data argument is specified in one of the following formats:\n"											\
  	"\n"																												\
  	"Format                           Syntax                                 Example\n"									\
  	"String                           string:<string>                        string:'\\x09color=red'\n"					\
  	"Hexadecimal string               hex:<hex string>                       hex:c0a80101 or hex:'C0 A8 01 01'\n"		\
  	"TXT record keys and values       txt:<comma-delimited keys and values>  txt:'key1=x,key2=y\\,z,key3'\n"			\
  	"File containing raw record data  file:<file path>                       file:dir/record_data.bin\n"
  
  #define RecordDataSection()		CLI_SECTION( kRecordDataSection_Name, kRecordDataSection_Text )
  
  //===========================================================================================================================
  //	Browse Command Options
  //===========================================================================================================================
  
  static char **			gBrowse_ServiceTypes		= NULL;
  static size_t			gBrowse_ServiceTypesCount	= 0;
  static const char *		gBrowse_Domain				= NULL;
  static int				gBrowse_DoResolve			= false;
  static int				gBrowse_QueryTXT			= false;
  static int				gBrowse_TimeLimitSecs		= 0;
  
  static CLIOption		kBrowseOpts[] =
  {
  	InterfaceOption(),
  	MultiStringOption(	't', "type",	&gBrowse_ServiceTypes, &gBrowse_ServiceTypesCount, "service type", "Service type(s), e.g., \"_ssh._tcp\".", true ),
  	StringOption(		'd', "domain",	&gBrowse_Domain, "domain", "Domain in which to browse for the service type(s).", false ),
  	
  	CLI_OPTION_GROUP( "Flags" ),
  	DNSSDFlagsOption(),
  	DNSSDFlagsOption_IncludeAWDL(),
  	
  	CLI_OPTION_GROUP( "Operation" ),
  	ConnectionOptions(),
  	BooleanOption(  0 , "resolve",		&gBrowse_DoResolve,		"Resolve service instances." ),
  	BooleanOption(  0 , "queryTXT",		&gBrowse_QueryTXT,		"Query TXT records of service instances." ),
  	IntegerOption( 'l', "timeLimit",	&gBrowse_TimeLimitSecs,	"seconds", "Specifies the max duration of the browse operation. Use '0' for no time limit.", false ),
  	
  	ConnectionSection(),
  	CLI_OPTION_END()
  };
  
  //===========================================================================================================================
  //	GetAddrInfo Command Options
  //===========================================================================================================================
  
  static const char *		gGetAddrInfo_Name			= NULL;
  static int				gGetAddrInfo_ProtocolIPv4	= false;
  static int				gGetAddrInfo_ProtocolIPv6	= false;
  static int				gGetAddrInfo_OneShot		= false;
  static int				gGetAddrInfo_TimeLimitSecs	= 0;
  
  static CLIOption		kGetAddrInfoOpts[] =
  {
  	InterfaceOption(),
  	StringOption(  'n', "name", &gGetAddrInfo_Name,			"domain name", "Domain name to resolve.", true ),
  	BooleanOption(  0 , "ipv4", &gGetAddrInfo_ProtocolIPv4,	"Use kDNSServiceProtocol_IPv4." ),
  	BooleanOption(  0 , "ipv6", &gGetAddrInfo_ProtocolIPv6,	"Use kDNSServiceProtocol_IPv6." ),
  	
  	CLI_OPTION_GROUP( "Flags" ),
  	DNSSDFlagsOption(),
  	DNSSDFlagsOption_DenyCellular(),
  	DNSSDFlagsOption_DenyExpensive(),
  	DNSSDFlagsOption_PathEvalDone(),
  	DNSSDFlagsOption_ReturnIntermediates(),
  	DNSSDFlagsOption_SuppressUnusable(),
  	DNSSDFlagsOption_Timeout(),
  	
  	CLI_OPTION_GROUP( "Operation" ),
  	ConnectionOptions(),
  	BooleanOption( 'o', "oneshot",		&gGetAddrInfo_OneShot,			"Finish after first set of results." ),
  	IntegerOption( 'l', "timeLimit",	&gGetAddrInfo_TimeLimitSecs,	"seconds", "Maximum duration of the GetAddrInfo operation. Use '0' for no time limit.", false ),
  	
  	ConnectionSection(),
  	CLI_OPTION_END()
  };
  
  //===========================================================================================================================
  //	QueryRecord Command Options
  //===========================================================================================================================
  
  static const char *		gQueryRecord_Name			= NULL;
  static const char *		gQueryRecord_Type			= NULL;
  static int				gQueryRecord_OneShot		= false;
  static int				gQueryRecord_TimeLimitSecs	= 0;
  static int				gQueryRecord_RawRData		= false;
  
  static CLIOption		kQueryRecordOpts[] =
  {
  	InterfaceOption(),
  	StringOption( 'n', "name", &gQueryRecord_Name, "domain name", "Full domain name of record to query.", true ),
  	StringOption( 't', "type", &gQueryRecord_Type, "record type", "Record type by name (e.g., TXT, SRV, etc.) or number.", true ),
  	
  	CLI_OPTION_GROUP( "Flags" ),
  	DNSSDFlagsOption(),
  	DNSSDFlagsOption_IncludeAWDL(),
  	DNSSDFlagsOption_ForceMulticast(),
  	DNSSDFlagsOption_Timeout(),
  	DNSSDFlagsOption_ReturnIntermediates(),
  	DNSSDFlagsOption_SuppressUnusable(),
  	DNSSDFlagsOption_UnicastResponse(),
  	DNSSDFlagsOption_DenyCellular(),
  	DNSSDFlagsOption_DenyExpensive(),
  	DNSSDFlagsOption_PathEvalDone(),
  	
  	CLI_OPTION_GROUP( "Operation" ),
  	ConnectionOptions(),
  	BooleanOption( 'o', "oneshot",		&gQueryRecord_OneShot,			"Finish after first set of results." ),
  	IntegerOption( 'l', "timeLimit",	&gQueryRecord_TimeLimitSecs,	"seconds", "Maximum duration of the query record operation. Use '0' for no time limit.", false ),
  	BooleanOption(  0 , "raw",			&gQueryRecord_RawRData,			"Show record data as a hexdump." ),
  	
  	ConnectionSection(),
  	CLI_OPTION_END()
  };
  
  //===========================================================================================================================
  //	Register Command Options
  //===========================================================================================================================
  
  static const char *			gRegister_Name			= NULL;
  static const char *			gRegister_Type			= NULL;
  static const char *			gRegister_Domain		= NULL;
  static int					gRegister_Port			= 0;
  static const char *			gRegister_TXT			= NULL;
  static int					gRegister_LifetimeMs	= -1;
  static const char **		gAddRecord_Types		= NULL;
  static size_t				gAddRecord_TypesCount	= 0;
  static const char **		gAddRecord_Data			= NULL;
  static size_t				gAddRecord_DataCount	= 0;
  static const char **		gAddRecord_TTLs			= NULL;
  static size_t				gAddRecord_TTLsCount	= 0;
  static const char *			gUpdateRecord_Data		= NULL;
  static int					gUpdateRecord_DelayMs	= 0;
  static int					gUpdateRecord_TTL		= 0;
  
  static CLIOption		kRegisterOpts[] =
  {
  	InterfaceOption(),
  	StringOption(  'n', "name",		&gRegister_Name,	"service name",	"Name of service.", false ),
  	StringOption(  't', "type",		&gRegister_Type,	"service type",	"Service type, e.g., \"_ssh._tcp\".", true ),
  	StringOption(  'd', "domain",	&gRegister_Domain,	"domain",		"Domain in which to advertise the service.", false ),
  	IntegerOption( 'p', "port",		&gRegister_Port,	"port number",	"Service's port number.", true ),
  	StringOption(   0 , "txt",		&gRegister_TXT,		"record data",	"The TXT record data. See " kRecordDataSection_Name " below.", false ),
  	
  	CLI_OPTION_GROUP( "Flags" ),
  	DNSSDFlagsOption(),
  	DNSSDFlagsOption_IncludeAWDL(),
  	DNSSDFlagsOption_NoAutoRename(),
  	
  	CLI_OPTION_GROUP( "Operation" ),
  	IntegerOption( 'l', "lifetime", &gRegister_LifetimeMs, "ms", "Lifetime of the service registration in milliseconds.", false ),
  	
  	CLI_OPTION_GROUP( "Options for updating the registered service's primary TXT record with DNSServiceUpdateRecord()\n" ),
  	StringOption(  0 , "updateData",	&gUpdateRecord_Data,	"record data",	"Record data for the record update. See " kRecordDataSection_Name " below.", false ),
  	IntegerOption( 0 , "updateDelay",	&gUpdateRecord_DelayMs,	"ms",			"Number of milliseconds after registration to wait before record update.", false ),
  	IntegerOption( 0 , "updateTTL",		&gUpdateRecord_TTL,		"seconds",		"Time-to-live of the updated record.", false ),
  	
  	CLI_OPTION_GROUP( "Options for adding extra record(s) to the registered service with DNSServiceAddRecord()\n" ),
  	MultiStringOption(   0 , "addType",	&gAddRecord_Types,	&gAddRecord_TypesCount,	"record type",	"Type of additional record by name (e.g., TXT, SRV, etc.) or number.", false ),
  	MultiStringOptionEx( 0 , "addData",	&gAddRecord_Data,	&gAddRecord_DataCount,	"record data",	"Additional record's data. See " kRecordDataSection_Name " below.", false, NULL ),
  	MultiStringOption(   0 , "addTTL",	&gAddRecord_TTLs,	&gAddRecord_TTLsCount,	"seconds",		"Time-to-live of additional record in seconds. Use '0' for default.", false ),
  	
  	RecordDataSection(),
  	CLI_OPTION_END()
  };
  
  //===========================================================================================================================
  //	RegisterRecord Command Options
  //===========================================================================================================================
  
  static const char *		gRegisterRecord_Name			= NULL;
  static const char *		gRegisterRecord_Type			= NULL;
  static const char *		gRegisterRecord_Data			= NULL;
  static int				gRegisterRecord_TTL				= 0;
  static int				gRegisterRecord_LifetimeMs		= -1;
  static const char *		gRegisterRecord_UpdateData		= NULL;
  static int				gRegisterRecord_UpdateDelayMs	= 0;
  static int				gRegisterRecord_UpdateTTL		= 0;
  
  static CLIOption		kRegisterRecordOpts[] =
  {
  	InterfaceOption(),
  	StringOption( 'n', "name",	&gRegisterRecord_Name,	"record name",	"Fully qualified domain name of record.", true ),
  	StringOption( 't', "type",	&gRegisterRecord_Type,	"record type",	"Record type by name (e.g., TXT, PTR, A) or number.", true ),
  	StringOption( 'd', "data",	&gRegisterRecord_Data,	"record data",	"The record data. See " kRecordDataSection_Name " below.", false ),
  	IntegerOption( 0 , "ttl",	&gRegisterRecord_TTL,	"seconds",		"Time-to-live in seconds. Use '0' for default.", false ),
  	
  	CLI_OPTION_GROUP( "Flags" ),
  	DNSSDFlagsOption(),
  	DNSSDFlagsOption_IncludeAWDL(),
  	DNSSDFlagsOption_Shared(),
  	DNSSDFlagsOption_Unique(),
  	
  	CLI_OPTION_GROUP( "Operation" ),
  	IntegerOption( 'l', "lifetime", &gRegisterRecord_LifetimeMs, "ms", "Lifetime of the service registration in milliseconds.", false ),
  	
  	CLI_OPTION_GROUP( "Options for updating the registered record with DNSServiceUpdateRecord()\n" ),
  	StringOption(  0 , "updateData",	&gRegisterRecord_UpdateData,	"record data",	"Record data for the record update.", false ),
  	IntegerOption( 0 , "updateDelay",	&gRegisterRecord_UpdateDelayMs,	"ms",			"Number of milliseconds after registration to wait before record update.", false ),
  	IntegerOption( 0 , "updateTTL",		&gRegisterRecord_UpdateTTL,		"seconds",		"Time-to-live of the updated record.", false ),
  	
  	RecordDataSection(),
  	CLI_OPTION_END()
  };
  
  //===========================================================================================================================
  //	Resolve Command Options
  //===========================================================================================================================
  
  static char *		gResolve_Name			= NULL;
  static char *		gResolve_Type			= NULL;
  static char *		gResolve_Domain			= NULL;
  static int			gResolve_TimeLimitSecs	= 0;
  
  static CLIOption		kResolveOpts[] =
  {
  	InterfaceOption(),
  	StringOption( 'n', "name",		&gResolve_Name,		"service name", "Name of the service instance to resolve.", true ),
  	StringOption( 't', "type",		&gResolve_Type,		"service type", "Type of the service instance to resolve.", true ),
  	StringOption( 'd', "domain",	&gResolve_Domain,	"domain", "Domain of the service instance to resolve.", true ),
  	
  	CLI_OPTION_GROUP( "Flags" ),
  	DNSSDFlagsOption(),
  	DNSSDFlagsOption_ForceMulticast(),
  	DNSSDFlagsOption_IncludeAWDL(),
  	DNSSDFlagsOption_ReturnIntermediates(),
  	
  	CLI_OPTION_GROUP( "Operation" ),
  	ConnectionOptions(),
  	IntegerOption( 'l', "timeLimit", &gResolve_TimeLimitSecs, "seconds", "Maximum duration of the resolve operation. Use '0' for no time limit.", false ),
  	
  	ConnectionSection(),
  	CLI_OPTION_END()
  };
  
  //===========================================================================================================================
  //	Reconfirm Command Options
  //===========================================================================================================================
  
  static const char *		gReconfirmRecord_Name	= NULL;
  static const char *		gReconfirmRecord_Type	= NULL;
  static const char *		gReconfirmRecord_Class	= NULL;
  static const char *		gReconfirmRecord_Data	= NULL;
  
  static CLIOption		kReconfirmOpts[] =
  {
  	InterfaceOption(),
  	StringOption( 'n', "name",	&gReconfirmRecord_Name,		"record name",	"Full name of the record to reconfirm.", true ),
  	StringOption( 't', "type",	&gReconfirmRecord_Type,		"record type",	"Type of the record to reconfirm.", true ),
  	StringOption( 'c', "class",	&gReconfirmRecord_Class,	"record class",	"Class of the record to reconfirm. Default class is IN.", false ),
  	StringOption( 'd', "data",	&gReconfirmRecord_Data,		"record data",	"The record data. See " kRecordDataSection_Name " below.", false ),
  	
  	CLI_OPTION_GROUP( "Flags" ),
  	DNSSDFlagsOption(),
  	
  	RecordDataSection(),
  	CLI_OPTION_END()
  };
  
  //===========================================================================================================================
  //	getaddrinfo-POSIX Command Options
  //===========================================================================================================================
  
  static const char *		gGAIPOSIX_HostName			= NULL;
  static const char *		gGAIPOSIX_ServName			= NULL;
  static const char *		gGAIPOSIX_Family			= NULL;
  static int				gGAIPOSIXFlag_AddrConfig	= false;
  static int				gGAIPOSIXFlag_All			= false;
  static int				gGAIPOSIXFlag_CanonName		= false;
  static int				gGAIPOSIXFlag_NumericHost	= false;
  static int				gGAIPOSIXFlag_NumericServ	= false;
  static int				gGAIPOSIXFlag_Passive		= false;
  static int				gGAIPOSIXFlag_V4Mapped		= false;
  #if( defined( AI_V4MAPPED_CFG ) )
  static int				gGAIPOSIXFlag_V4MappedCFG	= false;
  #endif
  #if( defined( AI_DEFAULT ) )
  static int				gGAIPOSIXFlag_Default		= false;
  #endif
  
  static CLIOption		kGetAddrInfoPOSIXOpts[] =
  {
  	StringOption(	'n', "hostname",			&gGAIPOSIX_HostName,		"hostname", "Domain name to resolve or an IPv4 or IPv6 address.", true ),
  	StringOption(	's', "servname",			&gGAIPOSIX_ServName,		"servname", "Port number in decimal or service name from services(5).", false ),
  	
  	CLI_OPTION_GROUP( "Hints " ),
  	StringOptionEx(	'f', "family",				&gGAIPOSIX_Family,			"address family", "Address family to use for hints ai_family field.", false,
  		"\n"
  		"Possible address family values are 'inet' for AF_INET, 'inet6' for AF_INET6, or 'unspec' for AF_UNSPEC. If no\n"
  		"address family is specified, then AF_UNSPEC is used.\n"
  		"\n" ),
  	BooleanOption(   0 , "flag-addrconfig",		&gGAIPOSIXFlag_AddrConfig,	"In hints ai_flags field, set AI_ADDRCONFIG." ),
  	BooleanOption(   0 , "flag-all",			&gGAIPOSIXFlag_All,			"In hints ai_flags field, set AI_ALL." ),
  	BooleanOption(   0 , "flag-canonname",		&gGAIPOSIXFlag_CanonName,	"In hints ai_flags field, set AI_CANONNAME." ),
  	BooleanOption(   0 , "flag-numerichost",	&gGAIPOSIXFlag_NumericHost,	"In hints ai_flags field, set AI_NUMERICHOST." ),
  	BooleanOption(   0 , "flag-numericserv",	&gGAIPOSIXFlag_NumericServ,	"In hints ai_flags field, set AI_NUMERICSERV." ),
  	BooleanOption(   0 , "flag-passive",		&gGAIPOSIXFlag_Passive,		"In hints ai_flags field, set AI_PASSIVE." ),
  	BooleanOption(   0 , "flag-v4mapped",		&gGAIPOSIXFlag_V4Mapped,	"In hints ai_flags field, set AI_V4MAPPED." ),
  #if( defined( AI_V4MAPPED_CFG ) )
  	BooleanOption(   0 , "flag-v4mappedcfg",	&gGAIPOSIXFlag_V4MappedCFG,	"In hints ai_flags field, set AI_V4MAPPED_CFG." ),
  #endif
  #if( defined( AI_DEFAULT ) )
  	BooleanOption(   0 , "flag-default",		&gGAIPOSIXFlag_Default,		"In hints ai_flags field, set AI_DEFAULT." ),
  #endif
  	
  	CLI_SECTION( "Notes", "See getaddrinfo(3) man page for more details.\n" ),
  	CLI_OPTION_END()
  };
  
  //===========================================================================================================================
  //	ReverseLookup Command Options
  //===========================================================================================================================
  
  static const char *		gReverseLookup_IPAddr			= NULL;
  static int				gReverseLookup_OneShot			= false;
  static int				gReverseLookup_TimeLimitSecs	= 0;
  
  static CLIOption		kReverseLookupOpts[] =
  {
  	InterfaceOption(),
  	StringOption( 'a', "address", &gReverseLookup_IPAddr, "IP address", "IPv4 or IPv6 address for which to perform a reverse IP lookup.", true ),
  	
  	CLI_OPTION_GROUP( "Flags" ),
  	DNSSDFlagsOption(),
  	DNSSDFlagsOption_ForceMulticast(),
  	DNSSDFlagsOption_ReturnIntermediates(),
  	DNSSDFlagsOption_SuppressUnusable(),
  	
  	CLI_OPTION_GROUP( "Operation" ),
  	ConnectionOptions(),
  	BooleanOption( 'o', "oneshot",		&gReverseLookup_OneShot,		"Finish after first set of results." ),
  	IntegerOption( 'l', "timeLimit",	&gReverseLookup_TimeLimitSecs,	"seconds", "Specifies the max duration of the query record operation. Use '0' for no time limit.", false ),
  	
  	ConnectionSection(),
  	CLI_OPTION_END()
  };
  
  //===========================================================================================================================
  //	BrowseAll Command Options
  //===========================================================================================================================
  
  static const char *		gBrowseAll_Domain				= NULL;
  static char **			gBrowseAll_ServiceTypes			= NULL;
  static size_t			gBrowseAll_ServiceTypesCount	= 0;
  static int				gBrowseAll_IncludeAWDL			= false;
  static int				gBrowseAll_BrowseTimeSecs		= 5;
  static int				gBrowseAll_ConnectTimeLimitSecs	= 5;
  
  static CLIOption		kBrowseAllOpts[] =
  {
  	InterfaceOption(),
  	StringOption(	   'd', "domain",	&gBrowseAll_Domain, "domain", "Domain in which to browse for the service.", false ),
  	MultiStringOption( 't', "type",		&gBrowseAll_ServiceTypes, &gBrowseAll_ServiceTypesCount, "service type", "Service type(s), e.g., \"_ssh._tcp\".", false ),
  	
  	CLI_OPTION_GROUP( "Flags" ),
  	DNSSDFlagsOption_IncludeAWDL(),
  	
  	CLI_OPTION_GROUP( "Operation" ),
  	IntegerOption( 'b', "browseTime",		&gBrowseAll_BrowseTimeSecs,			"seconds", "Specifies the duration of the browse.", false ),
  	IntegerOption( 'c', "connectTimeLimit",	&gBrowseAll_ConnectTimeLimitSecs,	"seconds", "Specifies the max duration of the connect operations.", false ),
  	CLI_OPTION_END()
  };
  
  //===========================================================================================================================
  //	GetAddrInfoStress Command Options
  //===========================================================================================================================
  
  static int		gGAIStress_TestDurationSecs	= 0;
  static int		gGAIStress_ConnectionCount	= 0;
  static int		gGAIStress_DurationMinMs	= 0;
  static int		gGAIStress_DurationMaxMs	= 0;
  static int		gGAIStress_RequestCountMax	= 0;
  
  static CLIOption		kGetAddrInfoStressOpts[] =
  {
  	InterfaceOption(),
  	
  	CLI_OPTION_GROUP( "Flags" ),
  	DNSSDFlagsOption_ReturnIntermediates(),
  	DNSSDFlagsOption_SuppressUnusable(),
  	
  	CLI_OPTION_GROUP( "Operation" ),
  	IntegerOption( 0, "testDuration",			&gGAIStress_TestDurationSecs,	"seconds",	"Stress test duration in seconds. Use '0' for forever.", false ),
  	IntegerOption( 0, "connectionCount",		&gGAIStress_ConnectionCount,	"integer",	"Number of simultaneous DNS-SD connections.", true ),
  	IntegerOption( 0, "requestDurationMin",		&gGAIStress_DurationMinMs,		"ms",		"Minimum duration of DNSServiceGetAddrInfo() request in milliseconds.", true ),
  	IntegerOption( 0, "requestDurationMax",		&gGAIStress_DurationMaxMs,		"ms",		"Maximum duration of DNSServiceGetAddrInfo() request in milliseconds.", true ),
  	IntegerOption( 0, "consecutiveRequestMax",	&gGAIStress_RequestCountMax,	"integer",	"Maximum number of requests on a connection before restarting it.", true ),
  	CLI_OPTION_END()
  };
  
  //===========================================================================================================================
  //	DNSQuery Command Options
  //===========================================================================================================================
  
  static char *		gDNSQuery_Name			= NULL;
  static char *		gDNSQuery_Type			= "A";
  static char *		gDNSQuery_Server		= NULL;
  static int			gDNSQuery_TimeLimitSecs	= 5;
  static int			gDNSQuery_UseTCP		= false;
  static int			gDNSQuery_Flags			= kDNSHeaderFlag_RecursionDesired;
  static int			gDNSQuery_RawRData		= false;
  static int			gDNSQuery_Verbose		= false;
  
  #if( TARGET_OS_DARWIN )
  	#define kDNSQueryServerOptionIsRequired		false
  #else
  	#define kDNSQueryServerOptionIsRequired		true
  #endif
  
  static CLIOption		kDNSQueryOpts[] =
  {
  	StringOption(  'n', "name",			&gDNSQuery_Name,			"name",	"Question name (QNAME) to put in DNS query message.", true ),
  	StringOption(  't', "type",			&gDNSQuery_Type,			"type",	"Question type (QTYPE) to put in DNS query message. Default value is 'A'.", false ),
  	StringOption(  's', "server",		&gDNSQuery_Server,			"IP address", "DNS server's IPv4 or IPv6 address.", kDNSQueryServerOptionIsRequired ),
  	IntegerOption( 'l', "timeLimit",	&gDNSQuery_TimeLimitSecs,	"seconds", "Specifies query time limit. Use '-1' for no limit and '0' to exit immediately after sending.", false ),
  	BooleanOption(  0 , "tcp",			&gDNSQuery_UseTCP,			"Send the DNS query via TCP instead of UDP." ),
  	IntegerOption( 'f', "flags",		&gDNSQuery_Flags,			"flags", "16-bit value for DNS header flags/codes field. Default value is 0x0100 (Recursion Desired).", false ),
  	BooleanOption(  0 , "raw",			&gDNSQuery_RawRData,		"Present record data as a hexdump." ),
  	BooleanOption( 'v', "verbose",		&gDNSQuery_Verbose,			"Prints the DNS message to be sent to the server." ),
  	CLI_OPTION_END()
  };
  
  #if( DNSSDUTIL_INCLUDE_DNSCRYPT )
  //===========================================================================================================================
  //	DNSCrypt Command Options
  //===========================================================================================================================
  
  static char *		gDNSCrypt_ProviderName	= NULL;
  static char *		gDNSCrypt_ProviderKey	= NULL;
  static char *		gDNSCrypt_Name			= NULL;
  static char *		gDNSCrypt_Type			= NULL;
  static char *		gDNSCrypt_Server		= NULL;
  static int			gDNSCrypt_TimeLimitSecs	= 5;
  static int			gDNSCrypt_RawRData		= false;
  static int			gDNSCrypt_Verbose		= false;
  
  static CLIOption		kDNSCryptOpts[] =
  {
  	StringOption(  'p', "providerName",	&gDNSCrypt_ProviderName,	"name", "The DNSCrypt provider name.", true ),
  	StringOption(  'k', "providerKey",	&gDNSCrypt_ProviderKey,		"hex string", "The DNSCrypt provider's public signing key.", true ),
  	StringOption(  'n', "name",			&gDNSCrypt_Name,			"name",	"Question name (QNAME) to put in DNS query message.", true ),
  	StringOption(  't', "type",			&gDNSCrypt_Type,			"type",	"Question type (QTYPE) to put in DNS query message.", true ),
  	StringOption(  's', "server",		&gDNSCrypt_Server,			"IP address", "DNS server's IPv4 or IPv6 address.", true ),
  	IntegerOption( 'l', "timeLimit",	&gDNSCrypt_TimeLimitSecs,	"seconds", "Specifies query time limit. Use '-1' for no time limit and '0' to exit immediately after sending.", false ),
  	BooleanOption(  0 , "raw",			&gDNSCrypt_RawRData,		"Present record data as a hexdump." ),
  	BooleanOption( 'v', "verbose",		&gDNSCrypt_Verbose,			"Prints the DNS message to be sent to the server." ),
  	CLI_OPTION_END()
  };
  #endif
  
  //===========================================================================================================================
  //	MDNSQuery Command Options
  //===========================================================================================================================
  
  static char *		gMDNSQuery_Name			= NULL;
  static char *		gMDNSQuery_Type			= NULL;
  static int			gMDNSQuery_SourcePort	= 0;
  static int			gMDNSQuery_IsQU			= false;
  static int			gMDNSQuery_RawRData		= false;
  static int			gMDNSQuery_UseIPv4		= false;
  static int			gMDNSQuery_UseIPv6		= false;
  static int			gMDNSQuery_AllResponses	= false;
  static int			gMDNSQuery_ReceiveSecs	= 1;
  
  static CLIOption		kMDNSQueryOpts[] =
  {
  	StringOption(  'i', "interface",	&gInterface,				"name or index", "Network interface by name or index.", true ),
  	StringOption(  'n', "name",			&gMDNSQuery_Name,			"name", "Question name (QNAME) to put in mDNS message.", true ),
  	StringOption(  't', "type",			&gMDNSQuery_Type,			"type", "Question type (QTYPE) to put in mDNS message.", true ),
  	IntegerOption( 'p', "sourcePort",	&gMDNSQuery_SourcePort,		"port number", "UDP source port to use when sending mDNS messages. Default is 5353 for QM questions.", false ),
  	BooleanOption( 'u', "QU",			&gMDNSQuery_IsQU,			"Set the unicast-response bit, i.e., send a QU question." ),
  	BooleanOption(  0 , "raw",			&gMDNSQuery_RawRData,		"Present record data as a hexdump." ),
  	BooleanOption(  0 , "ipv4",			&gMDNSQuery_UseIPv4,		"Use IPv4." ),
  	BooleanOption(  0 , "ipv6",			&gMDNSQuery_UseIPv6,		"Use IPv6." ),
  	BooleanOption( 'a', "allResponses",	&gMDNSQuery_AllResponses,	"Print all received mDNS messages, not just those containing answers." ),
  	IntegerOption( 'r', "receiveTime",	&gMDNSQuery_ReceiveSecs,	"seconds", "Amount of time to spend receiving messages after the query is sent. The default is one second. Use -1 for unlimited time.", false ),
  	CLI_OPTION_END()
  };
  
  //===========================================================================================================================
  //	PIDToUUID Command Options
  //===========================================================================================================================
  
  static int		gPIDToUUID_PID = 0;
  
  static CLIOption		kPIDToUUIDOpts[] =
  {
  	IntegerOption( 'p', "pid", &gPIDToUUID_PID, "PID", "Process ID.", true ),
  	CLI_OPTION_END()
  };
  
  //===========================================================================================================================
  //	Command Table
  //===========================================================================================================================
  
  static OSStatus	VersionOptionCallback( CLIOption *inOption, const char *inArg, int inUnset );
  
  static void	BrowseCmd( void );
  static void	GetAddrInfoCmd( void );
  static void	QueryRecordCmd( void );
  static void	RegisterCmd( void );
  static void	RegisterRecordCmd( void );
  static void	ResolveCmd( void );
  static void	ReconfirmCmd( void );
  static void	GetAddrInfoPOSIXCmd( void );
  static void	ReverseLookupCmd( void );
  static void	BrowseAllCmd( void );
  static void	GetAddrInfoStressCmd( void );
  static void	DNSQueryCmd( void );
  #if( DNSSDUTIL_INCLUDE_DNSCRYPT )
  static void	DNSCryptCmd( void );
  #endif
  static void	MDNSQueryCmd( void );
  static void	PIDToUUIDCmd( void );
  static void	DaemonVersionCmd( void );
  
  static CLIOption		kGlobalOpts[] =
  {
  	CLI_OPTION_CALLBACK_EX( 'V', "version", VersionOptionCallback, NULL, NULL,
  		kCLIOptionFlags_NoArgument | kCLIOptionFlags_GlobalOnly, "Displays the version of this tool.", NULL ),
  	CLI_OPTION_HELP(),
  	
  	// Common commands.
  	
  	Command( "browse",				BrowseCmd,				kBrowseOpts,			"Uses DNSServiceBrowse() to browse for one or more service types.", false ),
  	Command( "getAddrInfo",			GetAddrInfoCmd,			kGetAddrInfoOpts,		"Uses DNSServiceGetAddrInfo() to resolve a hostname to IP addresses.", false ),
  	Command( "queryRecord",			QueryRecordCmd,			kQueryRecordOpts,		"Uses DNSServiceQueryRecord() to query for an arbitrary DNS record.", false ),
  	Command( "register",			RegisterCmd,			kRegisterOpts,			"Uses DNSServiceRegister() to register a service.", false ),
  	Command( "registerRecord",		RegisterRecordCmd,		kRegisterRecordOpts,	"Uses DNSServiceRegisterRecord() to register a record.", false ),
  	Command( "resolve",				ResolveCmd,				kResolveOpts,			"Uses DNSServiceResolve() to resolve a service.", false ),
  	Command( "reconfirm",			ReconfirmCmd,			kReconfirmOpts,			"Uses DNSServiceReconfirmRecord() to reconfirm a record.", false ),
  	Command( "getaddrinfo-posix",	GetAddrInfoPOSIXCmd,	kGetAddrInfoPOSIXOpts,	"Uses getaddrinfo() to resolve a hostname to IP addresses.", false ),
  	Command( "reverseLookup",		ReverseLookupCmd,		kReverseLookupOpts,		"Uses DNSServiceQueryRecord() to perform a reverse IP address lookup.", false ),
  	Command( "browseAll",			BrowseAllCmd,			kBrowseAllOpts,			"Browse and resolve all, or just some, services.", false ),
  	
  	// Uncommon commands.
  	
  	Command( "getAddrInfoStress",	GetAddrInfoStressCmd,	kGetAddrInfoStressOpts,	"Runs DNSServiceGetAddrInfo() stress testing.", true ),
  	Command( "DNSQuery",			DNSQueryCmd,			kDNSQueryOpts,			"Crafts and sends a DNS query.", true ),
  #if( DNSSDUTIL_INCLUDE_DNSCRYPT )
  	Command( "DNSCrypt",			DNSCryptCmd,			kDNSCryptOpts,			"Crafts and sends a DNSCrypt query.", true ),
  #endif
  	Command( "mDNSQuery",			MDNSQueryCmd,			kMDNSQueryOpts,			"Crafts and sends an mDNS query over the specified interface.", true ),
  	Command( "pid2uuid",			PIDToUUIDCmd,			kPIDToUUIDOpts,			"Prints the UUID of a process.", true ),
  	Command( "daemonVersion",		DaemonVersionCmd,		NULL,					"Prints the version of the DNS-SD daemon.", true ),
  	
  	CLI_COMMAND_HELP(),
  	CLI_OPTION_END()
  };
  
  //===========================================================================================================================
  //	Helper Prototypes
  //===========================================================================================================================
  
  #define kExitReason_OneShotDone				"one-shot done"
  #define kExitReason_ReceivedResponse		"received response"
  #define kExitReason_SIGINT					"interrupt signal"
  #define kExitReason_Timeout					"timeout"
  #define kExitReason_TimeLimit				"time limit"
  
  static void	Exit( void *inContext ) ATTRIBUTE_NORETURN;
  
  #define kTimestampBufLen		27
  
  static char *			GetTimestampStr( char inBuffer[ kTimestampBufLen ] );
  static DNSServiceFlags	GetDNSSDFlagsFromOpts( void );
  
  typedef enum
  {
  	kConnectionType_None			= 0,
  	kConnectionType_Normal			= 1,
  	kConnectionType_DelegatePID		= 2,
  	kConnectionType_DelegateUUID	= 3
  	
  }	ConnectionType;
  
  typedef struct
  {
  	ConnectionType		type;
  	union
  	{
  		int32_t			pid;
  		uint8_t			uuid[ 16 ];
  		
  	}	delegate;
  	
  }	ConnectionDesc;
  
  static OSStatus
  	CreateConnectionFromArgString(
  		const char *			inString,
  		dispatch_queue_t		inQueue,
  		DNSServiceRef *			outSDRef,
  		ConnectionDesc *		outDesc );
  static OSStatus			InterfaceIndexFromArgString( const char *inString, uint32_t *outIndex );
  static OSStatus			RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen );
  static OSStatus			RecordTypeFromArgString( const char *inString, uint16_t *outValue );
  static OSStatus			RecordClassFromArgString( const char *inString, uint16_t *outValue );
  
  #define kInterfaceNameBufLen		( Max( IF_NAMESIZE, 16 ) + 1 )
  
  static char *			InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] );
  static const char *		RecordTypeToString( unsigned int inValue );
  
  // DNS message helpers
  
  typedef struct
  {
  	uint8_t		id[ 2 ];
  	uint8_t		flags[ 2 ];
  	uint8_t		questionCount[ 2 ];
  	uint8_t		answerCount[ 2 ];
  	uint8_t		authorityCount[ 2 ];
  	uint8_t		additionalCount[ 2 ];
  	
  }	DNSHeader;
  
  #define kDNSHeaderLength		12
  check_compile_time( sizeof( DNSHeader ) == kDNSHeaderLength );
  
  #define DNSHeaderGetID( HDR )					ReadBig16( ( HDR )->id )
  #define DNSHeaderGetFlags( HDR )				ReadBig16( ( HDR )->flags )
  #define DNSHeaderGetQuestionCount( HDR )		ReadBig16( ( HDR )->questionCount )
  #define DNSHeaderGetAnswerCount( HDR )			ReadBig16( ( HDR )->answerCount )
  #define DNSHeaderGetAuthorityCount( HDR )		ReadBig16( ( HDR )->authorityCount )
  #define DNSHeaderGetAdditionalCount( HDR )		ReadBig16( ( HDR )->additionalCount )
  
  static OSStatus
  	DNSMessageExtractDomainName(
  		const uint8_t *		inMsgPtr,
  		size_t				inMsgLen,
  		const uint8_t *		inNamePtr,
  		uint8_t				inBuf[ kDomainNameLengthMax ],
  		const uint8_t **	outNextPtr );
  static OSStatus
  	DNSMessageExtractDomainNameString(
  		const void *		inMsgPtr,
  		size_t				inMsgLen,
  		const void *		inNamePtr,
  		char				inBuf[ kDNSServiceMaxDomainName ],
  		const uint8_t **	outNextPtr );
  static OSStatus
  	DNSMessageExtractRecord(
  		const uint8_t *		inMsgPtr,
  		size_t				inMsgLen,
  		const uint8_t *		inPtr,
  		uint8_t				inNameBuf[ kDomainNameLengthMax ],
  		uint16_t *			outType,
  		uint16_t *			outClass,
  		uint32_t *			outTTL,
  		const uint8_t **	outRDataPtr,
  		size_t *			outRDataLen,
  		const uint8_t **	outPtr );
  static OSStatus	DNSMessageGetAnswerSection( const uint8_t *inMsgPtr, size_t inMsgLen, const uint8_t **outPtr );
  static OSStatus
  	DNSRecordDataToString(
  		const void *	inRDataPtr,
  		size_t			inRDataLen,
  		unsigned int	inRDataType,
  		const void *	inMsgPtr,
  		size_t			inMsgLen,
  		char **			outString );
  static OSStatus
  	DomainNameAppendString(
  		uint8_t			inDomainName[ kDomainNameLengthMax ],
  		const char *	inString,
  		uint8_t **		outEndPtr );
  static Boolean	DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 );
  static OSStatus
  	DomainNameToString(
  		const uint8_t *		inDomainName,
  		const uint8_t *		inEnd,
  		char				inBuf[ kDNSServiceMaxDomainName ],
  		const uint8_t **	outNextPtr );
  
  static OSStatus	PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, Boolean inIsMDNS, Boolean inPrintRaw );
  
  #define PrintMDNSMessage( MSGPTR, MSGLEN, RAW )		PrintDNSMessage( MSGPTR, MSGLEN, true, RAW )
  #define PrintUDNSMessage( MSGPTR, MSGLEN, RAW )		PrintDNSMessage( MSGPTR, MSGLEN, false, RAW )
  
  #define kDNSQueryMessageMaxLen		( kDNSHeaderLength + kDomainNameLengthMax + 4 )
  
  static OSStatus
  	WriteDNSQueryMessage(
  		uint8_t			inMsg[ kDNSQueryMessageMaxLen ],
  		uint16_t		inMsgID,
  		uint16_t		inFlags,
  		const char *	inQName,
  		uint16_t		inQType,
  		uint16_t		inQClass,
  		size_t *		outMsgLen );
  
  // Dispatch helpers
  
  typedef void ( *DispatchHandler )( void *inContext );
  
  static OSStatus
  	DispatchSignalSourceCreate(
  		int					inSignal,
  		DispatchHandler		inEventHandler,
  		void *				inContext,
  		dispatch_source_t *	outSource );
  static OSStatus
  	DispatchReadSourceCreate(
  		SocketRef			inSock,
  		DispatchHandler		inEventHandler,
  		DispatchHandler		inCancelHandler,
  		void *				inContext,
  		dispatch_source_t *	outSource );
  static OSStatus
  	DispatchTimerCreate(
  		dispatch_time_t		inStart,
  		uint64_t			inIntervalNs,
  		uint64_t			inLeewayNs,
  		DispatchHandler		inEventHandler,
  		DispatchHandler		inCancelHandler,
  		void *				inContext,
  		dispatch_source_t *	outTimer );
  
  static const char *	ServiceTypeDescription( const char *inName );
  
  typedef struct
  {
  	SocketRef		sock;
  	void *			context;
  	
  }	SocketContext;
  
  static void			SocketContextCancelHandler( void *inContext );
  static OSStatus		StringToInt32( const char *inString, int32_t *outValue );
  static OSStatus		StringToUInt32( const char *inString, uint32_t *outValue );
  #if( TARGET_OS_DARWIN )
  static OSStatus		GetDefaultDNSServer( sockaddr_ip *outAddr );
  #endif
  
  #define AddRmvString( X )		( ( (X) & kDNSServiceFlagsAdd ) ? "Add" : "Rmv" )
  #define Unused( X )				(void)(X)
  
  //===========================================================================================================================
  //	main
  //===========================================================================================================================
  
  int	main( int argc, const char **argv )
  {
  	// Route DebugServices logging output to stderr.
  	
  	dlog_control( "DebugServices:output=file;stderr" );
  	
  	CLIInit( argc, argv );
  	CLIParse( kGlobalOpts, kCLIFlags_None );
  	
  	return( gExitCode );
  }
  
  //===========================================================================================================================
  //	VersionOptionCallback
  //===========================================================================================================================
  
  static OSStatus	VersionOptionCallback( CLIOption *inOption, const char *inArg, int inUnset )
  {
  	const char *		srcVers;
  #if( MDNSRESPONDER_PROJECT )
  	char				srcStr[ 16 ];
  #endif
  	
  	Unused( inOption );
  	Unused( inArg );
  	Unused( inUnset );
  	
  #if( MDNSRESPONDER_PROJECT )
  	srcVers = SourceVersionToCString( _DNS_SD_H, srcStr );
  #else
  	srcVers = DNSSDUTIL_SOURCE_VERSION;
  #endif
  	FPrintF( stdout, "%s version %v (%s)\n", gProgramName, kDNSSDUtilNumVersion, srcVers );
  	
  	return( kEndingErr );
  }
  
  //===========================================================================================================================
  //	BrowseCmd
  //===========================================================================================================================
  
  typedef struct BrowseResolveOp		BrowseResolveOp;
  
  struct BrowseResolveOp
  {
  	BrowseResolveOp *		next;			// Next resolve operation in list.
  	DNSServiceRef			sdRef;			// sdRef of the DNSServiceResolve or DNSServiceQueryRecord operation.
  	char *					fullName;		// Full name of the service to resolve.
  	uint32_t				interfaceIndex;	// Interface index of the DNSServiceResolve or DNSServiceQueryRecord operation.
  };
  
  typedef struct
  {
  	DNSServiceRef			mainRef;			// Main sdRef for shared connection.
  	DNSServiceRef *			opRefs;				// Array of sdRefs for individual Browse operarions.
  	size_t					opRefsCount;		// Count of array of sdRefs for non-shared connections.
  	const char *			domain;				// Domain for DNSServiceBrowse operation(s).
  	DNSServiceFlags			flags;				// Flags for DNSServiceBrowse operation(s).
  	char **					serviceTypes;		// Array of service types to browse for.
  	size_t					serviceTypesCount;	// Count of array of service types to browse for.
  	int						timeLimitSecs;		// Time limit of DNSServiceBrowse operation in seconds.
  	BrowseResolveOp *		resolveList;		// List of resolve and/or TXT record query operations.
  	uint32_t				ifIndex;			// Interface index of DNSServiceBrowse operation(s).
  	Boolean					printedHeader;		// True if results header has been printed.
  	Boolean					doResolve;			// True if service instances are to be resolved.
  	Boolean					doResolveTXTOnly;	// True if TXT records of service instances are to be queried.
  	
  }	BrowseContext;
  
  static void		BrowsePrintPrologue( const BrowseContext *inContext );
  static void		BrowseContextFree( BrowseContext *inContext );
  static OSStatus	BrowseResolveOpCreate( const char *inFullName, uint32_t inInterfaceIndex, BrowseResolveOp **outOp );
  static void		BrowseResolveOpFree( BrowseResolveOp *inOp );
  static void DNSSD_API
  	BrowseCallback(
  		DNSServiceRef		inSDRef,
  		DNSServiceFlags		inFlags,
  		uint32_t			inInterfaceIndex,
  		DNSServiceErrorType	inError,
  		const char *		inName,
  		const char *		inRegType,
  		const char *		inDomain,
  		void *				inContext );
  static void DNSSD_API
  	BrowseResolveCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inFullName,
  		const char *			inHostname,
  		uint16_t				inPort,
  		uint16_t				inTXTLen,
  		const unsigned char *	inTXTPtr,
  		void *					inContext );
  static void DNSSD_API
  	BrowseQueryRecordCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inFullName,
  		uint16_t				inType,
  		uint16_t				inClass,
  		uint16_t				inRDataLen,
  		const void *			inRDataPtr,
  		uint32_t				inTTL,
  		void *					inContext );
  
  static void	BrowseCmd( void )
  {
  	OSStatus				err;
  	size_t					i;
  	BrowseContext *			context			= NULL;
  	dispatch_source_t		signalSource	= NULL;
  	int						useMainConnection;
  	
  	// Set up SIGINT handler.
  	
  	signal( SIGINT, SIG_IGN );
  	err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
  	require_noerr( err, exit );
  	dispatch_resume( signalSource );
  	
  	// Create context.
  	
  	context = (BrowseContext *) calloc( 1, sizeof( *context ) );
  	require_action( context, exit, err = kNoMemoryErr );
  	
  	context->opRefs = (DNSServiceRef *) calloc( gBrowse_ServiceTypesCount, sizeof( DNSServiceRef ) );
  	require_action( context->opRefs, exit, err = kNoMemoryErr );
  	context->opRefsCount = gBrowse_ServiceTypesCount;
  	
  	// Check command parameters.
  	
  	if( gBrowse_TimeLimitSecs < 0 )
  	{
  		FPrintF( stderr, "Invalid time limit: %d seconds.\n", gBrowse_TimeLimitSecs );
  		err = kParamErr;
  		goto exit;
  	}
  	
  	// Create main connection.
  	
  	if( gConnectionOpt )
  	{
  		err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL );
  		require_noerr_quiet( err, exit );
  		useMainConnection = true;
  	}
  	else
  	{
  		useMainConnection = false;
  	}
  	
  	// Get flags.
  	
  	context->flags = GetDNSSDFlagsFromOpts();
  	if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection;
  	
  	// Get interface.
  	
  	err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
  	require_noerr_quiet( err, exit );
  	
  	// Set remaining parameters.
  	
  	context->serviceTypes		= gBrowse_ServiceTypes;
  	context->serviceTypesCount	= gBrowse_ServiceTypesCount;
  	context->domain				= gBrowse_Domain;
  	context->doResolve			= gBrowse_DoResolve	? true : false;
  	context->timeLimitSecs		= gBrowse_TimeLimitSecs;
  	context->doResolveTXTOnly	= gBrowse_QueryTXT	? true : false;
  	
  	// Print prologue.
  	
  	BrowsePrintPrologue( context );
  	
  	// Start operation(s).
  	
  	for( i = 0; i < context->serviceTypesCount; ++i )
  	{
  		DNSServiceRef		sdRef;
  		
  		if( useMainConnection ) sdRef = context->mainRef;
  		err = DNSServiceBrowse( &sdRef, context->flags, context->ifIndex, context->serviceTypes[ i ], context->domain,
  			BrowseCallback, context );
  		require_noerr( err, exit );
  		
  		context->opRefs[ i ] = sdRef;
  		if( !useMainConnection )
  		{
  			err = DNSServiceSetDispatchQueue( context->opRefs[ i ], dispatch_get_main_queue() );
  			require_noerr( err, exit );
  		}
  	}
  	
  	// Set time limit.
  	
  	if( context->timeLimitSecs > 0 )
  	{
  		dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(),
  			kExitReason_TimeLimit, Exit );
  	}
  	dispatch_main();
  	
  exit:
  	dispatch_source_forget( &signalSource );
  	if( context ) BrowseContextFree( context );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	BrowsePrintPrologue
  //===========================================================================================================================
  
  static void	BrowsePrintPrologue( const BrowseContext *inContext )
  {
  	const int						timeLimitSecs	= inContext->timeLimitSecs;
  	const char * const *			serviceType		= (const char **) inContext->serviceTypes;
  	const char * const * const		end				= (const char **) inContext->serviceTypes + inContext->serviceTypesCount;
  	char							time[ kTimestampBufLen ];
  	char							ifName[ kInterfaceNameBufLen ];
  	
  	InterfaceIndexToName( inContext->ifIndex, ifName );
  	
  	FPrintF( stdout, "Flags:         %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
  	FPrintF( stdout, "Interface:     %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
  	FPrintF( stdout, "Service types: %s",			*serviceType++ );
  	while( serviceType < end ) FPrintF( stdout, ", %s", *serviceType++ );
  	FPrintF( stdout, "\n" );
  	FPrintF( stdout, "Domain:        %s\n",	inContext->domain ? inContext->domain : "<NULL> (default domains)" );
  	FPrintF( stdout, "Time limit:    " );
  	if( timeLimitSecs > 0 )	FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
  	else					FPrintF( stdout, "∞\n" );
  	FPrintF( stdout, "Start time:    %s\n", GetTimestampStr( time ) );
  	FPrintF( stdout, "---\n" );
  }
  
  //===========================================================================================================================
  //	BrowseContextFree
  //===========================================================================================================================
  
  static void	BrowseContextFree( BrowseContext *inContext )
  {
  	size_t		i;
  	
  	for( i = 0; i < inContext->opRefsCount; ++i )
  	{
  		DNSServiceForget( &inContext->opRefs[ i ] );
  	}
  	if( inContext->serviceTypes )
  	{
  		StringArray_Free( inContext->serviceTypes, inContext->serviceTypesCount );
  		inContext->serviceTypes			= NULL;
  		inContext->serviceTypesCount	= 0;
  	}
  	DNSServiceForget( &inContext->mainRef );
  	free( inContext );
  }
  
  //===========================================================================================================================
  //	BrowseResolveOpCreate
  //===========================================================================================================================
  
  static OSStatus	BrowseResolveOpCreate( const char *inFullName, uint32_t inInterfaceIndex, BrowseResolveOp **outOp )
  {
  	OSStatus				err;
  	BrowseResolveOp *		resolveOp;
  	
  	resolveOp = (BrowseResolveOp *) calloc( 1, sizeof( *resolveOp ) );
  	require_action( resolveOp, exit, err = kNoMemoryErr );
  	
  	resolveOp->fullName = strdup( inFullName );
  	require_action( resolveOp->fullName, exit, err = kNoMemoryErr );
  	
  	resolveOp->interfaceIndex = inInterfaceIndex;
  	
  	*outOp = resolveOp;
  	resolveOp = NULL;
  	err = kNoErr;
  	
  exit:
  	if( resolveOp ) BrowseResolveOpFree( resolveOp );
  	return( err );
  }
  
  //===========================================================================================================================
  //	BrowseResolveOpFree
  //===========================================================================================================================
  
  static void	BrowseResolveOpFree( BrowseResolveOp *inOp )
  {
  	DNSServiceForget( &inOp->sdRef );
  	ForgetMem( &inOp->fullName );
  	free( inOp );
  }
  
  //===========================================================================================================================
  //	BrowseCallback
  //===========================================================================================================================
  
  static void DNSSD_API
  	BrowseCallback(
  		DNSServiceRef		inSDRef,
  		DNSServiceFlags		inFlags,
  		uint32_t			inInterfaceIndex,
  		DNSServiceErrorType	inError,
  		const char *		inName,
  		const char *		inRegType,
  		const char *		inDomain,
  		void *				inContext )
  {
  	BrowseContext * const		context = (BrowseContext *) inContext;
  	OSStatus					err;
  	BrowseResolveOp *			newOp = NULL;
  	BrowseResolveOp **			p;
  	char						fullName[ kDNSServiceMaxDomainName ];
  	char						time[ kTimestampBufLen ];
  	
  	Unused( inSDRef );
  	
  	GetTimestampStr( time );
  	
  	err = inError;
  	require_noerr( err, exit );
  	
  	if( !context->printedHeader )
  	{
  		FPrintF( stdout, "%-26s  A/R Flags IF %-20s %-20s Instance Name\n", "Timestamp", "Domain", "Service Type" );
  		context->printedHeader = true;
  	}
  	FPrintF( stdout, "%-26s  %-3s %5X %2d %-20s %-20s %s\n",
  		time, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inDomain, inRegType, inName );
  	
  	if( !context->doResolve && !context->doResolveTXTOnly ) goto exit;
  	
  	err = DNSServiceConstructFullName( fullName, inName, inRegType, inDomain );
  	require_noerr( err, exit );
  	
  	if( inFlags & kDNSServiceFlagsAdd )
  	{
  		DNSServiceRef		sdRef;
  		DNSServiceFlags		flags;
  		
  		err = BrowseResolveOpCreate( fullName, inInterfaceIndex, &newOp );
  		require_noerr( err, exit );
  		
  		if( context->mainRef )
  		{
  			sdRef = context->mainRef;
  			flags = kDNSServiceFlagsShareConnection;
  		}
  		else
  		{
  			flags = 0;
  		}
  		if( context->doResolve )
  		{
  			err = DNSServiceResolve( &sdRef, flags, inInterfaceIndex, inName, inRegType, inDomain, BrowseResolveCallback,
  				NULL );
  			require_noerr( err, exit );
  		}
  		else
  		{
  			err = DNSServiceQueryRecord( &sdRef, flags, inInterfaceIndex, fullName, kDNSServiceType_TXT, kDNSServiceClass_IN,
  				BrowseQueryRecordCallback, NULL );
  			require_noerr( err, exit );
  		}
  		
  		newOp->sdRef = sdRef;
  		if( !context->mainRef )
  		{
  			err = DNSServiceSetDispatchQueue( newOp->sdRef, dispatch_get_main_queue() );
  			require_noerr( err, exit );
  		}
  		for( p = &context->resolveList; *p; p = &( *p )->next ) {}
  		*p = newOp;
  		newOp = NULL;
  	}
  	else
  	{
  		BrowseResolveOp *		resolveOp;
  		
  		for( p = &context->resolveList; ( resolveOp = *p ) != NULL; p = &resolveOp->next )
  		{
  			if( ( resolveOp->interfaceIndex == inInterfaceIndex ) && ( strcasecmp( resolveOp->fullName, fullName ) == 0 ) )
  			{
  				break;
  			}
  		}
  		if( resolveOp )
  		{
  			*p = resolveOp->next;
  			BrowseResolveOpFree( resolveOp );
  		}
  	}
  	
  exit:
  	if( newOp ) BrowseResolveOpFree( newOp );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	BrowseQueryRecordCallback
  //===========================================================================================================================
  
  static void DNSSD_API
  	BrowseQueryRecordCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inFullName,
  		uint16_t				inType,
  		uint16_t				inClass,
  		uint16_t				inRDataLen,
  		const void *			inRDataPtr,
  		uint32_t				inTTL,
  		void *					inContext )
  {
  	OSStatus		err;
  	char			time[ kTimestampBufLen ];
  	
  	Unused( inSDRef );
  	Unused( inClass );
  	Unused( inTTL );
  	Unused( inContext );
  	
  	GetTimestampStr( time );
  	
  	err = inError;
  	require_noerr( err, exit );
  	require_action( inType == kDNSServiceType_TXT, exit, err = kTypeErr );
  	
  	FPrintF( stdout, "%s  %s %s TXT on interface %d\n    TXT: %#{txt}\n",
  		time, AddRmvString( inFlags ), inFullName, (int32_t) inInterfaceIndex, inRDataPtr, (size_t) inRDataLen );
  	
  exit:
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	BrowseResolveCallback
  //===========================================================================================================================
  
  static void DNSSD_API
  	BrowseResolveCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inFullName,
  		const char *			inHostname,
  		uint16_t				inPort,
  		uint16_t				inTXTLen,
  		const unsigned char *	inTXTPtr,
  		void *					inContext )
  {
  	char		time[ kTimestampBufLen ];
  	char		errorStr[ 64 ];
  	
  	Unused( inSDRef );
  	Unused( inFlags );
  	Unused( inContext );
  	
  	GetTimestampStr( time );
  	
  	if( inError ) SNPrintF( errorStr, sizeof( errorStr ), " error %#m", inError );
  	
  	FPrintF( stdout, "%s  %s can be reached at %s:%u (interface %d)%?s\n",
  		time, inFullName, inHostname, ntohs( inPort ), (int32_t) inInterfaceIndex, inError, errorStr );
  	if( inTXTLen == 1 )
  	{
  		FPrintF( stdout, " TXT record: %#H\n", inTXTPtr, (int) inTXTLen, INT_MAX );
  	}
  	else
  	{
  		FPrintF( stdout, " TXT record: %#{txt}\n", inTXTPtr, (size_t) inTXTLen );
  	}
  }
  
  //===========================================================================================================================
  //	GetAddrInfoCmd
  //===========================================================================================================================
  
  typedef struct
  {
  	DNSServiceRef			mainRef;		// Main sdRef for shared connection.
  	DNSServiceRef			opRef;			// sdRef for the DNSServiceGetAddrInfo operation.
  	const char *			name;			// Hostname to resolve.
  	DNSServiceFlags			flags;			// Flags argument for DNSServiceGetAddrInfo().
  	DNSServiceProtocol		protocols;		// Protocols argument for DNSServiceGetAddrInfo().
  	uint32_t				ifIndex;		// Interface index argument for DNSServiceGetAddrInfo().
  	int						timeLimitSecs;	// Time limit for the DNSServiceGetAddrInfo() operation in seconds.
  	Boolean					printedHeader;	// True if the results header has been printed.
  	Boolean					oneShotMode;	// True if command is done after the first set of results (one-shot mode).
  	Boolean					needIPv4;		// True if in one-shot mode and an IPv4 result is needed.
  	Boolean					needIPv6;		// True if in one-shot mode and an IPv6 result is needed.
  	
  }	GetAddrInfoContext;
  
  static void	GetAddrInfoPrintPrologue( const GetAddrInfoContext *inContext );
  static void	GetAddrInfoContextFree( GetAddrInfoContext *inContext );
  static void DNSSD_API
  	GetAddrInfoCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inHostname,
  		const struct sockaddr *	inSockAddr,
  		uint32_t				inTTL,
  		void *					inContext );
  
  static void	GetAddrInfoCmd( void )
  {
  	OSStatus					err;
  	DNSServiceRef				sdRef;
  	GetAddrInfoContext *		context			= NULL;
  	dispatch_source_t			signalSource	= NULL;
  	int							useMainConnection;
  	
  	// Set up SIGINT handler.
  	
  	signal( SIGINT, SIG_IGN );
  	err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
  	require_noerr( err, exit );
  	dispatch_resume( signalSource );
  	
  	// Check command parameters.
  	
  	if( gGetAddrInfo_TimeLimitSecs < 0 )
  	{
  		FPrintF( stderr, "Invalid time limit: %d s.\n", gGetAddrInfo_TimeLimitSecs );
  		err = kParamErr;
  		goto exit;
  	}
  	
  	// Create context.
  	
  	context = (GetAddrInfoContext *) calloc( 1, sizeof( *context ) );
  	require_action( context, exit, err = kNoMemoryErr );
  	
  	// Create main connection.
  	
  	if( gConnectionOpt )
  	{
  		err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL );
  		require_noerr_quiet( err, exit );
  		useMainConnection = true;
  	}
  	else
  	{
  		useMainConnection = false;
  	}
  	
  	// Get flags.
  	
  	context->flags = GetDNSSDFlagsFromOpts();
  	if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection;
  	
  	// Get interface.
  	
  	err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
  	require_noerr_quiet( err, exit );
  	
  	// Set remaining parameters.
  	
  	context->name			= gGetAddrInfo_Name;
  	context->timeLimitSecs	= gGetAddrInfo_TimeLimitSecs;
  	if( gGetAddrInfo_ProtocolIPv4 ) context->protocols |= kDNSServiceProtocol_IPv4;
  	if( gGetAddrInfo_ProtocolIPv6 ) context->protocols |= kDNSServiceProtocol_IPv6;
  	if( gGetAddrInfo_OneShot )
  	{
  		context->oneShotMode	= true;
  		context->needIPv4		= ( gGetAddrInfo_ProtocolIPv4 || !gGetAddrInfo_ProtocolIPv6 ) ? true : false;
  		context->needIPv6		= ( gGetAddrInfo_ProtocolIPv6 || !gGetAddrInfo_ProtocolIPv4 ) ? true : false;
  	}
  	
  	// Print prologue.
  	
  	GetAddrInfoPrintPrologue( context );
  	
  	// Start operation.
  	
  	if( useMainConnection ) sdRef = context->mainRef;
  	err = DNSServiceGetAddrInfo( &sdRef, context->flags, context->ifIndex, context->protocols, context->name,
  		GetAddrInfoCallback, context );
  	require_noerr( err, exit );
  	
  	context->opRef = sdRef;
  	if( !useMainConnection )
  	{
  		err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() );
  		require_noerr( err, exit );
  	}
  	
  	// Set time limit.
  	
  	if( context->timeLimitSecs > 0 )
  	{
  		dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(),
  			kExitReason_TimeLimit, Exit );
  	}
  	dispatch_main();
  	
  exit:
  	dispatch_source_forget( &signalSource );
  	if( context ) GetAddrInfoContextFree( context );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	GetAddrInfoPrintPrologue
  //===========================================================================================================================
  
  static void	GetAddrInfoPrintPrologue( const GetAddrInfoContext *inContext )
  {
  	const int		timeLimitSecs = inContext->timeLimitSecs;
  	char			ifName[ kInterfaceNameBufLen ];
  	char			time[ kTimestampBufLen ];
  	
  	InterfaceIndexToName( inContext->ifIndex, ifName );
  	
  	FPrintF( stdout, "Flags:      %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
  	FPrintF( stdout, "Interface:  %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
  	FPrintF( stdout, "Protocols:  %#{flags}\n",	inContext->protocols, kDNSServiceProtocolDescriptors );
  	FPrintF( stdout, "Name:       %s\n",		inContext->name );
  	FPrintF( stdout, "Mode:       %s\n",		inContext->oneShotMode ? "one-shot" : "continuous" );
  	FPrintF( stdout, "Time limit: " );
  	if( timeLimitSecs > 0 )	FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
  	else					FPrintF( stdout, "∞\n" );
  	FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
  	FPrintF( stdout, "---\n" );
  }
  
  //===========================================================================================================================
  //	GetAddrInfoContextFree
  //===========================================================================================================================
  
  static void	GetAddrInfoContextFree( GetAddrInfoContext *inContext )
  {
  	DNSServiceForget( &inContext->opRef );
  	DNSServiceForget( &inContext->mainRef );
  	free( inContext );
  }
  
  //===========================================================================================================================
  //	GetAddrInfoCallback
  //===========================================================================================================================
  
  static void DNSSD_API
  	GetAddrInfoCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inHostname,
  		const struct sockaddr *	inSockAddr,
  		uint32_t				inTTL,
  		void *					inContext )
  {
  	GetAddrInfoContext * const		context = (GetAddrInfoContext *) inContext;
  	OSStatus						err;
  	const char *					addrStr;
  	char							addrStrBuf[ kSockAddrStringMaxSize ];
  	char							time[ kTimestampBufLen ];
  	
  	Unused( inSDRef );
  	
  	GetTimestampStr( time );
  	
  	switch( inError )
  	{
  		case kDNSServiceErr_NoError:
  		case kDNSServiceErr_NoSuchRecord:
  			err = kNoErr;
  			break;
  		
  		case kDNSServiceErr_Timeout:
  			Exit( kExitReason_Timeout );
  		
  		default:
  			err = inError;
  			goto exit;
  	}
  	
  	if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
  	{
  		dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
  		err = kTypeErr;
  		goto exit;
  	}
  	
  	if( !inError )
  	{
  		err = SockAddrToString( inSockAddr, kSockAddrStringFlagsNone, addrStrBuf );
  		require_noerr( err, exit );
  		addrStr = addrStrBuf;
  	}
  	else
  	{
  		addrStr = ( inSockAddr->sa_family == AF_INET ) ? "No Such Record (A)" : "No Such Record (AAAA)";
  	}
  	
  	if( !context->printedHeader )
  	{
  		FPrintF( stdout, "%-26s  A/R Flags IF %-32s %-38s %6s\n", "Timestamp", "Hostname", "Address", "TTL" );
  		context->printedHeader = true;
  	}
  	FPrintF( stdout, "%-26s  %s %5X %2d %-32s %-38s %6u\n",
  		time, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inHostname, addrStr, inTTL );
  	
  	if( context->oneShotMode )
  	{
  		if( inFlags & kDNSServiceFlagsAdd )
  		{
  			if( inSockAddr->sa_family == AF_INET )	context->needIPv4 = false;
  			else									context->needIPv6 = false;
  		}
  		if( !( inFlags & kDNSServiceFlagsMoreComing ) && !context->needIPv4 && !context->needIPv6 )
  		{
  			Exit( kExitReason_OneShotDone );
  		}
  	}
  	
  exit:
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	QueryRecordCmd
  //===========================================================================================================================
  
  typedef struct
  {
  	DNSServiceRef		mainRef;		// Main sdRef for shared connection.
  	DNSServiceRef		opRef;			// sdRef for the DNSServiceQueryRecord operation.
  	const char *		recordName;		// Resource record name argument for DNSServiceQueryRecord().
  	DNSServiceFlags		flags;			// Flags argument for DNSServiceQueryRecord().
  	uint32_t			ifIndex;		// Interface index argument for DNSServiceQueryRecord().
  	int					timeLimitSecs;	// Time limit for the DNSServiceQueryRecord() operation in seconds.
  	uint16_t			recordType;		// Resource record type argument for DNSServiceQueryRecord().
  	Boolean				printedHeader;	// True if the results header was printed.
  	Boolean				oneShotMode;	// True if command is done after the first set of results (one-shot mode).
  	Boolean				gotRecord;		// True if in one-shot mode and received at least one record of the desired type.
  	Boolean				printRawRData;	// True if RDATA results are not to be formatted when printed.
  	
  }	QueryRecordContext;
  
  static void	QueryRecordPrintPrologue( const QueryRecordContext *inContext );
  static void	QueryRecordContextFree( QueryRecordContext *inContext );
  static void DNSSD_API
  	QueryRecordCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inFullName,
  		uint16_t				inType,
  		uint16_t				inClass,
  		uint16_t				inRDataLen,
  		const void *			inRDataPtr,
  		uint32_t				inTTL,
  		void *					inContext );
  
  static void	QueryRecordCmd( void )
  {
  	OSStatus					err;
  	DNSServiceRef				sdRef;
  	QueryRecordContext *		context			= NULL;
  	dispatch_source_t			signalSource	= NULL;
  	int							useMainConnection;
  	
  	// Set up SIGINT handler.
  	
  	signal( SIGINT, SIG_IGN );
  	err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
  	require_noerr( err, exit );
  	dispatch_resume( signalSource );
  	
  	// Create context.
  	
  	context = (QueryRecordContext *) calloc( 1, sizeof( *context ) );
  	require_action( context, exit, err = kNoMemoryErr );
  	
  	// Check command parameters.
  	
  	if( gQueryRecord_TimeLimitSecs < 0 )
  	{
  		FPrintF( stderr, "Invalid time limit: %d seconds.\n", gQueryRecord_TimeLimitSecs );
  		err = kParamErr;
  		goto exit;
  	}
  	
  	// Create main connection.
  	
  	if( gConnectionOpt )
  	{
  		err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL );
  		require_noerr_quiet( err, exit );
  		useMainConnection = true;
  	}
  	else
  	{
  		useMainConnection = false;
  	}
  	
  	// Get flags.
  	
  	context->flags = GetDNSSDFlagsFromOpts();
  	if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection;
  	
  	// Get interface.
  	
  	err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
  	require_noerr_quiet( err, exit );
  	
  	// Get record type.
  	
  	err = RecordTypeFromArgString( gQueryRecord_Type, &context->recordType );
  	require_noerr( err, exit );
  	
  	// Set remaining parameters.
  	
  	context->recordName		= gQueryRecord_Name;
  	context->timeLimitSecs	= gQueryRecord_TimeLimitSecs;
  	context->oneShotMode	= gQueryRecord_OneShot	? true : false;
  	context->printRawRData	= gQueryRecord_RawRData	? true : false;
  	
  	// Print prologue.
  	
  	QueryRecordPrintPrologue( context );
  	
  	// Start operation.
  	
  	if( useMainConnection ) sdRef = context->mainRef;
  	err = DNSServiceQueryRecord( &sdRef, context->flags, context->ifIndex, context->recordName, context->recordType,
  		kDNSServiceClass_IN, QueryRecordCallback, context );
  	require_noerr( err, exit );
  	
  	context->opRef = sdRef;
  	if( !useMainConnection )
  	{
  		err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() );
  		require_noerr( err, exit );
  	}
  	
  	// Set time limit.
  	
  	if( context->timeLimitSecs > 0 )
  	{
  		dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_TimeLimit,
  			Exit );
  	}
  	dispatch_main();
  	
  exit:
  	dispatch_source_forget( &signalSource );
  	if( context ) QueryRecordContextFree( context );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	QueryRecordContextFree
  //===========================================================================================================================
  
  static void	QueryRecordContextFree( QueryRecordContext *inContext )
  {
  	DNSServiceForget( &inContext->opRef );
  	DNSServiceForget( &inContext->mainRef );
  	free( inContext );
  }
  
  //===========================================================================================================================
  //	QueryRecordPrintPrologue
  //===========================================================================================================================
  
  static void	QueryRecordPrintPrologue( const QueryRecordContext *inContext )
  {
  	const int		timeLimitSecs = inContext->timeLimitSecs;
  	char			ifName[ kInterfaceNameBufLen ];
  	char			time[ kTimestampBufLen ];
  	
  	InterfaceIndexToName( inContext->ifIndex, ifName );
  	
  	FPrintF( stdout, "Flags:       %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
  	FPrintF( stdout, "Interface:   %d (%s)\n",		(int32_t) inContext->ifIndex, ifName );
  	FPrintF( stdout, "Name:        %s\n",			inContext->recordName );
  	FPrintF( stdout, "Type:        %s (%u)\n",		RecordTypeToString( inContext->recordType ), inContext->recordType );
  	FPrintF( stdout, "Mode:        %s\n",			inContext->oneShotMode ? "one-shot" : "continuous" );
  	FPrintF( stdout, "Time limit:  " );
  	if( timeLimitSecs > 0 )	FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
  	else					FPrintF( stdout, "∞\n" );
  	FPrintF( stdout, "Start time:  %s\n", GetTimestampStr( time ) );
  	FPrintF( stdout, "---\n" );
  	
  }
  
  //===========================================================================================================================
  //	QueryRecordCallback
  //===========================================================================================================================
  
  static void DNSSD_API
  	QueryRecordCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inFullName,
  		uint16_t				inType,
  		uint16_t				inClass,
  		uint16_t				inRDataLen,
  		const void *			inRDataPtr,
  		uint32_t				inTTL,
  		void *					inContext )
  {
  	QueryRecordContext * const		context		= (QueryRecordContext *) inContext;
  	OSStatus						err;
  	char *							rdataStr	= NULL;
  	char							time[ kTimestampBufLen ];
  	
  	Unused( inSDRef );
  	
  	GetTimestampStr( time );
  	
  	switch( inError )
  	{
  		case kDNSServiceErr_NoError:
  		case kDNSServiceErr_NoSuchRecord:
  			err = kNoErr;
  			break;
  		
  		case kDNSServiceErr_Timeout:
  			Exit( kExitReason_Timeout );
  		
  		default:
  			err = inError;
  			goto exit;
  	}
  	
  	if( inError == kDNSServiceErr_NoSuchRecord )
  	{
  		ASPrintF( &rdataStr, "No Such Record" );
  	}
  	else
  	{
  		if( !context->printRawRData ) DNSRecordDataToString( inRDataPtr, inRDataLen, inType, NULL, 0, &rdataStr );
  		if( !rdataStr )
  		{
  			ASPrintF( &rdataStr, "%#H", inRDataPtr, inRDataLen, INT_MAX );
  			require_action( rdataStr, exit, err = kNoMemoryErr );
  		}
  	}
  	
  	if( !context->printedHeader )
  	{
  		FPrintF( stdout, "%-26s  A/R Flags IF %-32s %-5s %-5s %6s RData\n", "Timestamp", "Name", "Type", "Class", "TTL" );
  		context->printedHeader = true;
  	}
  	FPrintF( stdout, "%-26s  %-3s %5X %2d %-32s %-5s %?-5s%?5u %6u %s\n",
  		time, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inFullName, RecordTypeToString( inType ),
  		( inClass == kDNSServiceClass_IN ), "IN", ( inClass != kDNSServiceClass_IN ), inClass, inTTL, rdataStr );
  	
  	if( context->oneShotMode )
  	{
  		if( ( inFlags & kDNSServiceFlagsAdd ) &&
  			( ( context->recordType == kDNSServiceType_ANY ) || ( context->recordType == inType ) ) )
  		{
  			context->gotRecord = true;
  		}
  		if( !( inFlags & kDNSServiceFlagsMoreComing ) && context->gotRecord ) Exit( kExitReason_OneShotDone );
  	}
  	
  exit:
  	FreeNullSafe( rdataStr );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	RegisterCmd
  //===========================================================================================================================
  
  typedef struct
  {
  	DNSRecordRef		recordRef;	// Reference returned by DNSServiceAddRecord().
  	uint8_t *			dataPtr;	// Record data.
  	size_t				dataLen;	// Record data length.
  	uint32_t			ttl;		// Record TTL value.
  	uint16_t			type;		// Record type.
  	
  }	ExtraRecord;
  
  typedef struct
  {
  	DNSServiceRef		opRef;				// sdRef for DNSServiceRegister operation.
  	const char *		name;				// Service name argument for DNSServiceRegister().
  	const char *		type;				// Service type argument for DNSServiceRegister().
  	const char *		domain;				// Domain in which advertise the service.
  	uint8_t *			txtPtr;				// Service TXT record data. (malloc'd)
  	size_t				txtLen;				// Service TXT record data len.
  	ExtraRecord *		extraRecords;		// Array of extra records to add to registered service.
  	size_t				extraRecordsCount;	// Number of extra records.
  	uint8_t *			updateTXTPtr;		// Pointer to record data for TXT record update. (malloc'd)
  	size_t				updateTXTLen;		// Length of record data for TXT record update.
  	uint32_t			updateTTL;			// TTL of updated TXT record.
  	int					updateDelayMs;		// Post-registration TXT record update delay in milliseconds.
  	DNSServiceFlags		flags;				// Flags argument for DNSServiceRegister().
  	uint32_t			ifIndex;			// Interface index argument for DNSServiceRegister().
  	int					lifetimeMs;			// Lifetime of the record registration in milliseconds.
  	uint16_t			port;				// Service instance's port number.
  	Boolean				printedHeader;		// True if results header was printed.
  	Boolean				didRegister;		// True if service was registered.
  	
  }	RegisterContext;
  
  static void	RegisterPrintPrologue( const RegisterContext *inContext );
  static void	RegisterContextFree( RegisterContext *inContext );
  static void DNSSD_API
  	RegisterCallback(
  		DNSServiceRef		inSDRef,
  		DNSServiceFlags		inFlags,
  		DNSServiceErrorType	inError,
  		const char *		inName,
  		const char *		inType,
  		const char *		inDomain,
  		void *				inContext );
  static void	RegisterUpdate( void *inContext );
  
  static void	RegisterCmd( void )
  {
  	OSStatus				err;
  	RegisterContext *		context			= NULL;
  	dispatch_source_t		signalSource	= NULL;
  	
  	// Set up SIGINT handler.
  	
  	signal( SIGINT, SIG_IGN );
  	err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
  	require_noerr( err, exit );
  	dispatch_resume( signalSource );
  	
  	// Create context.
  	
  	context = (RegisterContext *) calloc( 1, sizeof( *context ) );
  	require_action( context, exit, err = kNoMemoryErr );
  	
  	// Check command parameters.
  	
  	if( ( gRegister_Port < 0 ) || ( gRegister_Port > UINT16_MAX ) )
  	{
  		FPrintF( stderr, "Port number %d is out-of-range.\n", gRegister_Port );
  		err = kParamErr;
  		goto exit;
  	}
  	
  	if( ( gAddRecord_DataCount != gAddRecord_TypesCount ) || ( gAddRecord_TTLsCount != gAddRecord_TypesCount ) )
  	{
  		FPrintF( stderr, "There are missing additional record parameters.\n" );
  		err = kParamErr;
  		goto exit;
  	}
  	
  	// Get flags.
  	
  	context->flags = GetDNSSDFlagsFromOpts();
  	
  	// Get interface index.
  	
  	err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
  	require_noerr_quiet( err, exit );
  	
  	// Get TXT record data.
  	
  	if( gRegister_TXT )
  	{
  		err = RecordDataFromArgString( gRegister_TXT, &context->txtPtr, &context->txtLen );
  		require_noerr_quiet( err, exit );
  	}
  	
  	// Set remaining parameters.
  	
  	context->name		= gRegister_Name;
  	context->type		= gRegister_Type;
  	context->domain		= gRegister_Domain;
  	context->port		= (uint16_t) gRegister_Port;
  	context->lifetimeMs	= gRegister_LifetimeMs;
  	
  	if( gAddRecord_TypesCount > 0 )
  	{
  		size_t		i;
  		
  		context->extraRecords = (ExtraRecord *) calloc( gAddRecord_TypesCount, sizeof( ExtraRecord ) );
  		require_action( context, exit, err = kNoMemoryErr );
  		context->extraRecordsCount = gAddRecord_TypesCount;
  		
  		for( i = 0; i < gAddRecord_TypesCount; ++i )
  		{
  			ExtraRecord * const		extraRecord = &context->extraRecords[ i ];
  			
  			err = RecordTypeFromArgString( gAddRecord_Types[ i ], &extraRecord->type );
  			require_noerr( err, exit );
  			
  			err = StringToUInt32( gAddRecord_TTLs[ i ], &extraRecord->ttl );
  			if( err )
  			{
  				FPrintF( stderr, "Invalid TTL value: %s\n", gAddRecord_TTLs[ i ] );
  				err = kParamErr;
  				goto exit;
  			}
  			
  			err = RecordDataFromArgString( gAddRecord_Data[ i ], &extraRecord->dataPtr, &extraRecord->dataLen );
  			require_noerr_quiet( err, exit );
  		}
  	}
  	
  	if( gUpdateRecord_Data )
  	{
  		err = RecordDataFromArgString( gUpdateRecord_Data, &context->updateTXTPtr, &context->updateTXTLen );
  		require_noerr_quiet( err, exit );
  		
  		context->updateTTL		= (uint32_t) gUpdateRecord_TTL;
  		context->updateDelayMs	= gUpdateRecord_DelayMs;
  	}
  	
  	// Print prologue.
  	
  	RegisterPrintPrologue( context );
  	
  	// Start operation.
  	
  	err = DNSServiceRegister( &context->opRef, context->flags, context->ifIndex, context->name, context->type,
  		context->domain, NULL, ntohs( context->port ), (uint16_t) context->txtLen, context->txtPtr,
  		RegisterCallback, context );
  	ForgetMem( &context->txtPtr );
  	require_noerr( err, exit );
  	
  	err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() );
  	require_noerr( err, exit );
  	
  	dispatch_main();
  	
  exit:
  	dispatch_source_forget( &signalSource );
  	if( context ) RegisterContextFree( context );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	RegisterPrintPrologue
  //===========================================================================================================================
  
  static void	RegisterPrintPrologue( const RegisterContext *inContext )
  {
  	size_t		i;
  	int			infinite;
  	char		ifName[ kInterfaceNameBufLen ];
  	char		time[ kTimestampBufLen ];
  	
  	InterfaceIndexToName( inContext->ifIndex, ifName );
  	
  	FPrintF( stdout, "Flags:      %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
  	FPrintF( stdout, "Interface:  %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
  	FPrintF( stdout, "Name:       %s\n",		inContext->name ? inContext->name : "<NULL>" );
  	FPrintF( stdout, "Type:       %s\n",		inContext->type );
  	FPrintF( stdout, "Domain:     %s\n",		inContext->domain ? inContext->domain : "<NULL> (default domains)" );
  	FPrintF( stdout, "Port:       %u\n",		inContext->port );
  	FPrintF( stdout, "TXT data:   %#{txt}\n",	inContext->txtPtr, inContext->txtLen );
  	infinite = ( inContext->lifetimeMs < 0 ) ? true : false;
  	FPrintF( stdout, "Lifetime:   %?s%?d ms\n",	infinite, "∞", !infinite, inContext->lifetimeMs );
  	if( inContext->updateTXTPtr )
  	{
  		FPrintF( stdout, "\nUpdate record:\n" );
  		FPrintF( stdout, "    Delay:    %d ms\n",	( inContext->updateDelayMs > 0 ) ? inContext->updateDelayMs : 0 );
  		FPrintF( stdout, "    TTL:      %u%?s\n",
  			inContext->updateTTL, inContext->updateTTL == 0, " (system will use a default value.)" );
  		FPrintF( stdout, "    TXT data: %#{txt}\n",	inContext->updateTXTPtr, inContext->updateTXTLen );
  	}
  	if( inContext->extraRecordsCount > 0 ) FPrintF( stdout, "\n" );
  	for( i = 0; i < inContext->extraRecordsCount; ++i )
  	{
  		const ExtraRecord *		record = &inContext->extraRecords[ i ];
  		
  		FPrintF( stdout, "Extra record %zu:\n",		i + 1 );
  		FPrintF( stdout, "    Type:  %s (%u)\n",	RecordTypeToString( record->type ), record->type );
  		FPrintF( stdout, "    TTL:   %u%?s\n",		record->ttl, record->ttl == 0, " (system will use a default value.)" );
  		FPrintF( stdout, "    RData: %#H\n\n",		record->dataPtr, (int) record->dataLen, INT_MAX );
  	}
  	FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
  	FPrintF( stdout, "---\n" );
  }
  
  //===========================================================================================================================
  //	RegisterContextFree
  //===========================================================================================================================
  
  static void	RegisterContextFree( RegisterContext *inContext )
  {
  	ExtraRecord *					record;
  	const ExtraRecord * const		end = inContext->extraRecords + inContext->extraRecordsCount;
  	
  	DNSServiceForget( &inContext->opRef );
  	ForgetMem( &inContext->txtPtr );
  	for( record = inContext->extraRecords; record < end; ++record )
  	{
  		check( !record->recordRef );
  		ForgetMem( &record->dataPtr );
  	}
  	ForgetMem( &inContext->extraRecords );
  	ForgetMem( &inContext->updateTXTPtr );
  	free( inContext );
  }
  
  //===========================================================================================================================
  //	RegisterCallback
  //===========================================================================================================================
  
  static void DNSSD_API
  	RegisterCallback(
  		DNSServiceRef		inSDRef,
  		DNSServiceFlags		inFlags,
  		DNSServiceErrorType	inError,
  		const char *		inName,
  		const char *		inType,
  		const char *		inDomain,
  		void *				inContext )
  {
  	RegisterContext * const		context = (RegisterContext *) inContext;
  	OSStatus					err;
  	char						time[ kTimestampBufLen ];
  	
  	Unused( inSDRef );
  	
  	GetTimestampStr( time );
  	
  	if( !context->printedHeader )
  	{
  		FPrintF( stdout, "%-26s  A/R Flags Service\n", "Timestamp" );
  		context->printedHeader = true;
  	}
  	FPrintF( stdout, "%-26s  %-3s %5X %s.%s%s %?#m\n",
  		time, AddRmvString( inFlags ), inFlags, inName, inType, inDomain, inError, inError );
  	
  	require_noerr_action_quiet( inError, exit, err = inError );
  	
  	if( !context->didRegister && ( inFlags & kDNSServiceFlagsAdd ) )
  	{
  		context->didRegister = true;
  		if( context->updateTXTPtr )
  		{
  			if( context->updateDelayMs > 0 )
  			{
  				dispatch_after_f( dispatch_time_milliseconds( context->updateDelayMs ), dispatch_get_main_queue(),
  					context, RegisterUpdate );
  			}
  			else
  			{
  				RegisterUpdate( context );
  			}
  		}
  		if( context->extraRecordsCount > 0 )
  		{
  			ExtraRecord *					record;
  			const ExtraRecord * const		end = context->extraRecords + context->extraRecordsCount;
  			
  			for( record = context->extraRecords; record < end; ++record )
  			{
  				err = DNSServiceAddRecord( context->opRef, &record->recordRef, 0, record->type,
  					(uint16_t) record->dataLen, record->dataPtr, record->ttl );
  				require_noerr( err, exit );
  			}
  		}
  		if( context->lifetimeMs == 0 )
  		{
  			Exit( kExitReason_TimeLimit );
  		}
  		else if( context->lifetimeMs > 0 )
  		{
  			dispatch_after_f( dispatch_time_milliseconds( context->lifetimeMs ), dispatch_get_main_queue(),
  				kExitReason_TimeLimit, Exit );
  		}
  	}
  	err = kNoErr;
  	
  exit:
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	RegisterUpdate
  //===========================================================================================================================
  
  static void	RegisterUpdate( void *inContext )
  {
  	OSStatus					err;
  	RegisterContext * const		context = (RegisterContext *) inContext;
  	
  	err = DNSServiceUpdateRecord( context->opRef, NULL, 0, (uint16_t) context->updateTXTLen, context->updateTXTPtr,
  		context->updateTTL );
  	require_noerr( err, exit );
  	
  exit:
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	RegisterRecordCmd
  //===========================================================================================================================
  
  typedef struct
  {
  	DNSServiceRef		conRef;			// sdRef to be initialized by DNSServiceCreateConnection().
  	DNSRecordRef		recordRef;		// Registered record reference.
  	const char *		recordName;		// Name of resource record.
  	uint8_t *			dataPtr;		// Pointer to resource record data.
  	size_t				dataLen;		// Length of resource record data.
  	uint32_t			ttl;			// TTL value of resource record in seconds.
  	uint32_t			ifIndex;		// Interface index argument for DNSServiceRegisterRecord().
  	DNSServiceFlags		flags;			// Flags argument for DNSServiceRegisterRecord().
  	int					lifetimeMs;		// Lifetime of the record registration in milliseconds.
  	uint16_t			recordType;		// Resource record type.
  	uint8_t *			updateDataPtr;	// Pointer to data for record update. (malloc'd)
  	size_t				updateDataLen;	// Length of data for record update.
  	uint32_t			updateTTL;		// TTL for updated record.
  	int					updateDelayMs;	// Post-registration record update delay in milliseconds.
  	Boolean				didRegister;	// True if the record was registered.
  	
  }	RegisterRecordContext;
  
  static void	RegisterRecordPrintPrologue( const RegisterRecordContext *inContext );
  static void	RegisterRecordContextFree( RegisterRecordContext *inContext );
  static void DNSSD_API
  	RegisterRecordCallback(
  		DNSServiceRef		inSDRef,
  		DNSRecordRef		inRecordRef,
  		DNSServiceFlags		inFlags,
  		DNSServiceErrorType	inError,
  		void *				inContext );
  static void	RegisterRecordUpdate( void *inContext );
  
  static void	RegisterRecordCmd( void )
  {
  	OSStatus					err;
  	RegisterRecordContext *		context			= NULL;
  	dispatch_source_t			signalSource	= NULL;
  	
  	// Set up SIGINT handler.
  	
  	signal( SIGINT, SIG_IGN );
  	err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
  	require_noerr( err, exit );
  	dispatch_resume( signalSource );
  	
  	// Create context.
  	
  	context = (RegisterRecordContext *) calloc( 1, sizeof( *context ) );
  	require_action( context, exit, err = kNoMemoryErr );
  	
  	// Create connection.
  	
  	err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->conRef, NULL );
  	require_noerr_quiet( err, exit );
  	
  	// Get flags.
  	
  	context->flags = GetDNSSDFlagsFromOpts();
  	
  	// Get interface.
  	
  	err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
  	require_noerr_quiet( err, exit );
  	
  	// Get record type.
  	
  	err = RecordTypeFromArgString( gRegisterRecord_Type, &context->recordType );
  	require_noerr( err, exit );
  	
  	// Get record data.
  	
  	if( gRegisterRecord_Data )
  	{
  		err = RecordDataFromArgString( gRegisterRecord_Data, &context->dataPtr, &context->dataLen );
  		require_noerr_quiet( err, exit );
  	}
  	
  	// Set remaining parameters.
  	
  	context->recordName	= gRegisterRecord_Name;
  	context->ttl		= (uint32_t) gRegisterRecord_TTL;
  	context->lifetimeMs	= gRegisterRecord_LifetimeMs;
  	
  	// Get update data.
  	
  	if( gRegisterRecord_UpdateData )
  	{
  		err = RecordDataFromArgString( gRegisterRecord_UpdateData, &context->updateDataPtr, &context->updateDataLen );
  		require_noerr_quiet( err, exit );
  		
  		context->updateTTL		= (uint32_t) gRegisterRecord_UpdateTTL;
  		context->updateDelayMs	= gRegisterRecord_UpdateDelayMs;
  	}
  	
  	// Print prologue.
  	
  	RegisterRecordPrintPrologue( context );
  	
  	// Start operation.
  	
  	err = DNSServiceRegisterRecord( context->conRef, &context->recordRef, context->flags, context->ifIndex,
  		context->recordName, context->recordType, kDNSServiceClass_IN, (uint16_t) context->dataLen, context->dataPtr,
  		context->ttl, RegisterRecordCallback, context );
  	if( err )
  	{
  		FPrintF( stderr, "DNSServiceRegisterRecord() returned %#m\n", err );
  		goto exit;
  	}
  	
  	dispatch_main();
  	
  exit:
  	dispatch_source_forget( &signalSource );
  	if( context ) RegisterRecordContextFree( context );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	RegisterRecordPrintPrologue
  //===========================================================================================================================
  
  static void	RegisterRecordPrintPrologue( const RegisterRecordContext *inContext )
  {
  	int			infinite;
  	char		time[ kTimestampBufLen ];
  	char		ifName[ kInterfaceNameBufLen ];
  	
  	InterfaceIndexToName( inContext->ifIndex, ifName );
  	
  	FPrintF( stdout, "Flags:       %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
  	FPrintF( stdout, "Interface:   %d (%s)\n",		(int32_t) inContext->ifIndex, ifName );
  	FPrintF( stdout, "Name:        %s\n",			inContext->recordName );
  	FPrintF( stdout, "Type:        %s (%u)\n",		RecordTypeToString( inContext->recordType ), inContext->recordType );
  	FPrintF( stdout, "TTL:         %u\n",			inContext->ttl );
  	FPrintF( stdout, "Data:        %#H\n",			inContext->dataPtr, (int) inContext->dataLen, INT_MAX );
  	infinite = ( inContext->lifetimeMs < 0 ) ? true : false;
  	FPrintF( stdout, "Lifetime:    %?s%?d ms\n",	infinite, "∞", !infinite, inContext->lifetimeMs );
  	if( inContext->updateDataPtr )
  	{
  		FPrintF( stdout, "\nUpdate record:\n" );
  		FPrintF( stdout, "    Delay:    %d ms\n",	( inContext->updateDelayMs >= 0 ) ? inContext->updateDelayMs : 0 );
  		FPrintF( stdout, "    TTL:      %u%?s\n",
  			inContext->updateTTL, inContext->updateTTL == 0, " (system will use a default value.)" );
  		FPrintF( stdout, "    RData:    %#H\n",		inContext->updateDataPtr, (int) inContext->updateDataLen, INT_MAX );
  	}
  	FPrintF( stdout, "Start time:  %s\n",			GetTimestampStr( time ) );
  	FPrintF( stdout, "---\n" );
  }
  
  //===========================================================================================================================
  //	RegisterRecordContextFree
  //===========================================================================================================================
  
  static void	RegisterRecordContextFree( RegisterRecordContext *inContext )
  {
  	DNSServiceForget( &inContext->conRef );
  	ForgetMem( &inContext->dataPtr );
  	ForgetMem( &inContext->updateDataPtr );
  	free( inContext );
  }
  
  //===========================================================================================================================
  //	RegisterRecordCallback
  //===========================================================================================================================
  
  static void
  	RegisterRecordCallback(
  		DNSServiceRef		inSDRef,
  		DNSRecordRef		inRecordRef,
  		DNSServiceFlags		inFlags,
  		DNSServiceErrorType	inError,
  		void *				inContext )
  {
  	RegisterRecordContext *		context = (RegisterRecordContext *) inContext;
  	char						time[ kTimestampBufLen ];
  	
  	Unused( inSDRef );
  	Unused( inRecordRef );
  	Unused( inFlags );
  	Unused( context );
  	
  	GetTimestampStr( time );
  	FPrintF( stdout, "%s Record registration result (error %#m)\n", time, inError );
  	
  	if( !context->didRegister && !inError )
  	{
  		context->didRegister = true;
  		if( context->updateDataPtr )
  		{
  			if( context->updateDelayMs > 0 )
  			{
  				dispatch_after_f( dispatch_time_milliseconds( context->updateDelayMs ), dispatch_get_main_queue(),
  					context, RegisterRecordUpdate );
  			}
  			else
  			{
  				RegisterRecordUpdate( context );
  			}
  		}
  		if( context->lifetimeMs == 0 )
  		{
  			Exit( kExitReason_TimeLimit );
  		}
  		else if( context->lifetimeMs > 0 )
  		{
  			dispatch_after_f( dispatch_time_milliseconds( context->lifetimeMs ), dispatch_get_main_queue(),
  				kExitReason_TimeLimit, Exit );
  		}
  	}
  }
  
  //===========================================================================================================================
  //	RegisterRecordUpdate
  //===========================================================================================================================
  
  static void	RegisterRecordUpdate( void *inContext )
  {
  	OSStatus							err;
  	RegisterRecordContext * const		context = (RegisterRecordContext *) inContext;
  	
  	err = DNSServiceUpdateRecord( context->conRef, context->recordRef, 0, (uint16_t) context->updateDataLen,
  		context->updateDataPtr, context->updateTTL );
  	require_noerr( err, exit );
  	
  exit:
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	ResolveCmd
  //===========================================================================================================================
  
  typedef struct
  {
  	DNSServiceRef		mainRef;		// Main sdRef for shared connections.
  	DNSServiceRef		opRef;			// sdRef for the DNSServiceResolve operation.
  	DNSServiceFlags		flags;			// Flags argument for DNSServiceResolve().
  	const char *		name;			// Service name argument for DNSServiceResolve().
  	const char *		type;			// Service type argument for DNSServiceResolve().
  	const char *		domain;			// Domain argument for DNSServiceResolve().
  	uint32_t			ifIndex;		// Interface index argument for DNSServiceResolve().
  	int					timeLimitSecs;	// Time limit for the DNSServiceResolve operation in seconds.
  	
  }	ResolveContext;
  
  static void	ResolvePrintPrologue( const ResolveContext *inContext );
  static void	ResolveContextFree( ResolveContext *inContext );
  static void DNSSD_API
  	ResolveCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inFullName,
  		const char *			inHostname,
  		uint16_t				inPort,
  		uint16_t				inTXTLen,
  		const unsigned char *	inTXTPtr,
  		void *					inContext );
  
  static void	ResolveCmd( void )
  {
  	OSStatus				err;
  	DNSServiceRef			sdRef;
  	ResolveContext *		context			= NULL;
  	dispatch_source_t		signalSource	= NULL;
  	int						useMainConnection;
  	
  	// Set up SIGINT handler.
  	
  	signal( SIGINT, SIG_IGN );
  	err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
  	require_noerr( err, exit );
  	dispatch_resume( signalSource );
  	
  	// Create context.
  	
  	context = (ResolveContext *) calloc( 1, sizeof( *context ) );
  	require_action( context, exit, err = kNoMemoryErr );
  	
  	// Check command parameters.
  	
  	if( gResolve_TimeLimitSecs < 0 )
  	{
  		FPrintF( stderr, "Invalid time limit: %d seconds.\n", gResolve_TimeLimitSecs );
  		err = kParamErr;
  		goto exit;
  	}
  	
  	// Create main connection.
  	
  	if( gConnectionOpt )
  	{
  		err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL );
  		require_noerr_quiet( err, exit );
  		useMainConnection = true;
  	}
  	else
  	{
  		useMainConnection = false;
  	}
  	
  	// Get flags.
  	
  	context->flags = GetDNSSDFlagsFromOpts();
  	if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection;
  	
  	// Get interface index.
  	
  	err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
  	require_noerr_quiet( err, exit );
  	
  	// Set remaining parameters.
  	
  	context->name			= gResolve_Name;
  	context->type			= gResolve_Type;
  	context->domain			= gResolve_Domain;
  	context->timeLimitSecs	= gResolve_TimeLimitSecs;
  	
  	// Print prologue.
  	
  	ResolvePrintPrologue( context );
  	
  	// Start operation.
  	
  	if( useMainConnection ) sdRef = context->mainRef;
  	err = DNSServiceResolve( &sdRef, context->flags, context->ifIndex, context->name, context->type, context->domain,
  		ResolveCallback, NULL );
  	require_noerr( err, exit );
  	
  	context->opRef = sdRef;
  	if( !useMainConnection )
  	{
  		err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() );
  		require_noerr( err, exit );
  	}
  	
  	// Set time limit.
  	
  	if( context->timeLimitSecs > 0 )
  	{
  		dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(),
  			kExitReason_TimeLimit, Exit );
  	}
  	dispatch_main();
  	
  exit:
  	dispatch_source_forget( &signalSource );
  	if( context ) ResolveContextFree( context );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	ReconfirmCmd
  //===========================================================================================================================
  
  static void	ReconfirmCmd( void )
  {
  	OSStatus			err;
  	uint8_t *			rdataPtr = NULL;
  	size_t				rdataLen = 0;
  	DNSServiceFlags		flags;
  	uint32_t			ifIndex;
  	uint16_t			type, class;
  	char				ifName[ kInterfaceNameBufLen ];
  	
  	// Get flags.
  	
  	flags = GetDNSSDFlagsFromOpts();
  	
  	// Get interface index.
  	
  	err = InterfaceIndexFromArgString( gInterface, &ifIndex );
  	require_noerr_quiet( err, exit );
  	
  	// Get record type.
  	
  	err = RecordTypeFromArgString( gReconfirmRecord_Type, &type );
  	require_noerr( err, exit );
  	
  	// Get record data.
  	
  	if( gReconfirmRecord_Data )
  	{
  		err = RecordDataFromArgString( gReconfirmRecord_Data, &rdataPtr, &rdataLen );
  		require_noerr_quiet( err, exit );
  	}
  	
  	// Get record data.
  	
  	if( gReconfirmRecord_Class )
  	{
  		err = RecordClassFromArgString( gReconfirmRecord_Class, &class );
  		require_noerr( err, exit );
  	}
  	else
  	{
  		class = kDNSServiceClass_IN;
  	}
  	
  	// Print prologue.
  	
  	FPrintF( stdout, "Flags:     %#{flags}\n",	flags, kDNSServiceFlagsDescriptors );
  	FPrintF( stdout, "Interface: %d (%s)\n",	(int32_t) ifIndex, InterfaceIndexToName( ifIndex, ifName ) );
  	FPrintF( stdout, "Name:      %s\n",			gReconfirmRecord_Name );
  	FPrintF( stdout, "Type:      %s (%u)\n",	RecordTypeToString( type ), type );
  	FPrintF( stdout, "Class:     %s (%u)\n",	( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
  	FPrintF( stdout, "Data:      %#H\n",		rdataPtr, (int) rdataLen, INT_MAX );
  	FPrintF( stdout, "---\n" );
  	
  	err = DNSServiceReconfirmRecord( flags, ifIndex, gReconfirmRecord_Name, type, class, (uint16_t) rdataLen, rdataPtr );
  	FPrintF( stdout, "Error:     %#m\n", err );
  	
  exit:
  	FreeNullSafe( rdataPtr );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	ResolvePrintPrologue
  //===========================================================================================================================
  
  static void	ResolvePrintPrologue( const ResolveContext *inContext )
  {
  	const int		timeLimitSecs = inContext->timeLimitSecs;
  	char			ifName[ kInterfaceNameBufLen ];
  	char			time[ kTimestampBufLen ];
  	
  	InterfaceIndexToName( inContext->ifIndex, ifName );
  	
  	FPrintF( stdout, "Flags:      %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
  	FPrintF( stdout, "Interface:  %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
  	FPrintF( stdout, "Name:       %s\n",		inContext->name );
  	FPrintF( stdout, "Type:       %s\n",		inContext->type );
  	FPrintF( stdout, "Domain:     %s\n",		inContext->domain );
  	FPrintF( stdout, "Time limit: " );
  	if( timeLimitSecs > 0 )	FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
  	else					FPrintF( stdout, "∞\n" );
  	FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
  	FPrintF( stdout, "---\n" );
  }
  
  //===========================================================================================================================
  //	ResolveContextFree
  //===========================================================================================================================
  
  static void	ResolveContextFree( ResolveContext *inContext )
  {
  	DNSServiceForget( &inContext->opRef );
  	DNSServiceForget( &inContext->mainRef );
  	free( inContext );
  }
  
  //===========================================================================================================================
  //	ResolveCallback
  //===========================================================================================================================
  
  static void DNSSD_API
  	ResolveCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inFullName,
  		const char *			inHostname,
  		uint16_t				inPort,
  		uint16_t				inTXTLen,
  		const unsigned char *	inTXTPtr,
  		void *					inContext )
  {
  	char		time[ kTimestampBufLen ];
  	char		errorStr[ 64 ];
  	
  	Unused( inSDRef );
  	Unused( inFlags );
  	Unused( inContext );
  	
  	GetTimestampStr( time );
  	
  	if( inError ) SNPrintF( errorStr, sizeof( errorStr ), " error %#m", inError );
  	
  	FPrintF( stdout, "%s: %s can be reached at %s:%u (interface %d)%?s\n",
  		time, inFullName, inHostname, ntohs( inPort ), (int32_t) inInterfaceIndex, inError, errorStr );
  	if( inTXTLen == 1 )
  	{
  		FPrintF( stdout, " TXT record: %#H\n", inTXTPtr, (int) inTXTLen, INT_MAX );
  	}
  	else
  	{
  		FPrintF( stdout, " TXT record: %#{txt}\n", inTXTPtr, (size_t) inTXTLen );
  	}
  }
  
  //===========================================================================================================================
  //	GetAddrInfoPOSIXCmd
  //===========================================================================================================================
  
  #define AddressFamilyStr( X ) (				\
  	( (X) == AF_INET )		? "inet"	:	\
  	( (X) == AF_INET6 )		? "inet6"	:	\
  	( (X) == AF_UNSPEC )	? "unspec"	:	\
  							  "???" )
  
  static void	GetAddrInfoPOSIXCmd( void )
  {
  	OSStatus					err;
  	struct addrinfo				hints;
  	const struct addrinfo *		addrInfo;
  	struct addrinfo *			addrInfoList = NULL;
  	char						time[ kTimestampBufLen ];
  	
  	memset( &hints, 0, sizeof( hints ) );
  	hints.ai_socktype = SOCK_STREAM;
  	
  	// Set hints address family.
  	
  	if( !gGAIPOSIX_Family )										hints.ai_family = AF_UNSPEC;
  	else if( strcasecmp( gGAIPOSIX_Family, "inet" ) == 0 )		hints.ai_family = AF_INET;
  	else if( strcasecmp( gGAIPOSIX_Family, "inet6" ) == 0 )		hints.ai_family = AF_INET6;
  	else if( strcasecmp( gGAIPOSIX_Family, "unspec" ) == 0 )	hints.ai_family = AF_UNSPEC;
  	else
  	{
  		FPrintF( stderr, "Invalid address family: %s.\n", gGAIPOSIX_Family );
  		err = kParamErr;
  		goto exit;
  	}
  	
  	// Set hints flags.
  	
  	if( gGAIPOSIXFlag_AddrConfig )	hints.ai_flags |= AI_ADDRCONFIG;
  	if( gGAIPOSIXFlag_All )			hints.ai_flags |= AI_ALL;
  	if( gGAIPOSIXFlag_CanonName )	hints.ai_flags |= AI_CANONNAME;
  	if( gGAIPOSIXFlag_NumericHost )	hints.ai_flags |= AI_NUMERICHOST;
  	if( gGAIPOSIXFlag_NumericServ )	hints.ai_flags |= AI_NUMERICSERV;
  	if( gGAIPOSIXFlag_Passive )		hints.ai_flags |= AI_PASSIVE;
  	if( gGAIPOSIXFlag_V4Mapped )	hints.ai_flags |= AI_V4MAPPED;
  #if( defined( AI_V4MAPPED_CFG ) )
  	if( gGAIPOSIXFlag_V4MappedCFG )	hints.ai_flags |= AI_V4MAPPED_CFG;
  #endif
  #if( defined( AI_DEFAULT ) )
  	if( gGAIPOSIXFlag_Default )		hints.ai_flags |= AI_DEFAULT;
  #endif
  	
  	// Print prologue.
  	
  	FPrintF( stdout, "Hostname:       %s\n",	gGAIPOSIX_HostName );
  	FPrintF( stdout, "Servname:       %s\n",	gGAIPOSIX_ServName );
  	FPrintF( stdout, "Address family: %s\n",	AddressFamilyStr( hints.ai_family ) );
  	FPrintF( stdout, "Flags:          0x%X < ",	hints.ai_flags );
  	if( hints.ai_flags & AI_NUMERICSERV )	FPrintF( stdout, "AI_NUMERICSERV " );
  	if( hints.ai_flags & AI_V4MAPPED )		FPrintF( stdout, "AI_V4MAPPED " );
  	if( hints.ai_flags & AI_ADDRCONFIG )	FPrintF( stdout, "AI_ADDRCONFIG " );
  #if( defined( AI_V4MAPPED_CFG ) )
  	if( hints.ai_flags & AI_V4MAPPED_CFG )	FPrintF( stdout, "AI_V4MAPPED_CFG " );
  #endif
  	if( hints.ai_flags & AI_ALL )			FPrintF( stdout, "AI_ALL " );
  	if( hints.ai_flags & AI_NUMERICHOST )	FPrintF( stdout, "AI_NUMERICHOST " );
  	if( hints.ai_flags & AI_CANONNAME )		FPrintF( stdout, "AI_CANONNAME " );
  	if( hints.ai_flags & AI_PASSIVE )		FPrintF( stdout, "AI_PASSIVE " );
  	FPrintF( stdout, ">\n" );
  	FPrintF( stdout, "Start time:     %s\n", GetTimestampStr( time ) );
  	FPrintF( stdout, "---\n" );
  	
  	// Call getaddrinfo().
  	
  	err = getaddrinfo( gGAIPOSIX_HostName, gGAIPOSIX_ServName, &hints, &addrInfoList );
  	GetTimestampStr( time );
  	if( err )
  	{
  		FPrintF( stderr, "Error %#m: %s.\n", err, gai_strerror( err ) );
  	}
  	else
  	{
  		int		addrCount = 0;
  		
  		for( addrInfo = addrInfoList; addrInfo; addrInfo = addrInfo->ai_next ) { ++addrCount; }
  		
  		FPrintF( stdout, "Addresses (%d total):\n", addrCount );
  		for( addrInfo = addrInfoList; addrInfo; addrInfo = addrInfo->ai_next )
  		{
  			FPrintF( stdout, "%##a\n", addrInfo->ai_addr );
  		}
  	}
  	FPrintF( stdout, "---\n" );
  	FPrintF( stdout, "End time:       %s\n", time );
  	
  exit:
  	if( addrInfoList ) freeaddrinfo( addrInfoList );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	ReverseLookupCmd
  //===========================================================================================================================
  
  static void	ReverseLookupCmd( void )
  {
  	OSStatus					err;
  	QueryRecordContext *		context			= NULL;
  	DNSServiceRef				sdRef;
  	dispatch_source_t			signalSource	= NULL;
  	uint32_t					ipv4Addr;
  	uint8_t						ipv6Addr[ 16 ];
  	char						recordName[ ( 16 * 4 ) + 9 + 1 ];
  	int							useMainConnection;
  	
  	// Set up SIGINT handler.
  	
  	signal( SIGINT, SIG_IGN );
  	err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
  	require_noerr( err, exit );
  	dispatch_resume( signalSource );
  	
  	// Create context.
  	
  	context = (QueryRecordContext *) calloc( 1, sizeof( *context ) );
  	require_action( context, exit, err = kNoMemoryErr );
  	
  	// Check command parameters.
  	
  	if( gReverseLookup_TimeLimitSecs < 0 )
  	{
  		FPrintF( stderr, "Invalid time limit: %d s.\n", gReverseLookup_TimeLimitSecs );
  		err = kParamErr;
  		goto exit;
  	}
  	
  	// Create main connection.
  	
  	if( gConnectionOpt )
  	{
  		err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL );
  		require_noerr_quiet( err, exit );
  		useMainConnection = true;
  	}
  	else
  	{
  		useMainConnection = false;
  	}
  	
  	// Get flags.
  	
  	context->flags = GetDNSSDFlagsFromOpts();
  	if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection;
  	
  	// Get interface index.
  	
  	err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
  	require_noerr_quiet( err, exit );
  	
  	// Create reverse lookup record name.
  	
  	err = StringToIPv4Address( gReverseLookup_IPAddr, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix,
  		&ipv4Addr, NULL, NULL, NULL, NULL );
  	if( err )
  	{
  		char *		dst;
  		int			i;
  		
  		err = StringToIPv6Address( gReverseLookup_IPAddr,
  			kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope,
  			ipv6Addr, NULL, NULL, NULL, NULL );
  		if( err )
  		{
  			FPrintF( stderr, "Invalid IP address: \"%s\".\n", gReverseLookup_IPAddr );
  			err = kParamErr;
  			goto exit;
  		}
  		dst = recordName;
  		for( i = 15; i >= 0; --i )
  		{
  			*dst++ = kHexDigitsLowercase[ ipv6Addr[ i ] & 0x0F ];
  			*dst++ = '.';
  			*dst++ = kHexDigitsLowercase[ ipv6Addr[ i ] >> 4 ];
  			*dst++ = '.';
  		}
  		strcpy( dst, "ip6.arpa." );
  		check( ( strlen( recordName ) + 1 ) <= sizeof( recordName ) );
  	}
  	else
  	{
  		SNPrintF( recordName, sizeof( recordName ), "%u.%u.%u.%u.in-addr.arpa.",
  			  ipv4Addr         & 0xFF,
  			( ipv4Addr >>  8 ) & 0xFF,
  			( ipv4Addr >> 16 ) & 0xFF,
  			( ipv4Addr >> 24 ) & 0xFF );
  	}
  	
  	// Set remaining parameters.
  	
  	context->recordName		= recordName;
  	context->recordType		= kDNSServiceType_PTR;
  	context->timeLimitSecs	= gReverseLookup_TimeLimitSecs;
  	context->oneShotMode	= gReverseLookup_OneShot ? true : false;
  	
  	// Print prologue.
  	
  	QueryRecordPrintPrologue( context );
  	
  	// Start operation.
  	
  	if( useMainConnection ) sdRef = context->mainRef;
  	err = DNSServiceQueryRecord( &sdRef, context->flags, context->ifIndex, context->recordName, context->recordType,
  		kDNSServiceClass_IN, QueryRecordCallback, context );
  	require_noerr( err, exit );
  	
  	context->opRef = sdRef;
  	if( !useMainConnection )
  	{
  		err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() );
  		require_noerr( err, exit );
  	}
  	
  	// Set time limit.
  	
  	if( context->timeLimitSecs > 0 )
  	{
  		dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(),
  			kExitReason_TimeLimit, Exit );
  	}
  	dispatch_main();
  	
  exit:
  	dispatch_source_forget( &signalSource );
  	if( context ) QueryRecordContextFree( context );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	BrowseAllCmd
  //===========================================================================================================================
  
  typedef struct BrowseDomain			BrowseDomain;
  typedef struct BrowseType			BrowseType;
  typedef struct BrowseOp				BrowseOp;
  typedef struct BrowseInstance		BrowseInstance;
  typedef struct BrowseIPAddr			BrowseIPAddr;
  
  typedef struct
  {
  	int						refCount;
  	DNSServiceRef			mainRef;
  	DNSServiceRef			domainsQuery;
  	const char *			domain;
  	BrowseDomain *			domainList;
  	char **					serviceTypes;
  	size_t					serviceTypesCount;
  	dispatch_source_t		exitTimer;
  	uint32_t				ifIndex;
  	int						pendingConnectCount;
  	int						browseTimeSecs;
  	int						connectTimeLimitSecs;
  	Boolean					includeAWDL;
  	Boolean					useColoredText;
  	
  }	BrowseAllContext;
  
  struct BrowseDomain
  {
  	BrowseDomain *			next;
  	char *					name;
  	DNSServiceRef			servicesQuery;
  	BrowseAllContext *		context;
  	BrowseType *			typeList;
  };
  
  struct BrowseType
  {
  	BrowseType *		next;
  	char *				name;
  	BrowseOp *			browseList;
  };
  
  struct BrowseOp
  {
  	BrowseOp *				next;
  	BrowseAllContext *		context;
  	DNSServiceRef			browse;
  	uint64_t				startTicks;
  	BrowseInstance *		instanceList;
  	uint32_t				ifIndex;
  	Boolean					isTCP;
  };
  
  struct BrowseInstance
  {
  	BrowseInstance *		next;
  	BrowseAllContext *		context;
  	char *					name;
  	uint64_t				foundTicks;
  	DNSServiceRef			resolve;
  	uint64_t				resolveStartTicks;
  	uint64_t				resolveDoneTicks;
  	DNSServiceRef			getAddr;
  	uint64_t				getAddrStartTicks;
  	BrowseIPAddr *			addrList;
  	uint8_t *				txtPtr;
  	size_t					txtLen;
  	char *					hostname;
  	uint32_t				ifIndex;
  	uint16_t				port;
  	Boolean					isTCP;
  };
  
  typedef enum
  {
  	kConnectStatus_None			= 0,
  	kConnectStatus_Pending		= 1,
  	kConnectStatus_Succeeded	= 2,
  	kConnectStatus_Failed		= 3
  	
  }	ConnectStatus;
  
  struct BrowseIPAddr
  {
  	BrowseIPAddr *			next;
  	sockaddr_ip				sip;
  	int						refCount;
  	BrowseAllContext *		context;
  	uint64_t				foundTicks;
  	AsyncConnectionRef		connection;
  	ConnectStatus			connectStatus;
  	CFTimeInterval			connectTimeSecs;
  	OSStatus				connectError;
  };
  
  static void	BrowseAllPrintPrologue( const BrowseAllContext *inContext );
  static void DNSSD_API
  	BrowseAllQueryDomainsCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inFullName,
  		uint16_t				inType,
  		uint16_t				inClass,
  		uint16_t				inRDataLen,
  		const void *			inRDataPtr,
  		uint32_t				inTTL,
  		void *					inContext );
  static void DNSSD_API
  	BrowseAllQueryCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inFullName,
  		uint16_t				inType,
  		uint16_t				inClass,
  		uint16_t				inRDataLen,
  		const void *			inRDataPtr,
  		uint32_t				inTTL,
  		void *					inContext );
  static void DNSSD_API
  	BrowseAllBrowseCallback(
  		DNSServiceRef		inSDRef,
  		DNSServiceFlags		inFlags,
  		uint32_t			inInterfaceIndex,
  		DNSServiceErrorType	inError,
  		const char *		inName,
  		const char *		inRegType,
  		const char *		inDomain,
  		void *				inContext );
  static void DNSSD_API
  	BrowseAllResolveCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inFullName,
  		const char *			inHostname,
  		uint16_t				inPort,
  		uint16_t				inTXTLen,
  		const unsigned char *	inTXTPtr,
  		void *					inContext );
  static void DNSSD_API
  	BrowseAllGAICallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inHostname,
  		const struct sockaddr *	inSockAddr,
  		uint32_t				inTTL,
  		void *					inContext );
  static void		BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg );
  static void		BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg );
  static void		BrowseAllStop( void *inContext );
  static void		BrowseAllExit( void *inContext );
  static OSStatus	BrowseAllAddDomain( BrowseAllContext *inContext, const char *inName );
  static OSStatus	BrowseAllRemoveDomain( BrowseAllContext *inContext, const char *inName );
  static void		BrowseAllContextRelease( BrowseAllContext *inContext );
  static OSStatus
  	BrowseAllAddServiceType(
  		BrowseAllContext *	inContext,
  		BrowseDomain *		inDomain,
  		const char *		inName,
  		uint32_t			inIfIndex,
  		Boolean				inIncludeAWDL );
  static OSStatus
  	BrowseAllRemoveServiceType(
  		BrowseAllContext *	inContext,
  		BrowseDomain *		inDomain,
  		const char *		inName,
  		uint32_t			inIfIndex );
  static OSStatus
  	BrowseAllAddServiceInstance(
  		BrowseAllContext *	inContext,
  		BrowseOp *			inBrowse,
  		const char *		inName,
  		const char *		inRegType,
  		const char *		inDomain,
  		uint32_t			inIfIndex );
  static OSStatus
  	BrowseAllRemoveServiceInstance(
  		BrowseAllContext *	inContext,
  		BrowseOp *			inBrowse,
  		const char *		inName,
  		uint32_t			inIfIndex );
  static OSStatus
  	BrowseAllAddIPAddress(
  		BrowseAllContext *		inContext,
  		BrowseInstance *		inInstance,
  		const struct sockaddr *	inSockAddr );
  static OSStatus
  	BrowseAllRemoveIPAddress(
  		BrowseAllContext *		inContext,
  		BrowseInstance *		inInstance,
  		const struct sockaddr *	inSockAddr );
  static void	BrowseDomainFree( BrowseDomain *inDomain );
  static void	BrowseTypeFree( BrowseType *inType );
  static void	BrowseOpFree( BrowseOp *inBrowse );
  static void	BrowseInstanceFree( BrowseInstance *inInstance );
  static void	BrowseIPAddrRelease( BrowseIPAddr *inAddr );
  static void	BrowseIPAddrReleaseList( BrowseIPAddr *inList );
  
  #define ForgetIPAddressList( X )		ForgetCustom( X, BrowseIPAddrReleaseList )
  #define ForgetBrowseAllContext( X )		ForgetCustom( X, BrowseAllContextRelease )
  
  #define kBrowseAllOpenFileMin		4096
  
  static void	BrowseAllCmd( void )
  {
  	OSStatus				err;
  	BrowseAllContext *		context = NULL;
  	
  	// Check command parameters.
  	
  	if( gBrowseAll_BrowseTimeSecs <= 0 )
  	{
  		FPrintF( stdout, "Invalid browse time: %d seconds.\n", gBrowseAll_BrowseTimeSecs );
  		err = kParamErr;
  		goto exit;
  	}
  	
  #if( TARGET_OS_POSIX )
  	// Set open file minimum.
  	
  	{
  		struct rlimit		fdLimits;
  		
  		err = getrlimit( RLIMIT_NOFILE, &fdLimits );
  		err = map_global_noerr_errno( err );
  		require_noerr( err, exit );
  		
  		if( fdLimits.rlim_cur < kBrowseAllOpenFileMin )
  		{
  			fdLimits.rlim_cur = kBrowseAllOpenFileMin;
  			err = setrlimit( RLIMIT_NOFILE, &fdLimits );
  			err = map_global_noerr_errno( err );
  			require_noerr( err, exit );
  		}
  	}
  #endif
  	
  	context = (BrowseAllContext *) calloc( 1, sizeof( *context ) );
  	require_action( context, exit, err = kNoMemoryErr );
  	
  	context->refCount				= 1;
  	context->domain					= gBrowseAll_Domain;
  	context->serviceTypes			= gBrowseAll_ServiceTypes;
  	context->serviceTypesCount		= gBrowseAll_ServiceTypesCount;
  	gBrowseAll_ServiceTypes			= NULL;
  	gBrowseAll_ServiceTypesCount	= 0;
  	context->browseTimeSecs			= gBrowseAll_BrowseTimeSecs;
  	context->connectTimeLimitSecs	= gBrowseAll_ConnectTimeLimitSecs;
  	context->includeAWDL			= gBrowseAll_IncludeAWDL ? true : false;
  #if( TARGET_OS_POSIX )
  	context->useColoredText			= isatty( STDOUT_FILENO ) ? true : false;
  #endif
  	
  	err = DNSServiceCreateConnection( &context->mainRef );
  	require_noerr( err, exit );
  	
  	err = DNSServiceSetDispatchQueue( context->mainRef, dispatch_get_main_queue() );
  	require_noerr( err, exit );
  	
  	// Set interface index.
  	
  	err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
  	require_noerr_quiet( err, exit );
  	
  	BrowseAllPrintPrologue( context );
  	
  	if( context->domain )
  	{
  		err = BrowseAllAddDomain( context, context->domain );
  		require_noerr( err, exit );
  	}
  	else
  	{
  		DNSServiceRef		sdRef;
  		
  		sdRef = context->mainRef;
  		err = DNSServiceQueryRecord( &sdRef, kDNSServiceFlagsShareConnection, kDNSServiceInterfaceIndexLocalOnly,
  			"b._dns-sd._udp.local.", kDNSServiceType_PTR, kDNSServiceClass_IN, BrowseAllQueryDomainsCallback, context );
  		require_noerr( err, exit );
  		
  		context->domainsQuery = sdRef;
  	}
  	
  	dispatch_after_f( dispatch_time_seconds( context->browseTimeSecs ), dispatch_get_main_queue(), context, BrowseAllStop );
  	dispatch_main();
  	
  exit:
  	if( context ) BrowseAllContextRelease( context );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	BrowseAllPrintPrologue
  //===========================================================================================================================
  
  static void	BrowseAllPrintPrologue( const BrowseAllContext *inContext )
  {
  	size_t		i;
  	char		ifName[ kInterfaceNameBufLen ];
  	char		time[ kTimestampBufLen ];
  	
  	InterfaceIndexToName( inContext->ifIndex, ifName );
  	
  	FPrintF( stdout, "Interface:          %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
  	FPrintF( stdout, "Service types:      ");
  	if( inContext->serviceTypesCount > 0 )
  	{
  		FPrintF( stdout, "%s", inContext->serviceTypes[ 0 ] );
  		for( i = 1; i < inContext->serviceTypesCount; ++i ) FPrintF( stdout, ", %s", inContext->serviceTypes[ i ] );
  		FPrintF( stdout, "\n" );
  	}
  	else
  	{
  		FPrintF( stdout, "all services\n" );
  	}
  	FPrintF( stdout, "Domain:             %s\n", inContext->domain ? inContext->domain : "default domains" );
  	FPrintF( stdout, "Browse time:        %d second%?c\n", inContext->browseTimeSecs, inContext->browseTimeSecs != 1, 's' );
  	FPrintF( stdout, "Connect time limit: %d second%?c\n",
  		inContext->connectTimeLimitSecs, inContext->connectTimeLimitSecs != 1, 's' );
  	FPrintF( stdout, "Start time:         %s\n", GetTimestampStr( time ) );
  	FPrintF( stdout, "---\n" );
  }
  
  //===========================================================================================================================
  //	BrowseAllQueryDomainsCallback
  //===========================================================================================================================
  
  static void DNSSD_API
  	BrowseAllQueryDomainsCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inFullName,
  		uint16_t				inType,
  		uint16_t				inClass,
  		uint16_t				inRDataLen,
  		const void *			inRDataPtr,
  		uint32_t				inTTL,
  		void *					inContext )
  {
  	OSStatus						err;
  	BrowseAllContext * const		context = (BrowseAllContext *) inContext;
  	char							domainStr[ kDNSServiceMaxDomainName ];
  	
  	Unused( inSDRef );
  	Unused( inInterfaceIndex );
  	Unused( inFullName );
  	Unused( inType );
  	Unused( inClass );
  	Unused( inTTL );
  	
  	err = inError;
  	require_noerr( err, exit );
  	
  	err = DomainNameToString( inRDataPtr, ( (const uint8_t *) inRDataPtr ) + inRDataLen, domainStr, NULL );
  	require_noerr( err, exit );
  	
  	if( inFlags & kDNSServiceFlagsAdd )
  	{
  		err = BrowseAllAddDomain( context, domainStr );
  		if( err == kDuplicateErr ) err = kNoErr;
  		require_noerr( err, exit );
  	}
  	else
  	{
  		err = BrowseAllRemoveDomain( context, domainStr );
  		if( err == kNotFoundErr ) err = kNoErr;
  		require_noerr( err, exit );
  	}
  	
  exit:
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	BrowseAllQueryCallback
  //===========================================================================================================================
  
  static void DNSSD_API
  	BrowseAllQueryCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inFullName,
  		uint16_t				inType,
  		uint16_t				inClass,
  		uint16_t				inRDataLen,
  		const void *			inRDataPtr,
  		uint32_t				inTTL,
  		void *					inContext )
  {
  	OSStatus					err;
  	BrowseDomain * const		domain			= (BrowseDomain *) inContext;
  	const uint8_t *				firstLabel;
  	const uint8_t *				secondLabel;
  	char *						serviceTypeStr	= NULL;
  	const uint8_t * const		end				= ( (uint8_t * ) inRDataPtr ) + inRDataLen;
  	
  	Unused( inSDRef );
  	Unused( inFullName );
  	Unused( inTTL );
  	Unused( inType );
  	Unused( inClass );
  	
  	err = inError;
  	require_noerr( err, exit );
  	
  	check( inType	== kDNSServiceType_PTR );
  	check( inClass	== kDNSServiceClass_IN );
  	require_action( inRDataLen > 0, exit, err = kSizeErr );
  	
  	firstLabel = inRDataPtr;
  	require_action_quiet( ( firstLabel + 1 + firstLabel[ 0 ] ) < end , exit, err = kUnderrunErr );
  	
  	secondLabel = firstLabel + 1 + firstLabel[ 0 ];
  	require_action_quiet( ( secondLabel + 1 + secondLabel[ 0 ] ) < end , exit, err = kUnderrunErr );
  	
  	ASPrintF( &serviceTypeStr, "%#s.%#s", firstLabel, secondLabel );
  	require_action( serviceTypeStr, exit, err = kNoMemoryErr );
  	
  	if( inFlags & kDNSServiceFlagsAdd )
  	{
  		err = BrowseAllAddServiceType( domain->context, domain, serviceTypeStr, inInterfaceIndex, false );
  		if( err == kDuplicateErr ) err = kNoErr;
  		require_noerr( err, exit );
  	}
  	else
  	{
  		err = BrowseAllRemoveServiceType( domain->context, domain, serviceTypeStr, inInterfaceIndex );
  		if( err == kNotFoundErr ) err = kNoErr;
  		require_noerr( err, exit );
  	}
  	
  exit:
  	FreeNullSafe( serviceTypeStr );
  }
  
  //===========================================================================================================================
  //	BrowseAllBrowseCallback
  //===========================================================================================================================
  
  static void DNSSD_API
  	BrowseAllBrowseCallback(
  		DNSServiceRef		inSDRef,
  		DNSServiceFlags		inFlags,
  		uint32_t			inInterfaceIndex,
  		DNSServiceErrorType	inError,
  		const char *		inName,
  		const char *		inRegType,
  		const char *		inDomain,
  		void *				inContext )
  {
  	OSStatus				err;
  	BrowseOp * const		browse = (BrowseOp *) inContext;
  	
  	Unused( inSDRef );
  	
  	err = inError;
  	require_noerr( err, exit );
  	
  	if( inFlags & kDNSServiceFlagsAdd )
  	{
  		err = BrowseAllAddServiceInstance( browse->context, browse, inName, inRegType, inDomain, inInterfaceIndex );
  		if( err == kDuplicateErr ) err = kNoErr;
  		require_noerr( err, exit );
  	}
  	else
  	{
  		err = BrowseAllRemoveServiceInstance( browse->context, browse, inName, inInterfaceIndex );
  		if( err == kNotFoundErr ) err = kNoErr;
  		require_noerr( err, exit );
  	}
  	
  exit:
  	return;
  }
  
  //===========================================================================================================================
  //	BrowseAllResolveCallback
  //===========================================================================================================================
  
  static void DNSSD_API
  	BrowseAllResolveCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inFullName,
  		const char *			inHostname,
  		uint16_t				inPort,
  		uint16_t				inTXTLen,
  		const unsigned char *	inTXTPtr,
  		void *					inContext )
  {
  	OSStatus					err;
  	const uint64_t				nowTicks	= UpTicks();
  	BrowseInstance * const		instance	= (BrowseInstance *) inContext;
  	
  	Unused( inSDRef );
  	Unused( inFlags );
  	Unused( inInterfaceIndex );
  	Unused( inFullName );
  	
  	err = inError;
  	require_noerr( err, exit );
  	
  	if( !MemEqual( instance->txtPtr, instance->txtLen, inTXTPtr, inTXTLen ) )
  	{
  		FreeNullSafe( instance->txtPtr );
  		instance->txtPtr = malloc( inTXTLen );
  		require_action( instance->txtPtr, exit, err = kNoMemoryErr );
  		
  		memcpy( instance->txtPtr, inTXTPtr, inTXTLen );
  		instance->txtLen = inTXTLen;
  	}
  	
  	instance->port = ntohs( inPort );
  	
  	if( !instance->hostname || ( strcasecmp( instance->hostname, inHostname ) != 0 ) )
  	{
  		DNSServiceRef		sdRef;
  		
  		if( !instance->hostname ) instance->resolveDoneTicks = nowTicks;
  		FreeNullSafe( instance->hostname );
  		instance->hostname = strdup( inHostname );
  		require_action( instance->hostname, exit, err = kNoMemoryErr );
  		
  		DNSServiceForget( &instance->getAddr );
  		ForgetIPAddressList( &instance->addrList );
  		
  		sdRef = instance->context->mainRef;
  		instance->getAddrStartTicks = UpTicks();
  		err = DNSServiceGetAddrInfo( &sdRef, kDNSServiceFlagsShareConnection, instance->ifIndex,
  			kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, instance->hostname, BrowseAllGAICallback, instance );
  		require_noerr( err, exit );
  		
  		instance->getAddr = sdRef;
  	}
  	
  exit:
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	BrowseAllGAICallback
  //===========================================================================================================================
  
  static void DNSSD_API
  	BrowseAllGAICallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inHostname,
  		const struct sockaddr *	inSockAddr,
  		uint32_t				inTTL,
  		void *					inContext )
  {
  	OSStatus					err;
  	BrowseInstance * const		instance = (BrowseInstance *) inContext;
  	
  	Unused( inSDRef );
  	Unused( inInterfaceIndex );
  	Unused( inHostname );
  	Unused( inTTL );
  	
  	err = inError;
  	require_noerr( err, exit );
  	
  	if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
  	{
  		dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
  		goto exit;
  	}
  	
  	if( inFlags & kDNSServiceFlagsAdd )
  	{
  		err = BrowseAllAddIPAddress( instance->context, instance, inSockAddr );
  		if( err == kDuplicateErr ) err = kNoErr;
  		require_noerr( err, exit );
  	}
  	else
  	{
  		err = BrowseAllRemoveIPAddress( instance->context, instance, inSockAddr );
  		if( err == kNotFoundErr ) err = kNoErr;
  		require_noerr( err, exit );
  	}
  	
  exit:
  	return;
  }
  
  //===========================================================================================================================
  //	BrowseAllConnectionProgress
  //===========================================================================================================================
  
  static void	BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg )
  {
  	BrowseIPAddr * const		addr = (BrowseIPAddr *) inArg;
  	
  	if( inPhase == kAsyncConnectionPhase_Connected )
  	{
  		const AsyncConnectedInfo * const		info = (AsyncConnectedInfo *) inDetails;
  		
  		addr->connectTimeSecs = info->connectSecs;
  	}
  }
  
  //===========================================================================================================================
  //	BrowseAllConnectionHandler
  //===========================================================================================================================
  
  static void	BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg )
  {
  	BrowseIPAddr * const			addr	= (BrowseIPAddr *) inArg;
  	BrowseAllContext * const		context	= addr->context;
  	
  	if( inError )
  	{
  		addr->connectStatus	= kConnectStatus_Failed;
  		addr->connectError	= inError;
  	}
  	else
  	{
  		addr->connectStatus = kConnectStatus_Succeeded;
  	}
  	
  	check( context->pendingConnectCount > 0 );
  	if( --context->pendingConnectCount == 0 )
  	{
  		if( context->exitTimer )
  		{
  			dispatch_source_forget( &context->exitTimer );
  			dispatch_async_f( dispatch_get_main_queue(), context, BrowseAllExit );
  		}
  	}
  	
  	ForgetSocket( &inSock );
  	BrowseIPAddrRelease( addr );
  }
  
  //===========================================================================================================================
  //	BrowseAllStop
  //===========================================================================================================================
  
  static void	BrowseAllStop( void *inContext )
  {
  	OSStatus						err;
  	BrowseAllContext * const		context = (BrowseAllContext *) inContext;
  	BrowseDomain *					domain;
  	BrowseType *					type;
  	BrowseOp *						browse;
  	BrowseInstance *				instance;
  	
  	DNSServiceForget( &context->domainsQuery );
  	for( domain = context->domainList; domain; domain = domain->next )
  	{
  		DNSServiceForget( &domain->servicesQuery );
  		for( type = domain->typeList; type; type = type->next )
  		{
  			for( browse = type->browseList; browse; browse = browse->next )
  			{
  				DNSServiceForget( &browse->browse );
  				for( instance = browse->instanceList; instance; instance = instance->next )
  				{
  					DNSServiceForget( &instance->resolve );
  					DNSServiceForget( &instance->getAddr );
  				}
  			}
  		}
  	}
  	DNSServiceForget( &context->mainRef );
  	
  	if( ( context->pendingConnectCount > 0 ) && ( context->connectTimeLimitSecs > 0 ) )
  	{
  		check( !context->exitTimer );
  		err = DispatchTimerCreate( dispatch_time_seconds( context->connectTimeLimitSecs ), DISPATCH_TIME_FOREVER,
  			100 * kNanosecondsPerMillisecond, BrowseAllExit, NULL, context, &context->exitTimer );
  		require_noerr( err, exit );
  		dispatch_resume( context->exitTimer );
  	}
  	else
  	{
  		dispatch_async_f( dispatch_get_main_queue(), context, BrowseAllExit );
  	}
  	err = kNoErr;
  	
  exit:
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	BrowseAllExit
  //===========================================================================================================================
  
  #define kStatusStr_CouldConnect					"connected"
  #define kStatusStr_CouldConnectColored			kANSIGreen kStatusStr_CouldConnect kANSINormal
  #define kStatusStr_CouldNotConnect				"could not connect"
  #define kStatusStr_CouldNotConnectColored		kANSIRed kStatusStr_CouldNotConnect kANSINormal
  #define kStatusStr_NoConnectionAttempted		"no connection attempted"
  #define kStatusStr_Unknown						"unknown"
  
  #define Indent( X )		( (X) * 4 ), ""
  
  static void	BrowseAllExit( void *inContext )
  {
  	BrowseAllContext * const		context = (BrowseAllContext *) inContext;
  	BrowseDomain *					domain;
  	BrowseType *					type;
  	BrowseOp *						browse;
  	BrowseInstance *				instance;
  	BrowseIPAddr *					addr;
  	
  	dispatch_source_forget( &context->exitTimer );
  	
  	for( domain = context->domainList; domain; domain = domain->next )
  	{
  		FPrintF( stdout, "%s\n\n", domain->name );
  		
  		for( type = domain->typeList; type; type = type->next )
  		{
  			const char *		desc;
  			
  			desc = ServiceTypeDescription( type->name );
  			if( desc )	FPrintF( stdout, "%*s" "%s (%s)\n\n",	Indent( 1 ), desc, type->name );
  			else		FPrintF( stdout, "%*s" "%s\n\n",		Indent( 1 ), type->name );
  			
  			for( browse = type->browseList; browse; browse = browse->next )
  			{
  				for( instance = browse->instanceList; instance; instance = instance->next )
  				{
  					char		ifname[ IF_NAMESIZE + 1 ];
  					
  					FPrintF( stdout, "%*s" "%s via ", Indent( 2 ), instance->name );
  					FPrintF( stdout, "%*s" "%s via ", Indent( 2 ), instance->name );
  					if( instance->ifIndex == 0 )
  					{
  						FPrintF( stdout, "the Internet" );
  					}
  					else if( if_indextoname( instance->ifIndex, ifname ) )
  					{
  						NetTransportType		netType;
  						
  						SocketGetInterfaceInfo( kInvalidSocketRef, ifname, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
  							&netType );
  						FPrintF( stdout, "%s (%s)",
  							( netType == kNetTransportType_Ethernet ) ? "ethernet" : NetTransportTypeToString( netType ),
  							ifname );
  					}
  					else
  					{
  						FPrintF( stdout, "interface index %u", instance->ifIndex );
  					}
  					FPrintF( stdout, "\n\n" );
  					
  					if( instance->hostname )
  					{
  						char		buffer[ 256 ];
  						
  						SNPrintF( buffer, sizeof( buffer ), "%s:%u", instance->hostname, instance->port );
  						FPrintF( stdout, "%*s" "%-51s %4llu ms\n", Indent( 3 ), buffer,
  							UpTicksToMilliseconds( instance->resolveDoneTicks - instance->resolveStartTicks ) );
  					}
  					else
  					{
  						FPrintF( stdout, "%*s" "%s:%u\n", Indent( 3 ), instance->hostname, instance->port );
  					}
  					
  					for( addr = instance->addrList; addr; addr = addr->next )
  					{
  						AsyncConnection_Forget( &addr->connection );
  						
  						if( addr->connectStatus == kConnectStatus_Pending )
  						{
  							addr->connectStatus	= kConnectStatus_Failed;
  							addr->connectError	= kTimeoutErr;
  						}
  						
  						FPrintF( stdout, "%*s" "%-##47a %4llu ms (", Indent( 4 ),
  							&addr->sip.sa, UpTicksToMilliseconds( addr->foundTicks - instance->getAddrStartTicks ) );
  						switch( addr->connectStatus )
  						{
  							case kConnectStatus_None:
  								FPrintF( stdout, "%s", kStatusStr_NoConnectionAttempted );
  								break;
  							
  							case kConnectStatus_Succeeded:
  								FPrintF( stdout, "%s in %.2f ms",
  									context->useColoredText ? kStatusStr_CouldConnectColored : kStatusStr_CouldConnect,
  									addr->connectTimeSecs * 1000 );
  								break;
  							
  							case kConnectStatus_Failed:
  								FPrintF( stdout, "%s: %m",
  									context->useColoredText ? kStatusStr_CouldNotConnectColored : kStatusStr_CouldNotConnect,
  									addr->connectError );
  								break;
  							
  							default:
  								FPrintF( stdout, "%s", kStatusStr_Unknown );
  								break;
  						}
  						FPrintF( stdout, ")\n" );
  					}
  					
  					FPrintF( stdout, "\n" );
  					if( instance->txtLen == 0 ) continue;
  					
  					FPrintF( stdout, "%*s" "TXT record:\n", Indent( 3 ) );
  					if( instance->txtLen > 1 )
  					{
  						FPrintF( stdout, "%3{txt}", instance->txtPtr, instance->txtLen );
  					}
  					else
  					{
  						FPrintF( stdout, "%*s" "%#H\n", Indent( 3 ), instance->txtPtr, (int) instance->txtLen, INT_MAX );
  					}
  					FPrintF( stdout, "\n" );
  				}
  			}
  			FPrintF( stdout, "\n" );
  		}
  	}
  	
  	while( ( domain = context->domainList ) != NULL )
  	{
  		context->domainList = domain->next;
  		BrowseDomainFree( domain );
  	}
  	
  	BrowseAllContextRelease( context );
  	Exit( NULL );
  }
  
  //===========================================================================================================================
  //	BrowseAllAddDomain
  //===========================================================================================================================
  
  static OSStatus	BrowseAllAddDomain( BrowseAllContext *inContext, const char *inName )
  {
  	OSStatus			err;
  	BrowseDomain *		domain;
  	BrowseDomain **		p;
  	BrowseDomain *		newDomain = NULL;
  	
  	for( p = &inContext->domainList; ( domain = *p ) != NULL; p = &domain->next )
  	{
  		if( strcasecmp( domain->name, inName ) == 0 ) break;
  	}
  	require_action_quiet( !domain, exit, err = kDuplicateErr );
  	
  	newDomain = (BrowseDomain *) calloc( 1, sizeof( *newDomain ) );
  	require_action( newDomain, exit, err = kNoMemoryErr );
  	
  	++inContext->refCount;
  	newDomain->context = inContext;
  	
  	newDomain->name = strdup( inName );
  	require_action( newDomain->name, exit, err = kNoMemoryErr );
  	
  	if( inContext->serviceTypesCount > 0 )
  	{
  		size_t		i;
  		
  		for( i = 0; i < inContext->serviceTypesCount; ++i )
  		{
  			err = BrowseAllAddServiceType( inContext, newDomain, inContext->serviceTypes[ i ], inContext->ifIndex,
  				inContext->includeAWDL );
  			if( err == kDuplicateErr ) err = kNoErr;
  			require_noerr( err, exit );
  		}
  	}
  	else
  	{
  		char *				recordName;
  		DNSServiceFlags		flags;
  		DNSServiceRef		sdRef;
  		
  		ASPrintF( &recordName, "_services._dns-sd._udp.%s", newDomain->name );
  		require_action( recordName, exit, err = kNoMemoryErr );
  		
  		flags = kDNSServiceFlagsShareConnection;
  		if( inContext->includeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
  		
  		sdRef = newDomain->context->mainRef;
  		err = DNSServiceQueryRecord( &sdRef, flags, inContext->ifIndex, recordName, kDNSServiceType_PTR, kDNSServiceClass_IN,
  			BrowseAllQueryCallback, newDomain );
  		free( recordName );
  		require_noerr( err, exit );
  		
  		newDomain->servicesQuery = sdRef;
  	}
  	
  	*p = newDomain;
  	newDomain = NULL;
  	err = kNoErr;
  	
  exit:
  	if( newDomain ) BrowseDomainFree( newDomain );
  	return( err );
  }
  
  //===========================================================================================================================
  //	BrowseAllRemoveDomain
  //===========================================================================================================================
  
  static OSStatus	BrowseAllRemoveDomain( BrowseAllContext *inContext, const char *inName )
  {
  	OSStatus			err;
  	BrowseDomain *		domain;
  	BrowseDomain **		p;
  	
  	for( p = &inContext->domainList; ( domain = *p ) != NULL; p = &domain->next )
  	{
  		if( strcasecmp( domain->name, inName ) == 0 ) break;
  	}
  	
  	if( domain )
  	{
  		*p = domain->next;
  		BrowseDomainFree( domain );
  		err = kNoErr;
  	}
  	else
  	{
  		err = kNotFoundErr;
  	}
  	
  	return( err );
  }
  
  //===========================================================================================================================
  //	BrowseAllContextRelease
  //===========================================================================================================================
  
  static void	BrowseAllContextRelease( BrowseAllContext *inContext )
  {
  	if( --inContext->refCount == 0 )
  	{
  		check( !inContext->domainsQuery );
  		check( !inContext->domainList );
  		check( !inContext->exitTimer );
  		check( !inContext->pendingConnectCount );
  		DNSServiceForget( &inContext->mainRef );
  		if( inContext->serviceTypes )
  		{
  			StringArray_Free( inContext->serviceTypes, inContext->serviceTypesCount );
  			inContext->serviceTypes			= NULL;
  			inContext->serviceTypesCount	= 0;
  		}
  		free( inContext );
  	}
  }
  
  //===========================================================================================================================
  //	BrowseAllAddServiceType
  //===========================================================================================================================
  
  static OSStatus
  	BrowseAllAddServiceType(
  		BrowseAllContext *	inContext,
  		BrowseDomain *		inDomain,
  		const char *		inName,
  		uint32_t			inIfIndex,
  		Boolean				inIncludeAWDL )
  {
  	OSStatus			err;
  	DNSServiceRef		sdRef;
  	DNSServiceFlags		flags;
  	BrowseType *		type;
  	BrowseType **		typePtr;
  	BrowseType *		newType		= NULL;
  	BrowseOp *			browse;
  	BrowseOp **			browsePtr;
  	BrowseOp *			newBrowse	= NULL;
  	
  	for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
  	{
  		if( strcasecmp( type->name, inName ) == 0 ) break;
  	}
  	if( !type )
  	{
  		newType = (BrowseType *) calloc( 1, sizeof( *newType ) );
  		require_action( newType, exit, err = kNoMemoryErr );
  		
  		newType->name = strdup( inName );
  		require_action( newType->name, exit, err = kNoMemoryErr );
  		
  		type = newType;
  	}
  	
  	for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
  	{
  		if( browse->ifIndex == inIfIndex ) break;
  	}
  	require_action_quiet( !browse, exit, err = kDuplicateErr );
  	
  	newBrowse = (BrowseOp *) calloc( 1, sizeof( *newBrowse ) );
  	require_action( newBrowse, exit, err = kNoMemoryErr );
  	
  	++inContext->refCount;
  	newBrowse->context	= inContext;
  	newBrowse->ifIndex	= inIfIndex;
  	if( stricmp_suffix( inName, "._tcp" ) == 0 ) newBrowse->isTCP = true;
  	
  	flags = kDNSServiceFlagsShareConnection;
  	if( inIncludeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
  	
  	newBrowse->startTicks = UpTicks();
  	
  	sdRef = inContext->mainRef;
  	err = DNSServiceBrowse( &sdRef, flags, newBrowse->ifIndex, type->name, inDomain->name, BrowseAllBrowseCallback,
  		newBrowse );
  	require_noerr( err, exit );
  	
  	newBrowse->browse = sdRef;
  	*browsePtr = newBrowse;
  	newBrowse = NULL;
  	
  	if( newType )
  	{
  		*typePtr = newType;
  		newType = NULL;
  	}
  	
  exit:
  	if( newBrowse )	BrowseOpFree( newBrowse );
  	if( newType )	BrowseTypeFree( newType );
  	return( err );
  }
  
  //===========================================================================================================================
  //	BrowseAllRemoveServiceType
  //===========================================================================================================================
  
  static OSStatus
  	BrowseAllRemoveServiceType(
  		BrowseAllContext *	inContext,
  		BrowseDomain *		inDomain,
  		const char *		inName,
  		uint32_t			inIfIndex )
  {
  	OSStatus			err;
  	BrowseType *		type;
  	BrowseType **		typePtr;
  	BrowseOp *			browse;
  	BrowseOp **			browsePtr;
  	
  	Unused( inContext );
  	
  	for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
  	{
  		if( strcasecmp( type->name, inName ) == 0 ) break;
  	}
  	require_action_quiet( type, exit, err = kNotFoundErr );
  	
  	for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
  	{
  		if( browse->ifIndex == inIfIndex ) break;
  	}
  	require_action_quiet( browse, exit, err = kNotFoundErr );
  	
  	*browsePtr = browse->next;
  	BrowseOpFree( browse );
  	if( !type->browseList )
  	{
  		*typePtr = type->next;
  		BrowseTypeFree( type );
  	}
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	BrowseAllAddServiceInstance
  //===========================================================================================================================
  
  static OSStatus
  	BrowseAllAddServiceInstance(
  		BrowseAllContext *	inContext,
  		BrowseOp *			inBrowse,
  		const char *		inName,
  		const char *		inRegType,
  		const char *		inDomain,
  		uint32_t			inIfIndex )
  {
  	OSStatus				err;
  	DNSServiceRef			sdRef;
  	BrowseInstance *		instance;
  	BrowseInstance **		p;
  	const uint64_t			nowTicks	= UpTicks();
  	BrowseInstance *		newInstance	= NULL;
  	
  	for( p = &inBrowse->instanceList; ( instance = *p ) != NULL; p = &instance->next )
  	{
  		if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
  	}
  	require_action_quiet( !instance, exit, err = kDuplicateErr );
  	
  	newInstance = (BrowseInstance *) calloc( 1, sizeof( *newInstance ) );
  	require_action( newInstance, exit, err = kNoMemoryErr );
  	
  	++inContext->refCount;
  	newInstance->context	= inContext;
  	newInstance->foundTicks	= nowTicks;
  	newInstance->ifIndex	= inIfIndex;
  	newInstance->isTCP		= inBrowse->isTCP;
  	
  	newInstance->name = strdup( inName );
  	require_action( newInstance->name, exit, err = kNoMemoryErr );
  	
  	sdRef = inContext->mainRef;
  	newInstance->resolveStartTicks = UpTicks();
  	err = DNSServiceResolve( &sdRef, kDNSServiceFlagsShareConnection, newInstance->ifIndex, inName, inRegType, inDomain,
  		BrowseAllResolveCallback, newInstance );
  	require_noerr( err, exit );
  	
  	newInstance->resolve = sdRef;
  	*p = newInstance;
  	newInstance = NULL;
  	
  exit:
  	if( newInstance ) BrowseInstanceFree( newInstance );
  	return( err );
  }
  
  //===========================================================================================================================
  //	BrowseAllRemoveServiceInstance
  //===========================================================================================================================
  
  static OSStatus
  	BrowseAllRemoveServiceInstance(
  		BrowseAllContext *	inContext,
  		BrowseOp *			inBrowse,
  		const char *		inName,
  		uint32_t			inIfIndex )
  {
  	OSStatus				err;
  	BrowseInstance *		instance;
  	BrowseInstance **		p;
  	
  	Unused( inContext );
  	
  	for( p = &inBrowse->instanceList; ( instance = *p ) != NULL; p = &instance->next )
  	{
  		if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
  	}
  	require_action_quiet( instance, exit, err = kNotFoundErr );
  	
  	*p = instance->next;
  	BrowseInstanceFree( instance );
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	BrowseAllAddIPAddress
  //===========================================================================================================================
  
  #define kDiscardProtocolPort	9
  
  static OSStatus
  	BrowseAllAddIPAddress(
  		BrowseAllContext *		inContext,
  		BrowseInstance *		inInstance,
  		const struct sockaddr *	inSockAddr )
  {
  	OSStatus			err;
  	BrowseIPAddr *		addr;
  	BrowseIPAddr **		p;
  	const uint64_t		nowTicks	= UpTicks();
  	BrowseIPAddr *		newAddr		= NULL;
  	
  	if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
  	{
  		dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
  		err = kTypeErr;
  		goto exit;
  	}
  	
  	for( p = &inInstance->addrList; ( addr = *p ) != NULL; p = &addr->next )
  	{
  		if( SockAddrCompareAddr( &addr->sip, inSockAddr ) == 0 ) break;
  	}
  	require_action_quiet( !addr, exit, err = kDuplicateErr );
  	
  	newAddr = (BrowseIPAddr *) calloc( 1, sizeof( *newAddr ) );
  	require_action( newAddr, exit, err = kNoMemoryErr );
  	
  	++inContext->refCount;
  	newAddr->refCount	= 1;
  	newAddr->context	= inContext;
  	newAddr->foundTicks	= nowTicks;
  	SockAddrCopy( inSockAddr, &newAddr->sip.sa );
  	
  	if( inInstance->isTCP && ( inInstance->port != kDiscardProtocolPort ) )
  	{
  		char		destination[ kSockAddrStringMaxSize ];
  		
  		err = SockAddrToString( &newAddr->sip, kSockAddrStringFlagsNoPort, destination );
  		require_noerr( err, exit );
  		
  		err = AsyncConnection_Connect( &newAddr->connection, destination, -inInstance->port, kAsyncConnectionFlag_P2P,
  			kAsyncConnectionNoTimeout, kSocketBufferSize_DontSet, kSocketBufferSize_DontSet,
  			BrowseAllConnectionProgress, newAddr, BrowseAllConnectionHandler, newAddr, dispatch_get_main_queue() );
  		require_noerr( err, exit );
  		
  		++newAddr->refCount;
  		newAddr->connectStatus = kConnectStatus_Pending;
  		++inContext->pendingConnectCount;
  	}
  	
  	*p = newAddr;
  	newAddr = NULL;
  	err = kNoErr;
  	
  exit:
  	if( newAddr ) BrowseIPAddrRelease( newAddr );
  	return( err );
  }
  
  //===========================================================================================================================
  //	BrowseAllRemoveIPAddress
  //===========================================================================================================================
  
  static OSStatus
  	BrowseAllRemoveIPAddress(
  		BrowseAllContext *		inContext,
  		BrowseInstance *		inInstance,
  		const struct sockaddr *	inSockAddr )
  {
  	OSStatus			err;
  	BrowseIPAddr *		addr;
  	BrowseIPAddr **		p;
  	
  	Unused( inContext );
  	
  	for( p = &inInstance->addrList; ( addr = *p ) != NULL; p = &addr->next )
  	{
  		if( SockAddrCompareAddr( &addr->sip.sa, inSockAddr ) == 0 ) break;
  	}
  	require_action_quiet( addr, exit, err = kNotFoundErr );
  	
  	*p = addr->next;
  	BrowseIPAddrRelease( addr );
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	BrowseDomainFree
  //===========================================================================================================================
  
  static void	BrowseDomainFree( BrowseDomain *inDomain )
  {
  	BrowseType *		type;
  	
  	ForgetBrowseAllContext( &inDomain->context );
  	ForgetMem( &inDomain->name );
  	DNSServiceForget( &inDomain->servicesQuery );
  	while( ( type = inDomain->typeList ) != NULL )
  	{
  		inDomain->typeList = type->next;
  		BrowseTypeFree( type );
  	}
  	free( inDomain );
  }
  
  //===========================================================================================================================
  //	BrowseTypeFree
  //===========================================================================================================================
  
  static void	BrowseTypeFree( BrowseType *inType )
  {
  	BrowseOp *		browse;
  	
  	ForgetMem( &inType->name );
  	while( ( browse = inType->browseList ) != NULL )
  	{
  		inType->browseList = browse->next;
  		BrowseOpFree( browse );
  	}
  	free( inType );
  }
  
  //===========================================================================================================================
  //	BrowseOpFree
  //===========================================================================================================================
  
  static void	BrowseOpFree( BrowseOp *inBrowse )
  {
  	BrowseInstance *		instance;
  	
  	ForgetBrowseAllContext( &inBrowse->context );
  	DNSServiceForget( &inBrowse->browse );
  	while( ( instance = inBrowse->instanceList ) != NULL )
  	{
  		inBrowse->instanceList = instance->next;
  		BrowseInstanceFree( instance );
  	}
  	free( inBrowse );
  }
  
  //===========================================================================================================================
  //	BrowseInstanceFree
  //===========================================================================================================================
  
  static void	BrowseInstanceFree( BrowseInstance *inInstance )
  {
  	ForgetBrowseAllContext( &inInstance->context );
  	ForgetMem( &inInstance->name );
  	DNSServiceForget( &inInstance->resolve );
  	DNSServiceForget( &inInstance->getAddr );
  	ForgetMem( &inInstance->txtPtr );
  	ForgetMem( &inInstance->hostname );
  	ForgetIPAddressList( &inInstance->addrList );
  	free( inInstance );
  }
  
  //===========================================================================================================================
  //	BrowseIPAddrRelease
  //===========================================================================================================================
  
  static void BrowseIPAddrRelease( BrowseIPAddr *inAddr )
  {
  	AsyncConnection_Forget( &inAddr->connection );
  	if( --inAddr->refCount == 0 )
  	{
  		ForgetBrowseAllContext( &inAddr->context );
  		free( inAddr );
  	}
  }
  
  //===========================================================================================================================
  //	BrowseIPAddrReleaseList
  //===========================================================================================================================
  
  static void BrowseIPAddrReleaseList( BrowseIPAddr *inList )
  {
  	BrowseIPAddr *		addr;
  	
  	while( ( addr = inList ) != NULL )
  	{
  		inList = addr->next;
  		BrowseIPAddrRelease( addr );
  	}
  }
  
  //===========================================================================================================================
  //	GetAddrInfoStressCmd
  //===========================================================================================================================
  
  typedef struct
  {
  	DNSServiceRef			mainRef;
  	DNSServiceRef			sdRef;
  	DNSServiceFlags			flags;
  	unsigned int			interfaceIndex;
  	unsigned int			connectionNumber;
  	unsigned int			requestCount;
  	unsigned int			requestCountMax;
  	unsigned int			requestCountLimit;
  	unsigned int			durationMinMs;
  	unsigned int			durationMaxMs;
  	
  }	GAIStressContext;
  
  static void	GetAddrInfoStressEvent( void *inContext );
  static void	DNSSD_API
  	GetAddrInfoStressCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inHostname,
  		const struct sockaddr *	inSockAddr,
  		uint32_t				inTTL,
  		void *					inContext );
  
  static void	GetAddrInfoStressCmd( void )
  {
  	OSStatus				err;
  	GAIStressContext *		context = NULL;
  	int						i;
  	DNSServiceFlags			flags;
  	uint32_t				ifIndex;
  	char					ifName[ kInterfaceNameBufLen ];
  	char					time[ kTimestampBufLen ];
  	
  	if( gGAIStress_TestDurationSecs < 0 )
  	{
  		FPrintF( stdout, "Invalid test duration: %d s.\n", gGAIStress_TestDurationSecs );
  		err = kParamErr;
  		goto exit;
  	}
  	if( gGAIStress_ConnectionCount <= 0 )
  	{
  		FPrintF( stdout, "Invalid simultaneous connection count: %d.\n", gGAIStress_ConnectionCount );
  		err = kParamErr;
  		goto exit;
  	}
  	if( gGAIStress_DurationMinMs <= 0 )
  	{
  		FPrintF( stdout, "Invalid minimum DNSServiceGetAddrInfo() duration: %d ms.\n", gGAIStress_DurationMinMs );
  		err = kParamErr;
  		goto exit;
  	}
  	if( gGAIStress_DurationMaxMs <= 0 )
  	{
  		FPrintF( stdout, "Invalid maximum DNSServiceGetAddrInfo() duration: %d ms.\n", gGAIStress_DurationMaxMs );
  		err = kParamErr;
  		goto exit;
  	}
  	if( gGAIStress_DurationMinMs > gGAIStress_DurationMaxMs )
  	{
  		FPrintF( stdout, "Invalid minimum and maximum DNSServiceGetAddrInfo() durations: %d ms and %d ms.\n",
  			gGAIStress_DurationMinMs, gGAIStress_DurationMaxMs );
  		err = kParamErr;
  		goto exit;
  	}
  	if( gGAIStress_RequestCountMax <= 0 )
  	{
  		FPrintF( stdout, "Invalid maximum request count: %d.\n", gGAIStress_RequestCountMax );
  		err = kParamErr;
  		goto exit;
  	}
  	
  	// Set flags.
  	
  	flags = GetDNSSDFlagsFromOpts();
  	
  	// Set interface index.
  	
  	err = InterfaceIndexFromArgString( gInterface, &ifIndex );
  	require_noerr_quiet( err, exit );
  	
  	for( i = 0; i < gGAIStress_ConnectionCount; ++i )
  	{
  		context = (GAIStressContext *) calloc( 1, sizeof( *context ) );
  		require_action( context, exit, err = kNoMemoryErr );
  		
  		context->flags				= flags;
  		context->interfaceIndex		= ifIndex;
  		context->connectionNumber	= (unsigned int)( i + 1 );
  		context->requestCountMax	= (unsigned int) gGAIStress_RequestCountMax;
  		context->durationMinMs		= (unsigned int) gGAIStress_DurationMinMs;
  		context->durationMaxMs		= (unsigned int) gGAIStress_DurationMaxMs;
  		
  		dispatch_async_f( dispatch_get_main_queue(), context, GetAddrInfoStressEvent );
  		context = NULL;
  	}
  	
  	if( gGAIStress_TestDurationSecs > 0 )
  	{
  		dispatch_after_f( dispatch_time_seconds( gGAIStress_TestDurationSecs ), dispatch_get_main_queue(), NULL, Exit );
  	}
  	
  	FPrintF( stdout, "Flags:                %#{flags}\n",	flags, kDNSServiceFlagsDescriptors );
  	FPrintF( stdout, "Interface:            %d (%s)\n",		(int32_t) ifIndex, InterfaceIndexToName( ifIndex, ifName ) );
  	FPrintF( stdout, "Test duration:        " );
  	if( gGAIStress_TestDurationSecs == 0 )
  	{
  		FPrintF( stdout, "∞\n" );
  	}
  	else
  	{
  		FPrintF( stdout, "%d s\n", gGAIStress_TestDurationSecs );
  	}
  	FPrintF( stdout, "Connection count:     %d\n",		gGAIStress_ConnectionCount );
  	FPrintF( stdout, "Request duration min: %d ms\n",	gGAIStress_DurationMinMs );
  	FPrintF( stdout, "Request duration max: %d ms\n",	gGAIStress_DurationMaxMs );
  	FPrintF( stdout, "Request count max:    %d\n",		gGAIStress_RequestCountMax );
  	FPrintF( stdout, "Start time:           %s\n",		GetTimestampStr( time ) );
  	FPrintF( stdout, "---\n" );
  	
  	dispatch_main();
  	
  exit:
  	FreeNullSafe( context );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	GetAddrInfoStressEvent
  //===========================================================================================================================
  
  #define kStressRandStrLen		5
  
  #define kLowercaseAlphaCharSet		"abcdefghijklmnopqrstuvwxyz"
  
  static void	GetAddrInfoStressEvent( void *inContext )
  {
  	GAIStressContext * const		context = (GAIStressContext *) inContext;
  	OSStatus						err;
  	DNSServiceRef					sdRef;
  	unsigned int					nextMs;
  	char							randomStr[ kStressRandStrLen + 1 ];
  	char							hostname[ kStressRandStrLen + 4 + 1 ];
  	char							time[ kTimestampBufLen ];
  	Boolean							isConnectionNew	= false;
  	static Boolean					printedHeader	= false;
  	
  	if( !context->mainRef || ( context->requestCount >= context->requestCountLimit ) )
  	{
  		DNSServiceForget( &context->mainRef );
  		context->sdRef				= NULL;
  		context->requestCount		= 0;
  		context->requestCountLimit	= RandomRange( 1, context->requestCountMax );
  		
  		err = DNSServiceCreateConnection( &context->mainRef );
  		require_noerr( err, exit );
  		
  		err = DNSServiceSetDispatchQueue( context->mainRef, dispatch_get_main_queue() );
  		require_noerr( err, exit );
  		
  		isConnectionNew = true;
  	}
  	
  	RandomString( kLowercaseAlphaCharSet, sizeof_string( kLowercaseAlphaCharSet ), 2, kStressRandStrLen, randomStr );
  	SNPrintF( hostname, sizeof( hostname ), "%s.com", randomStr );
  	
  	nextMs = RandomRange( context->durationMinMs, context->durationMaxMs );
  	
  	if( !printedHeader )
  	{
  		FPrintF( stdout, "%-26s Conn  Hostname Dur (ms)\n", "Timestamp" );
  		printedHeader = true;
  	}
  	FPrintF( stdout, "%-26s %3u%c %9s %8u\n",
  		GetTimestampStr( time ), context->connectionNumber, isConnectionNew ? '*': ' ', hostname, nextMs );
  	
  	DNSServiceForget( &context->sdRef );
  	sdRef = context->mainRef;
  	err = DNSServiceGetAddrInfo( &sdRef, context->flags | kDNSServiceFlagsShareConnection, context->interfaceIndex,
  		kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, hostname, GetAddrInfoStressCallback, NULL );
  	require_noerr( err, exit );
  	context->sdRef = sdRef;
  	
  	context->requestCount++;
  	
  	dispatch_after_f( dispatch_time_milliseconds( nextMs ), dispatch_get_main_queue(), context, GetAddrInfoStressEvent );
  	
  exit:
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	GetAddrInfoStressCallback
  //===========================================================================================================================
  
  static void DNSSD_API
  	GetAddrInfoStressCallback(
  		DNSServiceRef			inSDRef,
  		DNSServiceFlags			inFlags,
  		uint32_t				inInterfaceIndex,
  		DNSServiceErrorType		inError,
  		const char *			inHostname,
  		const struct sockaddr *	inSockAddr,
  		uint32_t				inTTL,
  		void *					inContext )
  {
  	Unused( inSDRef );
  	Unused( inFlags );
  	Unused( inInterfaceIndex );
  	Unused( inError );
  	Unused( inHostname );
  	Unused( inSockAddr );
  	Unused( inTTL );
  	Unused( inContext );
  }
  
  //===========================================================================================================================
  //	DNSQueryCmd
  //===========================================================================================================================
  
  #define kDNSPort	53
  
  typedef struct
  {
  	sockaddr_ip				serverAddr;
  	uint64_t				sendTicks;
  	uint8_t *				msgPtr;
  	size_t					msgLen;
  	size_t					msgOffset;
  	const char *			name;
  	dispatch_source_t		readSource;
  	SocketRef				sock;
  	int						timeLimitSecs;
  	uint16_t				queryID;
  	uint16_t				type;
  	Boolean					haveTCPLen;
  	Boolean					useTCP;
  	Boolean					printRawRData;	// True if RDATA results are not to be formatted.
  	uint8_t					msgBuf[ 512 ];
  	
  }	DNSQueryContext;
  
  static void	DNSQueryPrintPrologue( const DNSQueryContext *inContext );
  static void	DNSQueryReadHandler( void *inContext );
  static void	DNSQueryCancelHandler( void *inContext );
  
  static void	DNSQueryCmd( void )
  {
  	OSStatus				err;
  	DNSQueryContext *		context = NULL;
  	uint8_t *				msgPtr;
  	size_t					msgLen, sendLen;
  	
  	// Check command parameters.
  	
  	if( gDNSQuery_TimeLimitSecs < -1 )
  	{
  		FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSQuery_TimeLimitSecs );
  		err = kParamErr;
  		goto exit;
  	}
  	if( ( gDNSQuery_Flags < INT16_MIN ) || ( gDNSQuery_Flags > UINT16_MAX ) )
  	{
  		FPrintF( stdout, "DNS flags-and-codes value is out of the unsigned 16-bit range: 0x%08X.\n", gDNSQuery_Flags );
  		err = kParamErr;
  		goto exit;
  	}
  	
  	// Create context.
  	
  	context = (DNSQueryContext *) calloc( 1, sizeof( *context ) );
  	require_action( context, exit, err = kNoMemoryErr );
  	
  	context->name			= gDNSQuery_Name;
  	context->sock			= kInvalidSocketRef;
  	context->timeLimitSecs	= gDNSQuery_TimeLimitSecs;
  	context->queryID		= (uint16_t) Random32();
  	context->useTCP			= gDNSQuery_UseTCP	 ? true : false;
  	context->printRawRData	= gDNSQuery_RawRData ? true : false;
  	
  #if( TARGET_OS_DARWIN )
  	if( gDNSQuery_Server )
  #endif
  	{
  		err = StringToSockAddr( gDNSQuery_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
  		require_noerr( err, exit );
  	}
  #if( TARGET_OS_DARWIN )
  	else
  	{
  		err = GetDefaultDNSServer( &context->serverAddr );
  		require_noerr( err, exit );
  	}
  #endif
  	if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSPort );
  	
  	err = RecordTypeFromArgString( gDNSQuery_Type, &context->type );
  	require_noerr( err, exit );
  	
  	// Write query message.
  	
  	check_compile_time_code( sizeof( context->msgBuf ) >= ( kDNSQueryMessageMaxLen + 2 ) );
  	
  	msgPtr = context->useTCP ? &context->msgBuf[ 2 ] : context->msgBuf;
  	err = WriteDNSQueryMessage( msgPtr, context->queryID, (uint16_t) gDNSQuery_Flags, context->name, context->type,
  		kDNSServiceClass_IN, &msgLen );
  	require_noerr( err, exit );
  	check( msgLen <= UINT16_MAX );
  	
  	if( context->useTCP )
  	{
  		WriteBig16( context->msgBuf, msgLen );
  		sendLen = 2 + msgLen;
  	}
  	else
  	{
  		sendLen = msgLen;
  	}
  	
  	DNSQueryPrintPrologue( context );
  	
  	if( gDNSQuery_Verbose )
  	{
  		FPrintF( stdout, "DNS message to send:\n\n" );
  		PrintUDNSMessage( msgPtr, msgLen, false );
  		FPrintF( stdout, "---\n" );
  	}
  	
  	if( context->useTCP )
  	{
  		// Create TCP socket.
  		
  		context->sock = socket( context->serverAddr.sa.sa_family, SOCK_STREAM, IPPROTO_TCP );
  		err = map_socket_creation_errno( context->sock );
  		require_noerr( err, exit );
  		
  		err = SocketConnect( context->sock, &context->serverAddr, 5 );
  		require_noerr( err, exit );
  	}
  	else
  	{
  		// Create UDP socket.
  		
  		err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &context->sock );
  		require_noerr( err, exit );
  	}
  	
  	context->sendTicks = UpTicks();
  	err = SocketWriteAll( context->sock, context->msgBuf, sendLen, 5 );
  	require_noerr( err, exit );
  	
  	if( context->timeLimitSecs == 0 ) goto exit;
  	
  	err = DispatchReadSourceCreate( context->sock, DNSQueryReadHandler, DNSQueryCancelHandler, context,
  		&context->readSource );
  	require_noerr( err, exit );
  	dispatch_resume( context->readSource );
  	
  	if( context->timeLimitSecs > 0 )
  	{
  		dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
  			Exit );
  	}
  	dispatch_main();
  	
  exit:
  	if( context )
  	{
  		dispatch_source_forget( &context->readSource );
  		ForgetSocket( &context->sock );
  		free( context );
  	}
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	DNSQueryPrintPrologue
  //===========================================================================================================================
  
  static void	DNSQueryPrintPrologue( const DNSQueryContext *inContext )
  {
  	const int		timeLimitSecs = inContext->timeLimitSecs;
  	char			time[ kTimestampBufLen ];
  	
  	FPrintF( stdout, "Name:        %s\n",		inContext->name );
  	FPrintF( stdout, "Type:        %s (%u)\n",	RecordTypeToString( inContext->type ), inContext->type );
  	FPrintF( stdout, "Server:      %##a\n",		&inContext->serverAddr );
  	FPrintF( stdout, "Transport:   %s\n",		inContext->useTCP ? "TCP" : "UDP" );
  	FPrintF( stdout, "Time limit:  " );
  	if( timeLimitSecs >= 0 )	FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
  	else						FPrintF( stdout, "∞\n" );
  	FPrintF( stdout, "Start time:  %s\n", GetTimestampStr( time ) );
  	FPrintF( stdout, "---\n" );
  }
  
  //===========================================================================================================================
  //	DNSQueryReadHandler
  //===========================================================================================================================
  
  static void	DNSQueryReadHandler( void *inContext )
  {
  	OSStatus					err;
  	const uint64_t				nowTicks	= UpTicks();
  	DNSQueryContext * const		context		= (DNSQueryContext *) inContext;
  	char						time[ kTimestampBufLen ];
  	
  	GetTimestampStr( time );
  	
  	if( context->useTCP )
  	{
  		if( !context->haveTCPLen )
  		{
  			err = SocketReadData( context->sock, &context->msgBuf, 2, &context->msgOffset );
  			if( err == EWOULDBLOCK ) { err = kNoErr; goto exit; }
  			require_noerr( err, exit );
  			
  			context->msgOffset	= 0;
  			context->msgLen		= ReadBig16( context->msgBuf );
  			context->haveTCPLen	= true;
  			if( context->msgLen <= sizeof( context->msgBuf ) )
  			{
  				context->msgPtr = context->msgBuf;
  			}
  			else
  			{
  				context->msgPtr = (uint8_t *) malloc( context->msgLen );
  				require_action( context->msgPtr, exit, err = kNoMemoryErr );
  			}
  		}
  		
  		err = SocketReadData( context->sock, context->msgPtr, context->msgLen, &context->msgOffset );
  		if( err == EWOULDBLOCK ) { err = kNoErr; goto exit; }
  		require_noerr( err, exit );
  		context->msgOffset	= 0;
  		context->haveTCPLen	= false;
  	}
  	else
  	{
  		sockaddr_ip		fromAddr;
  		
  		context->msgPtr = context->msgBuf;
  		err = SocketRecvFrom( context->sock, context->msgPtr, sizeof( context->msgBuf ), &context->msgLen, &fromAddr,
  			sizeof( fromAddr ), NULL, NULL, NULL, NULL );
  		require_noerr( err, exit );
  		
  		check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
  	}
  	
  	FPrintF( stdout, "Receive time: %s\n",			time );
  	FPrintF( stdout, "Source:       %##a\n",		&context->serverAddr );
  	FPrintF( stdout, "Message size: %zu\n",			context->msgLen );
  	FPrintF( stdout, "RTT:          %llu ms\n\n",	UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
  	PrintUDNSMessage( context->msgPtr, context->msgLen, context->printRawRData );
  	
  	if( ( context->msgLen >= kDNSHeaderLength ) && ( DNSHeaderGetID( (DNSHeader *) context->msgPtr ) == context->queryID ) )
  	{
  		Exit( kExitReason_ReceivedResponse );
  	}
  	
  exit:
  	if( err ) dispatch_source_forget( &context->readSource );
  }
  
  //===========================================================================================================================
  //	DNSQueryCancelHandler
  //===========================================================================================================================
  
  static void	DNSQueryCancelHandler( void *inContext )
  {
  	DNSQueryContext * const		context = (DNSQueryContext *) inContext;
  	
  	check( !context->readSource );
  	ForgetSocket( &context->sock );
  	if( context->msgPtr != context->msgBuf ) ForgetMem( &context->msgPtr );
  	free( context );
  	dispatch_async_f( dispatch_get_main_queue(), NULL, Exit );
  }
  
  #if( DNSSDUTIL_INCLUDE_DNSCRYPT )
  //===========================================================================================================================
  //	DNSCryptCmd
  //===========================================================================================================================
  
  #define kDNSCryptPort		443
  
  #define kDNSCryptMinPadLength				8
  #define kDNSCryptMaxPadLength				256
  #define kDNSCryptBlockSize					64
  #define kDNSCryptCertMinimumLength			124
  #define kDNSCryptClientMagicLength			8
  #define kDNSCryptResolverMagicLength		8
  #define kDNSCryptHalfNonceLength			12
  #define kDNSCryptCertMagicLength			4
  
  check_compile_time( ( kDNSCryptHalfNonceLength * 2 ) == crypto_box_NONCEBYTES );
  
  static const uint8_t		kDNSCryptCertMagic[ kDNSCryptCertMagicLength ] = { 'D', 'N', 'S', 'C' };
  static const uint8_t		kDNSCryptResolverMagic[ kDNSCryptResolverMagicLength ] =
  {
  	0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38
  };
  
  typedef struct
  {
  	uint8_t		certMagic[ kDNSCryptCertMagicLength ];
  	uint8_t		esVersion[ 2 ];
  	uint8_t		minorVersion[ 2 ];
  	uint8_t		signature[ crypto_sign_BYTES ];
  	uint8_t		publicKey[ crypto_box_PUBLICKEYBYTES ];
  	uint8_t		clientMagic[ kDNSCryptClientMagicLength ];
  	uint8_t		serial[ 4 ];
  	uint8_t		startTime[ 4 ];
  	uint8_t		endTime[ 4 ];
  	uint8_t		extensions[ 1 ];	// Variably-sized extension data.
  	
  }	DNSCryptCert;
  
  check_compile_time( offsetof( DNSCryptCert, extensions ) == kDNSCryptCertMinimumLength );
  
  typedef struct
  {
  	uint8_t		clientMagic[ kDNSCryptClientMagicLength ];
  	uint8_t		clientPublicKey[ crypto_box_PUBLICKEYBYTES ];
  	uint8_t		clientNonce[ kDNSCryptHalfNonceLength ];
  	uint8_t		poly1305MAC[ 16 ];
  	
  }	DNSCryptQueryHeader;
  
  check_compile_time( sizeof( DNSCryptQueryHeader ) == 68 );
  check_compile_time( sizeof( DNSCryptQueryHeader ) >= crypto_box_ZEROBYTES );
  check_compile_time( ( sizeof( DNSCryptQueryHeader ) - crypto_box_ZEROBYTES + crypto_box_BOXZEROBYTES ) ==
  	offsetof( DNSCryptQueryHeader, poly1305MAC ) );
  
  typedef struct
  {
  	uint8_t		resolverMagic[ kDNSCryptResolverMagicLength ];
  	uint8_t		clientNonce[ kDNSCryptHalfNonceLength ];
  	uint8_t		resolverNonce[ kDNSCryptHalfNonceLength ];
  	uint8_t		poly1305MAC[ 16 ];
  	
  }	DNSCryptResponseHeader;
  
  check_compile_time( sizeof( DNSCryptResponseHeader ) == 48 );
  check_compile_time( offsetof( DNSCryptResponseHeader, poly1305MAC ) >= crypto_box_BOXZEROBYTES );
  check_compile_time( ( offsetof( DNSCryptResponseHeader, poly1305MAC ) - crypto_box_BOXZEROBYTES + crypto_box_ZEROBYTES ) ==
  	sizeof( DNSCryptResponseHeader ) );
  
  typedef struct
  {
  	sockaddr_ip				serverAddr;
  	uint64_t				sendTicks;
  	const char *			providerName;
  	const char *			qname;
  	const uint8_t *			certPtr;
  	size_t					certLen;
  	dispatch_source_t		readSource;
  	size_t					msgLen;
  	int						timeLimitSecs;
  	uint16_t				queryID;
  	uint16_t				qtype;
  	Boolean					printRawRData;
  	uint8_t					serverPublicSignKey[ crypto_sign_PUBLICKEYBYTES ];
  	uint8_t					serverPublicKey[ crypto_box_PUBLICKEYBYTES ];
  	uint8_t					clientPublicKey[ crypto_box_PUBLICKEYBYTES ];
  	uint8_t					clientSecretKey[ crypto_box_SECRETKEYBYTES ];
  	uint8_t					clientMagic[ kDNSCryptClientMagicLength ];
  	uint8_t					clientNonce[ kDNSCryptHalfNonceLength ];
  	uint8_t					nmKey[ crypto_box_BEFORENMBYTES ];
  	uint8_t					msgBuf[ 512 ];
  	
  }	DNSCryptContext;
  
  static void		DNSCryptReceiveCertHandler( void *inContext );
  static void		DNSCryptReceiveResponseHandler( void *inContext );
  static void		DNSCryptProceed( void *inContext );
  static OSStatus	DNSCryptProcessCert( DNSCryptContext *inContext );
  static OSStatus	DNSCryptBuildQuery( DNSCryptContext *inContext );
  static OSStatus	DNSCryptSendQuery( DNSCryptContext *inContext );
  static void		DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen );
  
  static void	DNSCryptCmd( void )
  {
  	OSStatus				err;
  	DNSCryptContext *		context		= NULL;
  	size_t					writtenBytes;
  	size_t					totalBytes;
  	SocketContext *			sockContext;
  	SocketRef				sock		= kInvalidSocketRef;
  	const char *			ptr;
  	
  	// Check command parameters.
  	
  	if( gDNSCrypt_TimeLimitSecs < -1 )
  	{
  		FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSCrypt_TimeLimitSecs );
  		err = kParamErr;
  		goto exit;
  	}
  	
  	// Create context.
  	
  	context = (DNSCryptContext *) calloc( 1, sizeof( *context ) );
  	require_action( context, exit, err = kNoMemoryErr );
  	
  	context->providerName	= gDNSCrypt_ProviderName;
  	context->qname			= gDNSCrypt_Name;
  	context->timeLimitSecs	= gDNSCrypt_TimeLimitSecs;
  	context->printRawRData	= gDNSCrypt_RawRData ? true : false;
  	
  	err = crypto_box_keypair( context->clientPublicKey, context->clientSecretKey );
  	require_noerr( err, exit );
  	
  	err = HexToData( gDNSCrypt_ProviderKey, kSizeCString, kHexToData_DefaultFlags,
  		context->serverPublicSignKey, sizeof( context->serverPublicSignKey ), &writtenBytes, &totalBytes, &ptr );
  	if( err || ( *ptr != '\0' ) )
  	{
  		FPrintF( stderr, "Failed to parse public signing key hex string (%s).\n", gDNSCrypt_ProviderKey );
  		goto exit;
  	}
  	else if( totalBytes != sizeof( context->serverPublicSignKey ) )
  	{
  		FPrintF( stderr, "Public signing key contains incorrect number of hex bytes (%zu != %zu)\n",
  			totalBytes, sizeof( context->serverPublicSignKey ) );
  		err = kSizeErr;
  		goto exit;
  	}
  	check( writtenBytes == totalBytes );
  	
  	err = StringToSockAddr( gDNSCrypt_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
  	require_noerr( err, exit );
  	if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSCryptPort );
  	
  	err = RecordTypeFromArgString( gDNSCrypt_Type, &context->qtype );
  	require_noerr( err, exit );
  	
  	// Write query message.
  	
  	context->queryID = (uint16_t) Random32();
  	err = WriteDNSQueryMessage( context->msgBuf, context->queryID, kDNSHeaderFlag_RecursionDesired, context->providerName,
  		kDNSServiceType_TXT, kDNSServiceClass_IN, &context->msgLen );
  	require_noerr( err, exit );
  	
  	// Create UDP socket.
  	
  	err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &sock );
  	require_noerr( err, exit );
  	
  	// Send DNS query.
  	
  	context->sendTicks = UpTicks();
  	err = SocketWriteAll( sock, context->msgBuf, context->msgLen, 5 );
  	require_noerr( err, exit );
  	
  	sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
  	require_action( sockContext, exit, err = kNoMemoryErr );
  	
  	err = DispatchReadSourceCreate( sock, DNSCryptReceiveCertHandler, SocketContextCancelHandler, sockContext,
  		&context->readSource );
  	if( err ) ForgetMem( &sockContext );
  	require_noerr( err, exit );
  	
  	sockContext->context	= context;
  	sockContext->sock		= sock;
  	sock = kInvalidSocketRef;
  	dispatch_resume( context->readSource );
  	
  	if( context->timeLimitSecs > 0 )
  	{
  		dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
  			Exit );
  	}
  	dispatch_main();
  	
  exit:
  	if( context ) free( context );
  	ForgetSocket( &sock );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	DNSCryptReceiveCertHandler
  //===========================================================================================================================
  
  static void	DNSCryptReceiveCertHandler( void *inContext )
  {
  	OSStatus					err;
  	const uint64_t				nowTicks	= UpTicks();
  	SocketContext * const		sockContext	= (SocketContext *) inContext;
  	DNSCryptContext * const		context		= (DNSCryptContext *) sockContext->context;
  	const DNSHeader *			hdr;
  	sockaddr_ip					fromAddr;
  	const uint8_t *				ptr;
  	const uint8_t *				txtPtr;
  	size_t						txtLen;
  	unsigned int				answerCount, i;
  	uint8_t						targetName[ kDomainNameLengthMax ];
  	char						time[ kTimestampBufLen ];
  	
  	GetTimestampStr( time );
  	
  	dispatch_source_forget( &context->readSource );
  	
  	err = SocketRecvFrom( sockContext->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
  		&fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL );
  	require_noerr( err, exit );
  	check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
  	
  	FPrintF( stdout, "Receive time: %s\n",			time );
  	FPrintF( stdout, "Source:       %##a\n",		&context->serverAddr );
  	FPrintF( stdout, "Message size: %zu\n",			context->msgLen );
  	FPrintF( stdout, "RTT:          %llu ms\n\n",	UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
  	
  	PrintUDNSMessage( context->msgBuf, context->msgLen, context->printRawRData );
  	
  	require_action_quiet( context->msgLen >= kDNSHeaderLength, exit, err = kSizeErr );
  	
  	hdr = (DNSHeader *) context->msgBuf;
  	require_action_quiet( DNSHeaderGetID( hdr ) == context->queryID, exit, err = kMismatchErr );
  	
  	err = DNSMessageGetAnswerSection( context->msgBuf, context->msgLen, &ptr );
  	require_noerr( err, exit );
  	
  	targetName[ 0 ] = 0;
  	err = DomainNameAppendString( targetName, context->providerName, NULL );
  	require_noerr( err, exit );
  	
  	answerCount = DNSHeaderGetAnswerCount( hdr );
  	for( i = 0; i < answerCount; ++i )
  	{
  		uint16_t		type;
  		uint16_t		class;
  		uint8_t			name[ kDomainNameLengthMax ];
  		
  		err = DNSMessageExtractRecord( context->msgBuf, context->msgLen, ptr, name, &type, &class, NULL, &txtPtr, &txtLen,
  			&ptr );
  		require_noerr( err, exit );
  		
  		if( ( type == kDNSServiceType_TXT ) && ( class == kDNSServiceClass_IN ) && DomainNameEqual( name, targetName ) )
  		{
  			break;
  		}
  	}
  	
  	if( txtLen < ( 1 + kDNSCryptCertMinimumLength ) )
  	{
  		FPrintF( stderr, "TXT record length is too short (%u < %u)\n", txtLen, kDNSCryptCertMinimumLength + 1 );
  		err = kSizeErr;
  		goto exit;
  	}
  	if( txtPtr[ 0 ] < kDNSCryptCertMinimumLength )
  	{
  		FPrintF( stderr, "TXT record value length is too short (%u < %u)\n", txtPtr[ 0 ], kDNSCryptCertMinimumLength );
  		err = kSizeErr;
  		goto exit;
  	}
  	
  	context->certLen = txtPtr[ 0 ];
  	context->certPtr = &txtPtr[ 1 ];
  	
  	dispatch_async_f( dispatch_get_main_queue(), context, DNSCryptProceed );
  	
  exit:
  	if( err ) Exit( NULL );
  }
  
  //===========================================================================================================================
  //	DNSCryptReceiveResponseHandler
  //===========================================================================================================================
  
  static void	DNSCryptReceiveResponseHandler( void *inContext )
  {
  	OSStatus						err;
  	const uint64_t					nowTicks	= UpTicks();
  	SocketContext * const			sockContext	= (SocketContext *) inContext;
  	DNSCryptContext * const			context		= (DNSCryptContext *) sockContext->context;
  	sockaddr_ip						fromAddr;
  	DNSCryptResponseHeader *		hdr;
  	const uint8_t *					end;
  	uint8_t *						ciphertext;
  	uint8_t *						plaintext;
  	const uint8_t *					response;
  	char							time[ kTimestampBufLen ];
  	uint8_t							nonce[ crypto_box_NONCEBYTES ];
  	
  	GetTimestampStr( time );
  	
  	dispatch_source_forget( &context->readSource );
  	
  	err = SocketRecvFrom( sockContext->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
  		&fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL );
  	require_noerr( err, exit );
  	check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
  	
  	FPrintF( stdout, "Receive time: %s\n",			time );
  	FPrintF( stdout, "Source:       %##a\n",		&context->serverAddr );
  	FPrintF( stdout, "Message size: %zu\n",			context->msgLen );
  	FPrintF( stdout, "RTT:          %llu ms\n\n",	UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
  	
  	if( context->msgLen < sizeof( DNSCryptResponseHeader ) )
  	{
  		FPrintF( stderr, "DNSCrypt response is too short.\n" );
  		err = kSizeErr;
  		goto exit;
  	}
  	
  	hdr = (DNSCryptResponseHeader *) context->msgBuf;
  	
  	if( memcmp( hdr->resolverMagic, kDNSCryptResolverMagic, kDNSCryptResolverMagicLength ) != 0 )
  	{
  		FPrintF( stderr, "DNSCrypt response resolver magic %#H != %#H\n",
  			hdr->resolverMagic,		kDNSCryptResolverMagicLength, INT_MAX,
  			kDNSCryptResolverMagic, kDNSCryptResolverMagicLength, INT_MAX );
  		err = kValueErr;
  		goto exit;
  	}
  	
  	if( memcmp( hdr->clientNonce, context->clientNonce, kDNSCryptHalfNonceLength ) != 0 )
  	{
  		FPrintF( stderr, "DNSCrypt response client nonce mismatch.\n" );
  		err = kValueErr;
  		goto exit;
  	}
  	
  	memcpy( nonce, hdr->clientNonce, crypto_box_NONCEBYTES );
  	
  	ciphertext = hdr->poly1305MAC - crypto_box_BOXZEROBYTES;
  	memset( ciphertext, 0, crypto_box_BOXZEROBYTES );
  	
  	plaintext = (uint8_t *)( hdr + 1 ) - crypto_box_ZEROBYTES;
  	check( plaintext == ciphertext );
  	
  	end = context->msgBuf + context->msgLen;
  	
  	err = crypto_box_open_afternm( plaintext, ciphertext, (size_t)( end - ciphertext ), nonce, context->nmKey );
  	require_noerr( err, exit );
  	
  	response = plaintext + crypto_box_ZEROBYTES;
  	PrintUDNSMessage( response, (size_t)( end - response ), context->printRawRData );
  	Exit( kExitReason_ReceivedResponse );
  	
  exit:
  	if( err ) Exit( NULL );
  }
  
  //===========================================================================================================================
  //	DNSCryptProceed
  //===========================================================================================================================
  
  static void	DNSCryptProceed( void *inContext )
  {
  	OSStatus					err;
  	DNSCryptContext * const		context = (DNSCryptContext *) inContext;
  	
  	err = DNSCryptProcessCert( context );
  	require_noerr_quiet( err, exit );
  	
  	err = DNSCryptBuildQuery( context );
  	require_noerr_quiet( err, exit );
  	
  	err = DNSCryptSendQuery( context );
  	require_noerr_quiet( err, exit );
  	
  exit:
  	if( err ) Exit( NULL );
  }
  
  //===========================================================================================================================
  //	DNSCryptProcessCert
  //===========================================================================================================================
  
  static OSStatus	DNSCryptProcessCert( DNSCryptContext *inContext )
  {
  	OSStatus						err;
  	const DNSCryptCert * const		cert	= (DNSCryptCert *) inContext->certPtr;
  	const uint8_t * const			certEnd	= inContext->certPtr + inContext->certLen;
  	struct timeval					now;
  	time_t							startTimeSecs, endTimeSecs;
  	size_t							signedLen;
  	uint8_t *						tempBuf;
  	unsigned long long				tempLen;
  	
  	DNSCryptPrintCertificate( cert, inContext->certLen );
  	
  	if( memcmp( cert->certMagic, kDNSCryptCertMagic, kDNSCryptCertMagicLength ) != 0 )
  	{
  		FPrintF( stderr, "DNSCrypt certificate magic %#H != %#H\n",
  			cert->certMagic,	kDNSCryptCertMagicLength, INT_MAX,
  			kDNSCryptCertMagic, kDNSCryptCertMagicLength, INT_MAX );
  		err = kValueErr;
  		goto exit;
  	}
  	
  	startTimeSecs	= (time_t) ReadBig32( cert->startTime );
  	endTimeSecs		= (time_t) ReadBig32( cert->endTime );
  	
  	gettimeofday( &now, NULL );
  	if( now.tv_sec < startTimeSecs )
  	{
  		FPrintF( stderr, "DNSCrypt certificate start time is in the future.\n" );
  		err = kDateErr;
  		goto exit;
  	}
  	if( now.tv_sec >= endTimeSecs )
  	{
  		FPrintF( stderr, "DNSCrypt certificate has expired.\n" );
  		err = kDateErr;
  		goto exit;
  	}
  	
  	signedLen = (size_t)( certEnd - cert->signature );
  	tempBuf = (uint8_t *) malloc( signedLen );
  	require_action( tempBuf, exit, err = kNoMemoryErr );
  	err = crypto_sign_open( tempBuf, &tempLen, cert->signature, signedLen, inContext->serverPublicSignKey );
  	free( tempBuf );
  	if( err )
  	{
  		FPrintF( stderr, "DNSCrypt certificate failed verification.\n" );
  		err = kAuthenticationErr;
  		goto exit;
  	}
  	
  	memcpy( inContext->serverPublicKey,	cert->publicKey,	crypto_box_PUBLICKEYBYTES );
  	memcpy( inContext->clientMagic,		cert->clientMagic,	kDNSCryptClientMagicLength );
  	
  	err = crypto_box_beforenm( inContext->nmKey, inContext->serverPublicKey, inContext->clientSecretKey );
  	require_noerr( err, exit );
  	
  	inContext->certPtr	= NULL;
  	inContext->certLen	= 0;
  	inContext->msgLen	= 0;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	DNSCryptBuildQuery
  //===========================================================================================================================
  
  static OSStatus	DNSCryptPadQuery( uint8_t *inMsgPtr, size_t inMsgLen, size_t inMaxLen, size_t *outPaddedLen );
  
  static OSStatus	DNSCryptBuildQuery( DNSCryptContext *inContext )
  {
  	OSStatus						err;
  	DNSCryptQueryHeader * const		hdr			= (DNSCryptQueryHeader *) inContext->msgBuf;
  	uint8_t * const					queryPtr	= (uint8_t *)( hdr + 1 );
  	size_t							queryLen;
  	size_t							paddedQueryLen;
  	const uint8_t * const			msgLimit	= inContext->msgBuf + sizeof( inContext->msgBuf );
  	const uint8_t *					padLimit;
  	uint8_t							nonce[ crypto_box_NONCEBYTES ];
  	
  	check_compile_time_code( sizeof( inContext->msgBuf ) >= ( sizeof( DNSCryptQueryHeader ) + kDNSQueryMessageMaxLen ) );
  	
  	inContext->queryID = (uint16_t) Random32();
  	err = WriteDNSQueryMessage( queryPtr, inContext->queryID, kDNSHeaderFlag_RecursionDesired, inContext->qname,
  		inContext->qtype, kDNSServiceClass_IN, &queryLen );
  	require_noerr( err, exit );
  	
  	padLimit = &queryPtr[ queryLen + kDNSCryptMaxPadLength ];
  	if( padLimit > msgLimit ) padLimit = msgLimit;
  	
  	err = DNSCryptPadQuery( queryPtr, queryLen, (size_t)( padLimit - queryPtr ), &paddedQueryLen );
  	require_noerr( err, exit );
  	
  	memset( queryPtr - crypto_box_ZEROBYTES, 0, crypto_box_ZEROBYTES );
  	RandomBytes( inContext->clientNonce, kDNSCryptHalfNonceLength );
  	memcpy( nonce, inContext->clientNonce, kDNSCryptHalfNonceLength );
  	memset( &nonce[ kDNSCryptHalfNonceLength ], 0, kDNSCryptHalfNonceLength );
  	
  	err = crypto_box_afternm( queryPtr - crypto_box_ZEROBYTES, queryPtr - crypto_box_ZEROBYTES,
  		paddedQueryLen + crypto_box_ZEROBYTES, nonce, inContext->nmKey );
  	require_noerr( err, exit );
  	
  	memcpy( hdr->clientMagic,		inContext->clientMagic,		kDNSCryptClientMagicLength );
  	memcpy( hdr->clientPublicKey,	inContext->clientPublicKey,	crypto_box_PUBLICKEYBYTES );
  	memcpy( hdr->clientNonce,		nonce,						kDNSCryptHalfNonceLength );
  	
  	inContext->msgLen = (size_t)( &queryPtr[ paddedQueryLen ] - inContext->msgBuf );
  	
  exit:
  	return( err );
  }
  
  static OSStatus	DNSCryptPadQuery( uint8_t *inMsgPtr, size_t inMsgLen, size_t inMaxLen, size_t *outPaddedLen )
  {
  	OSStatus		err;
  	size_t			paddedLen;
  	
  	require_action_quiet( ( inMsgLen + kDNSCryptMinPadLength ) <= inMaxLen, exit, err = kSizeErr );
  	
  	paddedLen = inMsgLen + kDNSCryptMinPadLength +
  		arc4random_uniform( (uint32_t)( inMaxLen - ( inMsgLen + kDNSCryptMinPadLength ) + 1 ) );
  	paddedLen += ( kDNSCryptBlockSize - ( paddedLen % kDNSCryptBlockSize ) );
  	if( paddedLen > inMaxLen ) paddedLen = inMaxLen;
  	
  	inMsgPtr[ inMsgLen ] = 0x80;
  	memset( &inMsgPtr[ inMsgLen + 1 ], 0, paddedLen - ( inMsgLen + 1 ) );
  	
  	if( outPaddedLen ) *outPaddedLen = paddedLen;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	DNSCryptSendQuery
  //===========================================================================================================================
  
  static OSStatus	DNSCryptSendQuery( DNSCryptContext *inContext )
  {
  	OSStatus			err;
  	SocketContext *		sockContext;
  	SocketRef			sock = kInvalidSocketRef;
  	
  	check( inContext->msgLen > 0 );
  	check( !inContext->readSource );
  	
  	err = UDPClientSocketOpen( AF_UNSPEC, &inContext->serverAddr, 0, -1, NULL, &sock );
  	require_noerr( err, exit );
  	
  	inContext->sendTicks = UpTicks();
  	err = SocketWriteAll( sock, inContext->msgBuf, inContext->msgLen, 5 );
  	require_noerr( err, exit );
  	
  	sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
  	require_action( sockContext, exit, err = kNoMemoryErr );
  	
  	err = DispatchReadSourceCreate( sock, DNSCryptReceiveResponseHandler, SocketContextCancelHandler, sockContext,
  		&inContext->readSource );
  	if( err ) ForgetMem( &sockContext );
  	require_noerr( err, exit );
  	
  	sockContext->context	= inContext;
  	sockContext->sock		= sock;
  	sock = kInvalidSocketRef;
  	
  	dispatch_resume( inContext->readSource );
  	
  exit:
  	ForgetSocket( &sock );
  	return( err );
  }
  
  //===========================================================================================================================
  //	DNSCryptPrintCertificate
  //===========================================================================================================================
  
  #define kCertTimeStrBufLen		32
  
  static char *	CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] );
  
  static void	DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen )
  {
  	time_t		startTime, endTime;
  	int			extLen;
  	char		timeBuf[ kCertTimeStrBufLen ];
  	
  	check( inLen >= kDNSCryptCertMinimumLength );
  	
  	startTime	= (time_t) ReadBig32( inCert->startTime );
  	endTime		= (time_t) ReadBig32( inCert->endTime );
  	
  	FPrintF( stdout, "DNSCrypt certificate (%zu bytes):\n", inLen );
  	FPrintF( stdout, "Cert Magic:    %#H\n", inCert->certMagic, kDNSCryptCertMagicLength, INT_MAX );
  	FPrintF( stdout, "ES Version:    %u\n",	ReadBig16( inCert->esVersion ) );
  	FPrintF( stdout, "Minor Version: %u\n",	ReadBig16( inCert->minorVersion ) );
  	FPrintF( stdout, "Signature:     %H\n",	inCert->signature, crypto_sign_BYTES / 2, INT_MAX );
  	FPrintF( stdout, "               %H\n",	&inCert->signature[ crypto_sign_BYTES / 2 ], crypto_sign_BYTES / 2, INT_MAX );
  	FPrintF( stdout, "Public Key:    %H\n", inCert->publicKey, sizeof( inCert->publicKey ), INT_MAX );
  	FPrintF( stdout, "Client Magic:  %H\n", inCert->clientMagic, kDNSCryptClientMagicLength, INT_MAX );
  	FPrintF( stdout, "Serial:        %u\n",	ReadBig32( inCert->serial ) );
  	FPrintF( stdout, "Start Time:    %u (%s)\n", (uint32_t) startTime, CertTimeStr( startTime, timeBuf ) );
  	FPrintF( stdout, "End Time:      %u (%s)\n", (uint32_t) endTime, CertTimeStr( endTime, timeBuf ) );
  	
  	if( inLen > kDNSCryptCertMinimumLength )
  	{
  		extLen = (int)( inLen - kDNSCryptCertMinimumLength );
  		FPrintF( stdout, "Extensions:    %.1H\n", inCert->extensions, extLen, extLen );
  	}
  	FPrintF( stdout, "\n" );
  }
  
  static char *	CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] )
  {
  	struct tm *		tm;
  	
  	tm = localtime( &inTime );
  	if( !tm )
  	{
  		dlogassert( "localtime() returned a NULL pointer.\n" );
  		*inBuffer = '\0';
  	}
  	else
  	{
  		strftime( inBuffer, kCertTimeStrBufLen, "%a %b %d %H:%M:%S %Z %Y", tm );
  	}
  	
  	return( inBuffer );
  }
  
  #endif	// DNSSDUTIL_INCLUDE_DNSCRYPT
  
  //===========================================================================================================================
  //	MDNSQueryCmd
  //===========================================================================================================================
  
  #define kMDNSPort					5353
  
  #define kDefaultMDNSMessageID		0
  #define kDefaultMDNSQueryFlags		0
  
  typedef struct
  {
  	const char *			qnameStr;							// Name (QNAME) of the record being queried as a C string.
  	dispatch_source_t		readSourceV4;						// Read dispatch source for IPv4 socket.
  	dispatch_source_t		readSourceV6;						// Read dispatch source for IPv6 socket.
  	int						localPort;							// The port number to which the sockets are bound.
  	int						receiveSecs;						// After send, the amount of time to spend receiving.
  	uint32_t				ifIndex;							// Index of the interface over which to send the query.
  	uint16_t				qtype;								// The type (QTYPE) of the record being queried.
  	Boolean					isQU;								// True if the query is QU, i.e., requests unicast responses.
  	Boolean					allResponses;						// True if all mDNS messages received should be printed.
  	Boolean					printRawRData;						// True if RDATA should be printed as hexdumps.
  	Boolean					useIPv4;							// True if the query should be sent via IPv4 multicast.
  	Boolean					useIPv6;							// True if the query should be sent via IPv6 multicast.
  	char					ifName[ IF_NAMESIZE + 1 ];			// Name of the interface over which to send the query.
  	uint8_t					qname[ kDomainNameLengthMax ];		// Buffer to hold the QNAME in label format.
  	uint8_t					msgBuf[ 8940 ];						// Message buffer. 8940 is max size used by mDNSResponder.
  	
  }	MDNSQueryContext;
  
  static void	MDNSQueryPrintPrologue( const MDNSQueryContext *inContext );
  static void	MDNSQueryReadHandler( void *inContext );
  
  static void	MDNSQueryCmd( void )
  {
  	OSStatus				err;
  	MDNSQueryContext *		context;
  	struct sockaddr_in		mcastAddr4;
  	struct sockaddr_in6		mcastAddr6;
  	SocketRef				sockV4 = kInvalidSocketRef;
  	SocketRef				sockV6 = kInvalidSocketRef;
  	ssize_t					n;
  	const char *			ifNamePtr;
  	size_t					msgLen;
  	unsigned int			sendCount;
  	
  	// Check command parameters.
  	
  	if( gMDNSQuery_ReceiveSecs < -1 )
  	{
  		FPrintF( stdout, "Invalid receive time value: %d seconds.\n", gMDNSQuery_ReceiveSecs );
  		err = kParamErr;
  		goto exit;
  	}
  	
  	context = (MDNSQueryContext *) calloc( 1, sizeof( *context ) );
  	require_action( context, exit, err = kNoMemoryErr );
  	
  	context->qnameStr		= gMDNSQuery_Name;
  	context->receiveSecs	= gMDNSQuery_ReceiveSecs;
  	context->isQU			= gMDNSQuery_IsQU		  ? true : false;
  	context->allResponses	= gMDNSQuery_AllResponses ? true : false;
  	context->printRawRData	= gMDNSQuery_RawRData	  ? true : false;
  	context->useIPv4		= ( gMDNSQuery_UseIPv4 || !gMDNSQuery_UseIPv6 ) ? true : false;
  	context->useIPv6		= ( gMDNSQuery_UseIPv6 || !gMDNSQuery_UseIPv4 ) ? true : false;
  	
  	err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
  	require_noerr_quiet( err, exit );
  	
  	ifNamePtr = if_indextoname( context->ifIndex, context->ifName );
  	require_action( ifNamePtr, exit, err = kNameErr );
  	
  	err = RecordTypeFromArgString( gMDNSQuery_Type, &context->qtype );
  	require_noerr( err, exit );
  	
  	// Set up IPv4 socket.
  	
  	if( context->useIPv4 )
  	{
  		err = ServerSocketOpen( AF_INET, SOCK_DGRAM, IPPROTO_UDP,
  			gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ),
  			&context->localPort, kSocketBufferSize_DontSet, &sockV4 );
  		require_noerr( err, exit );
  		
  		err = SocketSetMulticastInterface( sockV4, ifNamePtr, context->ifIndex );
  		require_noerr( err, exit );
  		
  		err = setsockopt( sockV4, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) );
  		err = map_socket_noerr_errno( sockV4, err );
  		require_noerr( err, exit );
  		
  		memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) );
  		SIN_LEN_SET( &mcastAddr4 );
  		mcastAddr4.sin_family		= AF_INET;
  		mcastAddr4.sin_port			= htons( kMDNSPort );
  		mcastAddr4.sin_addr.s_addr	= htonl( 0xE00000FB );	// The mDNS IPv4 multicast address is 224.0.0.251
  		
  		if( !context->isQU && ( context->localPort == kMDNSPort ) )
  		{
  			SocketJoinMulticast( sockV4, &mcastAddr4, ifNamePtr, context->ifIndex );
  			require_noerr( err, exit );
  		}
  	}
  	
  	// Set up IPv6 socket.
  	
  	if( context->useIPv6 )
  	{
  		err = ServerSocketOpen( AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
  			gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ),
  			&context->localPort, kSocketBufferSize_DontSet, &sockV6 );
  		require_noerr( err, exit );
  		
  		err = SocketSetMulticastInterface( sockV6, ifNamePtr, context->ifIndex );
  		require_noerr( err, exit );
  		
  		err = setsockopt( sockV6, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) );
  		err = map_socket_noerr_errno( sockV6, err );
  		require_noerr( err, exit );
  		
  		memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) );
  		SIN6_LEN_SET( &mcastAddr6 );
  		mcastAddr6.sin6_family	= AF_INET6;
  		mcastAddr6.sin6_port	= htons( kMDNSPort );
  		mcastAddr6.sin6_addr.s6_addr[  0 ] = 0xFF;	// mDNS IPv6 multicast address FF02::FB
  		mcastAddr6.sin6_addr.s6_addr[  1 ] = 0x02;
  		mcastAddr6.sin6_addr.s6_addr[ 15 ] = 0xFB;
  		
  		if( !context->isQU && ( context->localPort == kMDNSPort ) )
  		{
  			SocketJoinMulticast( sockV6, &mcastAddr6, ifNamePtr, context->ifIndex );
  			require_noerr( err, exit );
  		}
  	}
  	
  	// Craft mDNS query message.
  	
  	check_compile_time_code( sizeof( context->msgBuf ) >= kDNSQueryMessageMaxLen );
  	err = WriteDNSQueryMessage( context->msgBuf, kDefaultMDNSMessageID, kDefaultMDNSQueryFlags, context->qnameStr,
  		context->qtype, context->isQU ? ( kDNSServiceClass_IN | kQClassUnicastResponseBit ) : kDNSServiceClass_IN, &msgLen );
  	require_noerr( err, exit );
  	
  	// Print prologue.
  	
  	MDNSQueryPrintPrologue( context );
  	
  	// Send mDNS query message.
  	
  	sendCount = 0;
  	if( IsValidSocket( sockV4 ) )
  	{
  		n = sendto( sockV4, context->msgBuf, msgLen, 0, (struct sockaddr *) &mcastAddr4, (socklen_t) sizeof( mcastAddr4 ) );
  		err = map_socket_value_errno( sockV4, n == (ssize_t) msgLen, n );
  		if( err )
  		{
  			FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
  			ForgetSocket( &sockV4 );
  		}
  		else
  		{
  			++sendCount;
  		}
  	}
  	if( IsValidSocket( sockV6 ) )
  	{
  		n = sendto( sockV6, context->msgBuf, msgLen, 0, (struct sockaddr *) &mcastAddr6, (socklen_t) sizeof( mcastAddr6 ) );
  		err = map_socket_value_errno( sockV6, n == (ssize_t) msgLen, n );
  		if( err )
  		{
  			FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err );
  			ForgetSocket( &sockV6 );
  		}
  		else
  		{
  			++sendCount;
  		}
  	}
  	require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
  	
  	// If there's no wait period after the send, then exit.
  	
  	if( context->receiveSecs == 0 ) goto exit;
  	
  	// Create dispatch read sources for socket(s).
  	
  	if( IsValidSocket( sockV4 ) )
  	{
  		SocketContext *		sockContext;
  		
  		sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
  		require_action( sockContext, exit, err = kNoMemoryErr );
  		
  		err = DispatchReadSourceCreate( sockV4, MDNSQueryReadHandler, SocketContextCancelHandler, sockContext,
  			&context->readSourceV4 );
  		if( err ) ForgetMem( &sockContext );
  		require_noerr( err, exit );
  		
  		sockContext->context	= context;
  		sockContext->sock		= sockV4;
  		sockV4 = kInvalidSocketRef;
  		dispatch_resume( context->readSourceV4 );
  	}
  	
  	if( IsValidSocket( sockV6 ) )
  	{
  		SocketContext *		sockContext;
  		
  		sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
  		require_action( sockContext, exit, err = kNoMemoryErr );
  		
  		err = DispatchReadSourceCreate( sockV6, MDNSQueryReadHandler, SocketContextCancelHandler, sockContext,
  			&context->readSourceV6 );
  		if( err ) ForgetMem( &sockContext );
  		require_noerr( err, exit );
  		
  		sockContext->context	= context;
  		sockContext->sock		= sockV6;
  		sockV6 = kInvalidSocketRef;
  		dispatch_resume( context->readSourceV6 );
  	}
  	
  	if( context->receiveSecs > 0 )
  	{
  		dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
  			Exit );
  	}
  	dispatch_main();
  	
  exit:
  	ForgetSocket( &sockV4 );
  	ForgetSocket( &sockV6 );
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	MDNSQueryPrintPrologue
  //===========================================================================================================================
  
  static void	MDNSQueryPrintPrologue( const MDNSQueryContext *inContext )
  {
  	const int		receiveSecs = inContext->receiveSecs;
  	char			time[ kTimestampBufLen ];
  	
  	FPrintF( stdout, "Interface:        %d (%s)\n",		(int32_t) inContext->ifIndex, inContext->ifName );
  	FPrintF( stdout, "Name:             %s\n",			inContext->qnameStr );
  	FPrintF( stdout, "Type:             %s (%u)\n",		RecordTypeToString( inContext->qtype ), inContext->qtype );
  	FPrintF( stdout, "Class:            IN (%s)\n",		inContext->isQU ? "QU" : "QM" );
  	FPrintF( stdout, "Local port:       %d\n",			inContext->localPort );
  	FPrintF( stdout, "IP protocols:     %?s%?s%?s\n",
  		inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" );
  	FPrintF( stdout, "Receive duration: " );
  	if( receiveSecs >= 0 )	FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' );
  	else					FPrintF( stdout, "∞\n" );
  	FPrintF( stdout, "Start time:  %s\n",		GetTimestampStr( time ) );
  }
  
  //===========================================================================================================================
  //	MDNSQueryReadHandler
  //===========================================================================================================================
  
  static void	MDNSQueryReadHandler( void *inContext )
  {
  	OSStatus						err;
  	SocketContext * const			sockContext	= (SocketContext *) inContext;
  	MDNSQueryContext * const		context		= (MDNSQueryContext *) sockContext->context;
  	size_t							msgLen;
  	sockaddr_ip						fromAddr;
  	char							time[ kTimestampBufLen ];
  	Boolean							foundAnswer	= false;
  	
  	GetTimestampStr( time );
  	
  	err = SocketRecvFrom( sockContext->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &fromAddr,
  		sizeof( fromAddr ), NULL, NULL, NULL, NULL );
  	require_noerr( err, exit );
  	
  	if( !context->allResponses && ( msgLen >= kDNSHeaderLength ) )
  	{
  		const uint8_t *				ptr;
  		const DNSHeader * const		hdr = (DNSHeader *) context->msgBuf;
  		unsigned int				rrCount, i;
  		uint16_t					type, class;
  		uint8_t						name[ kDomainNameLengthMax ];
  		
  		err = DNSMessageGetAnswerSection( context->msgBuf, msgLen, &ptr );
  		require_noerr( err, exit );
  		
  		if( context->qname[ 0 ] == 0 )
  		{
  			err = DomainNameAppendString( context->qname, context->qnameStr, NULL );
  			require_noerr( err, exit );
  		}
  		
  		rrCount = DNSHeaderGetAnswerCount( hdr ) + DNSHeaderGetAuthorityCount( hdr ) + DNSHeaderGetAdditionalCount( hdr );
  		for( i = 0; i < rrCount; ++i )
  		{
  			err = DNSMessageExtractRecord( context->msgBuf, msgLen, ptr, name, &type, &class, NULL, NULL, NULL, &ptr );
  			require_noerr( err, exit );
  			
  			if( ( ( context->qtype == kDNSServiceType_ANY ) || ( type == context->qtype ) ) &&
  				DomainNameEqual( name, context->qname ) )
  			{
  				foundAnswer = true;
  				break;
  			}
  		}
  	}
  	if( context->allResponses || foundAnswer )
  	{
  		FPrintF( stdout, "---\n" );
  		FPrintF( stdout, "Receive time: %s\n",		time );
  		FPrintF( stdout, "Source:       %##a\n",	&fromAddr );
  		FPrintF( stdout, "Message size: %zu\n\n",	msgLen );
  		
  		PrintMDNSMessage( context->msgBuf, msgLen, context->printRawRData );
  	}
  	
  exit:
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	PIDToUUIDCmd
  //===========================================================================================================================
  
  static void	PIDToUUIDCmd( void )
  {
  	OSStatus							err;
  	int									n;
  	struct proc_uniqidentifierinfo		info;
  	
  	n = proc_pidinfo( gPIDToUUID_PID, PROC_PIDUNIQIDENTIFIERINFO, 1, &info, sizeof( info ) );
  	require_action_quiet( n == (int) sizeof( info ), exit, err = kUnknownErr );
  	
  	FPrintF( stdout, "%#U\n", info.p_uuid );
  	err = kNoErr;
  	
  exit:
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	DaemonVersionCmd
  //===========================================================================================================================
  
  static void	DaemonVersionCmd( void )
  {
  	OSStatus		err;
  	uint32_t		size, version;
  	char			strBuf[ 16 ];
  	
  	size = (uint32_t) sizeof( version );
  	err = DNSServiceGetProperty( kDNSServiceProperty_DaemonVersion, &version, &size );
  	require_noerr( err, exit );
  	
  	FPrintF( stdout, "Daemon version: %s\n", SourceVersionToCString( version, strBuf ) );
  	
  exit:
  	if( err ) exit( 1 );
  }
  
  //===========================================================================================================================
  //	Exit
  //===========================================================================================================================
  
  static void	Exit( void *inContext )
  {
  	const char * const		reason = (const char *) inContext;
  	char					time[ kTimestampBufLen ];
  	
  	FPrintF( stdout, "---\n" );
  	FPrintF( stdout, "End time:   %s\n", GetTimestampStr( time ) );
  	if( reason ) FPrintF( stdout, "End reason: %s\n", reason );
  	exit( gExitCode );
  }
  
  //===========================================================================================================================
  //	GetTimestampStr
  //===========================================================================================================================
  
  static char *	GetTimestampStr( char inBuffer[ kTimestampBufLen ] )
  {
  	struct timeval		now;
  	struct tm *			tm;
  	size_t				len;
  	
  	gettimeofday( &now, NULL );
  	tm = localtime( &now.tv_sec );
  	require_action( tm, exit, *inBuffer = '\0' );
  	
  	len = strftime( inBuffer, kTimestampBufLen, "%Y-%m-%d %H:%M:%S", tm );
  	SNPrintF( &inBuffer[ len ], kTimestampBufLen - len, ".%06u", (unsigned int) now.tv_usec );
  	
  exit:
  	return( inBuffer );
  }
  
  //===========================================================================================================================
  //	GetDNSSDFlagsFromOpts
  //===========================================================================================================================
  
  static DNSServiceFlags	GetDNSSDFlagsFromOpts( void )
  {
  	DNSServiceFlags		flags;
  	
  	flags = (DNSServiceFlags) gDNSSDFlags;
  	if( flags & kDNSServiceFlagsShareConnection )
  	{
  		FPrintF( stderr, "*** Warning: kDNSServiceFlagsShareConnection (0x%X) is explicitly set in flag parameters.\n",
  			kDNSServiceFlagsShareConnection );
  	}
  	
  	if( gDNSSDFlag_BrowseDomains )			flags |= kDNSServiceFlagsBrowseDomains;
  	if( gDNSSDFlag_DenyCellular )			flags |= kDNSServiceFlagsDenyCellular;
  	if( gDNSSDFlag_DenyExpensive )			flags |= kDNSServiceFlagsDenyExpensive;
  	if( gDNSSDFlag_ForceMulticast )			flags |= kDNSServiceFlagsForceMulticast;
  	if( gDNSSDFlag_IncludeAWDL )			flags |= kDNSServiceFlagsIncludeAWDL;
  	if( gDNSSDFlag_NoAutoRename )			flags |= kDNSServiceFlagsNoAutoRename;
  	if( gDNSSDFlag_PathEvaluationDone )		flags |= kDNSServiceFlagsPathEvaluationDone;
  	if( gDNSSDFlag_RegistrationDomains )	flags |= kDNSServiceFlagsRegistrationDomains;
  	if( gDNSSDFlag_ReturnIntermediates )	flags |= kDNSServiceFlagsReturnIntermediates;
  	if( gDNSSDFlag_Shared )					flags |= kDNSServiceFlagsShared;
  	if( gDNSSDFlag_SuppressUnusable )		flags |= kDNSServiceFlagsSuppressUnusable;
  	if( gDNSSDFlag_Timeout )				flags |= kDNSServiceFlagsTimeout;
  	if( gDNSSDFlag_UnicastResponse )		flags |= kDNSServiceFlagsUnicastResponse;
  	if( gDNSSDFlag_Unique )					flags |= kDNSServiceFlagsUnique;
  	
  	return( flags );
  }
  
  //===========================================================================================================================
  //	CreateConnectionFromArgString
  //===========================================================================================================================
  
  static OSStatus
  	CreateConnectionFromArgString(
  		const char *			inString,
  		dispatch_queue_t		inQueue,
  		DNSServiceRef *			outSDRef,
  		ConnectionDesc *		outDesc )
  {
  	OSStatus			err;
  	DNSServiceRef		sdRef = NULL;
  	ConnectionType		type;
  	int32_t				pid = -1;	// Initializing because the analyzer claims pid may be used uninitialized.
  	uint8_t				uuid[ 16 ];
  	
  	if( strcasecmp( inString, kConnectionArg_Normal ) == 0 )
  	{
  		err = DNSServiceCreateConnection( &sdRef );
  		require_noerr( err, exit );
  		type = kConnectionType_Normal;
  	}
  	else if( stricmp_prefix( inString, kConnectionArgPrefix_PID ) == 0 )
  	{
  		const char * const		pidStr = inString + sizeof_string( kConnectionArgPrefix_PID );
  		
  		err = StringToInt32( pidStr, &pid );
  		if( err )
  		{
  			FPrintF( stderr, "Invalid delegate connection PID value: %s\n", pidStr );
  			err = kParamErr;
  			goto exit;
  		}
  		
  		memset( uuid, 0, sizeof( uuid ) );
  		err = DNSServiceCreateDelegateConnection( &sdRef, pid, uuid );
  		if( err )
  		{
  			FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for PID %d\n", err, pid );
  			goto exit;
  		}
  		type = kConnectionType_DelegatePID;
  	}
  	else if( stricmp_prefix( inString, kConnectionArgPrefix_UUID ) == 0 )
  	{
  		const char * const		uuidStr = inString + sizeof_string( kConnectionArgPrefix_UUID );
  		
  		check_compile_time_code( sizeof( uuid ) == sizeof( uuid_t ) );
  		
  		err = StringToUUID( uuidStr, kSizeCString, false, uuid );
  		if( err )
  		{
  			FPrintF( stderr, "Invalid delegate connection UUID value: %s\n", uuidStr );
  			err = kParamErr;
  			goto exit;
  		}
  		
  		err = DNSServiceCreateDelegateConnection( &sdRef, 0, uuid );
  		if( err )
  		{
  			FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for UUID %#U\n", err, uuid );
  			goto exit;
  		}
  		type = kConnectionType_DelegateUUID;
  	}
  	else
  	{
  		FPrintF( stderr, "Unrecognized connection string \"%s\".\n", inString );
  		err = kParamErr;
  		goto exit;
  	}
  	
  	err = DNSServiceSetDispatchQueue( sdRef, inQueue );
  	require_noerr( err, exit );
  	
  	*outSDRef = sdRef;
  	if( outDesc )
  	{
  		outDesc->type = type;
  		if(      type == kConnectionType_DelegatePID )	outDesc->delegate.pid = pid;
  		else if( type == kConnectionType_DelegateUUID )	memcpy( outDesc->delegate.uuid, uuid, 16 );
  	}
  	sdRef = NULL;
  	
  exit:
  	if( sdRef ) DNSServiceRefDeallocate( sdRef );
  	return( err );
  }
  
  //===========================================================================================================================
  //	InterfaceIndexFromArgString
  //===========================================================================================================================
  
  static OSStatus	InterfaceIndexFromArgString( const char *inString, uint32_t *outIndex )
  {
  	OSStatus		err;
  	uint32_t		ifIndex;
  	
  	if( inString )
  	{
  		ifIndex = if_nametoindex( inString );
  		if( ifIndex == 0 )
  		{
  			err = StringToUInt32( inString, &ifIndex );
  			if( err )
  			{
  				FPrintF( stderr, "Invalid interface value: %s\n", inString );
  				err = kParamErr;
  				goto exit;
  			}
  		}
  	}
  	else
  	{
  		ifIndex	= 0;
  	}
  	
  	*outIndex = ifIndex;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	RecordDataFromArgString
  //===========================================================================================================================
  
  #define kRDataMaxLen		UINT16_C( 0xFFFF )
  
  static OSStatus	RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen )
  {
  	OSStatus		err;
  	uint8_t *		dataPtr = NULL;
  	size_t			dataLen;
  	DataBuffer		dataBuf;
  	
  	DataBuffer_Init( &dataBuf, NULL, 0, kRDataMaxLen );
  	
  	if( stricmp_prefix( inString, kRDataArgPrefix_String ) == 0 )
  	{
  		const char * const		strPtr = inString + sizeof_string( kRDataArgPrefix_String );
  		const size_t			strLen = strlen( strPtr );
  		size_t					copiedLen;
  		size_t					totalLen;
  		
  		if( strLen > 0 )
  		{
  			require_action( strLen <= kRDataMaxLen, exit, err = kSizeErr );
  			dataPtr = (uint8_t *) malloc( strLen );
  			require_action( dataPtr, exit, err = kNoMemoryErr );
  			
  			copiedLen = 0;
  			ParseQuotedEscapedString( strPtr, strPtr + strLen, "", (char *) dataPtr, strLen, &copiedLen, &totalLen, NULL );
  			check( copiedLen == totalLen );
  			dataLen = copiedLen;
  		}
  		else
  		{
  			dataPtr = NULL;
  			dataLen = 0;
  		}
  	}
  	else if( stricmp_prefix( inString, kRDataArgPrefix_HexString ) == 0 )
  	{
  		const char * const		strPtr = inString + sizeof_string( kRDataArgPrefix_HexString );
  		
  		err = HexToDataCopy( strPtr, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL );
  		require_noerr( err, exit );
  		require_action( dataLen <= kRDataMaxLen, exit, err = kSizeErr );
  	}
  	else if( stricmp_prefix( inString, kRDataArgPrefix_File ) == 0 )
  	{
  		const char * const		path = inString + sizeof_string( kRDataArgPrefix_File );
  		
  		err = CopyFileDataByPath( path, (char **) &dataPtr, &dataLen );
  		require_noerr( err, exit );
  		require_action( dataLen <= kRDataMaxLen, exit, err = kSizeErr );
  	}
  	else if( stricmp_prefix( inString, kRDataArgPrefix_TXT ) == 0 )
  	{
  		const char *			strPtr = inString + sizeof_string( kRDataArgPrefix_TXT );
  		const char * const		strEnd = strPtr + strlen( strPtr );
  		
  		while( strPtr < strEnd )
  		{
  			size_t		copiedLen, totalLen;
  			uint8_t		kvBuf[ 1 + 255 + 1 ];	// Length byte + max key-value length + 1 for NUL terminator.
  			
  			err = ParseEscapedString( strPtr, strEnd, ',', (char *) &kvBuf[ 1 ], sizeof( kvBuf ) - 1,
  				&copiedLen, &totalLen, &strPtr );
  			require_noerr_quiet( err, exit );
  			check( copiedLen == totalLen );
  			if( totalLen > 255 )
  			{
  				FPrintF( stderr, "TXT key-value pair length %zu is too long (> 255 bytes).\n", totalLen );
  				err = kParamErr;
  				goto exit;
  			}
  			
  			kvBuf[ 0 ] = (uint8_t) copiedLen;
  			err = DataBuffer_Append( &dataBuf, kvBuf, 1 + kvBuf[ 0 ] );
  			require_noerr( err, exit );
  		}
  		
  		err = DataBuffer_Commit( &dataBuf, NULL, NULL );
  		require_noerr( err, exit );
  		
  		err = DataBuffer_Detach( &dataBuf, &dataPtr, &dataLen );
  		require_noerr( err, exit );
  	}
  	else
  	{
  		FPrintF( stderr, "Unrecognized record data string \"%s\".\n", inString );
  		err = kParamErr;
  		goto exit;
  	}
  	err = kNoErr;
  	
  	*outDataLen = dataLen;
  	*outDataPtr = dataPtr;
  	dataPtr = NULL;
  	
  exit:
  	DataBuffer_Free( &dataBuf );
  	FreeNullSafe( dataPtr );
  	return( err );
  }
  
  //===========================================================================================================================
  //	RecordTypeFromArgString
  //===========================================================================================================================
  
  typedef struct
  {
  	uint16_t			value;	// Record type's numeric value.
  	const char *		name;	// Record type's name as a string (e.g., "A", "PTR", "SRV").
  	
  }	RecordType;
  
  static const RecordType		kRecordTypes[] =
  {
  	// Common types.
  	
  	{ kDNSServiceType_A,			"A" },
  	{ kDNSServiceType_AAAA,			"AAAA" },
  	{ kDNSServiceType_PTR,			"PTR" },
  	{ kDNSServiceType_SRV,			"SRV" },
  	{ kDNSServiceType_TXT,			"TXT" },
  	{ kDNSServiceType_CNAME,		"CNAME" },
  	{ kDNSServiceType_SOA,			"SOA" },
  	{ kDNSServiceType_NSEC,			"NSEC" },
  	{ kDNSServiceType_NS,			"NS" },
  	{ kDNSServiceType_MX,			"MX" },
  	{ kDNSServiceType_ANY,			"ANY" },
  	{ kDNSServiceType_OPT,			"OPT" },
  	
  	// Less common types.
  	
  	{ kDNSServiceType_MD,			"MD" },
  	{ kDNSServiceType_NS,			"NS" },
  	{ kDNSServiceType_MD,			"MD" },
  	{ kDNSServiceType_MF,			"MF" },
  	{ kDNSServiceType_MB,			"MB" },
  	{ kDNSServiceType_MG,			"MG" },
  	{ kDNSServiceType_MR,			"MR" },
  	{ kDNSServiceType_NULL,			"NULL" },
  	{ kDNSServiceType_WKS,			"WKS" },
  	{ kDNSServiceType_HINFO,		"HINFO" },
  	{ kDNSServiceType_MINFO,		"MINFO" },
  	{ kDNSServiceType_RP,			"RP" },
  	{ kDNSServiceType_AFSDB,		"AFSDB" },
  	{ kDNSServiceType_X25,			"X25" },
  	{ kDNSServiceType_ISDN,			"ISDN" },
  	{ kDNSServiceType_RT,			"RT" },
  	{ kDNSServiceType_NSAP,			"NSAP" },
  	{ kDNSServiceType_NSAP_PTR,		"NSAP_PTR" },
  	{ kDNSServiceType_SIG,			"SIG" },
  	{ kDNSServiceType_KEY,			"KEY" },
  	{ kDNSServiceType_PX,			"PX" },
  	{ kDNSServiceType_GPOS,			"GPOS" },
  	{ kDNSServiceType_LOC,			"LOC" },
  	{ kDNSServiceType_NXT,			"NXT" },
  	{ kDNSServiceType_EID,			"EID" },
  	{ kDNSServiceType_NIMLOC,		"NIMLOC" },
  	{ kDNSServiceType_ATMA,			"ATMA" },
  	{ kDNSServiceType_NAPTR,		"NAPTR" },
  	{ kDNSServiceType_KX,			"KX" },
  	{ kDNSServiceType_CERT,			"CERT" },
  	{ kDNSServiceType_A6,			"A6" },
  	{ kDNSServiceType_DNAME,		"DNAME" },
  	{ kDNSServiceType_SINK,			"SINK" },
  	{ kDNSServiceType_APL,			"APL" },
  	{ kDNSServiceType_DS,			"DS" },
  	{ kDNSServiceType_SSHFP,		"SSHFP" },
  	{ kDNSServiceType_IPSECKEY,		"IPSECKEY" },
  	{ kDNSServiceType_RRSIG,		"RRSIG" },
  	{ kDNSServiceType_DNSKEY,		"DNSKEY" },
  	{ kDNSServiceType_DHCID,		"DHCID" },
  	{ kDNSServiceType_NSEC3,		"NSEC3" },
  	{ kDNSServiceType_NSEC3PARAM,	"NSEC3PARAM" },
  	{ kDNSServiceType_HIP,			"HIP" },
  	{ kDNSServiceType_SPF,			"SPF" },
  	{ kDNSServiceType_UINFO,		"UINFO" },
  	{ kDNSServiceType_UID,			"UID" },
  	{ kDNSServiceType_GID,			"GID" },
  	{ kDNSServiceType_UNSPEC,		"UNSPEC" },
  	{ kDNSServiceType_TKEY,			"TKEY" },
  	{ kDNSServiceType_TSIG,			"TSIG" },
  	{ kDNSServiceType_IXFR,			"IXFR" },
  	{ kDNSServiceType_AXFR,			"AXFR" },
  	{ kDNSServiceType_MAILB,		"MAILB" },
  	{ kDNSServiceType_MAILA,		"MAILA" }
  };
  
  static OSStatus	RecordTypeFromArgString( const char *inString, uint16_t *outValue )
  {
  	OSStatus						err;
  	int32_t							i32;
  	const RecordType *				type;
  	const RecordType * const		end = kRecordTypes + countof( kRecordTypes );
  	
  	for( type = kRecordTypes; type < end; ++type )
  	{
  		if( strcasecmp( type->name, inString ) == 0 )
  		{
  			*outValue = type->value;
  			return( kNoErr );
  		}
  	}
  	
  	err = StringToInt32( inString, &i32 );
  	require_noerr_quiet( err, exit );
  	require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
  	
  	*outValue = (uint16_t) i32;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	RecordClassFromArgString
  //===========================================================================================================================
  
  static OSStatus	RecordClassFromArgString( const char *inString, uint16_t *outValue )
  {
  	OSStatus		err;
  	int32_t			i32;
  	
  	if( strcasecmp( inString, "IN" ) == 0 )
  	{
  		*outValue = kDNSServiceClass_IN;
  		err = kNoErr;
  		goto exit;
  	}
  	
  	err = StringToInt32( inString, &i32 );
  	require_noerr_quiet( err, exit );
  	require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
  	
  	*outValue = (uint16_t) i32;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	InterfaceIndexToName
  //===========================================================================================================================
  
  static char * InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] )
  {
  	switch( inIfIndex )
  	{
  		case kDNSServiceInterfaceIndexAny:
  			strlcpy( inNameBuf, "Any", kInterfaceNameBufLen );
  			break;
  		
  		case kDNSServiceInterfaceIndexLocalOnly:
  			strlcpy( inNameBuf, "LocalOnly", kInterfaceNameBufLen );
  			break;
  		
  		case kDNSServiceInterfaceIndexUnicast:
  			strlcpy( inNameBuf, "Unicast", kInterfaceNameBufLen );
  			break;
  		
  		case kDNSServiceInterfaceIndexP2P:
  			strlcpy( inNameBuf, "P2P", kInterfaceNameBufLen );
  			break;
  		
  	#if( defined( kDNSServiceInterfaceIndexBLE ) )
  		case kDNSServiceInterfaceIndexBLE:
  			strlcpy( inNameBuf, "BLE", kInterfaceNameBufLen );
  			break;
  	#endif
  		
  		default:
  		{
  			const char *		name;
  			
  			name = if_indextoname( inIfIndex, inNameBuf );
  			if( !name ) strlcpy( inNameBuf, "NO NAME", kInterfaceNameBufLen );
  			break;
  		}
  	}
  	
  	return( inNameBuf );
  }
  
  //===========================================================================================================================
  //	RecordTypeToString
  //===========================================================================================================================
  
  static const char *	RecordTypeToString( unsigned int inValue )
  {
  	const RecordType *				type;
  	const RecordType * const		end = kRecordTypes + countof( kRecordTypes );
  	
  	for( type = kRecordTypes; type < end; ++type )
  	{
  		if( type->value == inValue ) return( type->name );
  	}
  	return( "???" );
  }
  
  //===========================================================================================================================
  //	DNSMessageExtractDomainName
  //===========================================================================================================================
  
  #define IsCompressionByte( X )		( ( ( X ) & 0xC0 ) == 0xC0 )
  
  static OSStatus
  	DNSMessageExtractDomainName(
  		const uint8_t *		inMsgPtr,
  		size_t				inMsgLen,
  		const uint8_t *		inNamePtr,
  		uint8_t				inBuf[ kDomainNameLengthMax ],
  		const uint8_t **	outNextPtr )
  {
  	OSStatus					err;
  	const uint8_t *				label;
  	uint8_t						labelLen;
  	const uint8_t *				nextLabel;
  	const uint8_t * const		msgEnd	= inMsgPtr + inMsgLen;
  	uint8_t *					dst		= inBuf;
  	const uint8_t * const		dstLim	= inBuf ? ( inBuf + kDomainNameLengthMax ) : NULL;
  	const uint8_t *				nameEnd	= NULL;
  	
  	require_action( ( inNamePtr >= inMsgPtr ) && ( inNamePtr < msgEnd ), exit, err = kRangeErr );
  	
  	for( label = inNamePtr; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
  	{
  		if( labelLen <= kDomainLabelLengthMax )
  		{
  			nextLabel = label + 1 + labelLen;
  			require_action( nextLabel < msgEnd, exit, err = kUnderrunErr );
  			if( dst )
  			{
  				require_action( ( dstLim - dst ) > ( 1 + labelLen ), exit, err = kOverrunErr );
  				memcpy( dst, label, 1 + labelLen );
  				dst += ( 1 + labelLen );
  			}
  		}
  		else if( IsCompressionByte( labelLen ) )
  		{
  			uint16_t		offset;
  			
  			require_action( ( msgEnd - label ) >= 2, exit, err = kUnderrunErr );
  			if( !nameEnd )
  			{
  				nameEnd = label + 2;
  				if( !dst ) break;
  			}
  			offset = (uint16_t)( ( ( label[ 0 ] & 0x3F ) << 8 ) | label[ 1 ] );
  			nextLabel = inMsgPtr + offset;
  			require_action( nextLabel < msgEnd, exit, err = kUnderrunErr );
  			require_action( !IsCompressionByte( nextLabel[ 0 ] ), exit, err = kMalformedErr );
  		}
  		else
  		{
  			dlogassert( "Unhandled label length 0x%02X\n", labelLen );
  			err = kMalformedErr;
  			goto exit;
  		}
  	}
  	
  	if( dst ) *dst = 0;
  	if( !nameEnd ) nameEnd = label + 1;
  	
  	if( outNextPtr ) *outNextPtr = nameEnd;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	DNSMessageExtractDomainNameString
  //===========================================================================================================================
  
  static OSStatus
  	DNSMessageExtractDomainNameString(
  		const void *		inMsgPtr,
  		size_t				inMsgLen,
  		const void *		inNamePtr,
  		char				inBuf[ kDNSServiceMaxDomainName ],
  		const uint8_t **	outNextPtr )
  {
  	OSStatus			err;
  	const uint8_t *		nextPtr;
  	uint8_t				domainName[ kDomainNameLengthMax ];
  	
  	err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inNamePtr, domainName, &nextPtr );
  	require_noerr( err, exit );
  	
  	err = DomainNameToString( domainName, NULL, inBuf, NULL );
  	require_noerr( err, exit );
  	
  	if( outNextPtr ) *outNextPtr = nextPtr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	DNSMessageExtractRecord
  //===========================================================================================================================
  
  typedef struct
  {
  	uint8_t		type[ 2 ];
  	uint8_t		class[ 2 ];
  	uint8_t		ttl[ 4 ];
  	uint8_t		rdLength[ 2 ];
  	uint8_t		rdata[ 1 ];
  	
  }	DNSRecordFields;
  
  check_compile_time( offsetof( DNSRecordFields, rdata ) == 10 );
  
  static OSStatus
  	DNSMessageExtractRecord(
  		const uint8_t *		inMsgPtr,
  		size_t				inMsgLen,
  		const uint8_t *		inPtr,
  		uint8_t				inNameBuf[ kDomainNameLengthMax ],
  		uint16_t *			outType,
  		uint16_t *			outClass,
  		uint32_t *			outTTL,
  		const uint8_t **	outRDataPtr,
  		size_t *			outRDataLen,
  		const uint8_t **	outPtr )
  {
  	OSStatus					err;
  	const uint8_t * const		msgEnd = inMsgPtr + inMsgLen;
  	const uint8_t *				ptr;
  	const DNSRecordFields *		record;
  	size_t						rdLength;
  	
  	err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inPtr, inNameBuf, &ptr );
  	require_noerr_quiet( err, exit );
  	require_action_quiet( (size_t)( msgEnd - ptr ) >= offsetof( DNSRecordFields, rdata ), exit, err = kUnderrunErr );
  	
  	record = (DNSRecordFields *) ptr;
  	rdLength = ReadBig16( record->rdLength );
  	require_action_quiet( (size_t)( msgEnd - record->rdata ) >= rdLength , exit, err = kUnderrunErr );
  	
  	if( outType )		*outType		= ReadBig16( record->type );
  	if( outClass )		*outClass		= ReadBig16( record->class );
  	if( outTTL )		*outTTL			= ReadBig32( record->ttl );
  	if( outRDataPtr )	*outRDataPtr	= record->rdata;
  	if( outRDataLen )	*outRDataLen	= rdLength;
  	if( outPtr )		*outPtr			= record->rdata + rdLength;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	DNSMessageGetAnswerSection
  //===========================================================================================================================
  
  static OSStatus	DNSMessageGetAnswerSection( const uint8_t *inMsgPtr, size_t inMsgLen, const uint8_t **outPtr )
  {
  	OSStatus					err;
  	const uint8_t * const		msgEnd	= inMsgPtr + inMsgLen;
  	unsigned int				questionCount, i;
  	const DNSHeader *			hdr;
  	const uint8_t *				ptr;
  	
  	require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
  	
  	hdr = (DNSHeader *) inMsgPtr;
  	questionCount = DNSHeaderGetQuestionCount( hdr );
  	
  	ptr = (uint8_t *)( hdr + 1 );
  	for( i = 0; i < questionCount; ++i )
  	{
  		err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, ptr, NULL, &ptr );
  		require_noerr( err, exit );
  		require_action_quiet( ( msgEnd - ptr ) >= 4, exit, err = kUnderrunErr );
  		ptr += 4;
  	}
  	
  	if( outPtr ) *outPtr = ptr;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	DNSRecordDataToString
  //===========================================================================================================================
  
  static OSStatus
  	DNSRecordDataToString(
  		const void *	inRDataPtr,
  		size_t			inRDataLen,
  		unsigned int	inRDataType,
  		const void *	inMsgPtr,
  		size_t			inMsgLen,
  		char **			outString )
  {
  	OSStatus					err;
  	const uint8_t * const		rdataPtr = (uint8_t *) inRDataPtr;
  	const uint8_t * const		rdataEnd = rdataPtr + inRDataLen;
  	char *						rdataStr;
  	const uint8_t *				ptr;
  	int							n;
  	char						domainNameStr[ kDNSServiceMaxDomainName ];
  	
  	rdataStr = NULL;
  	if( inRDataType == kDNSServiceType_A )
  	{
  		require_action_quiet( inRDataLen == 4, exit, err = kMalformedErr );
  		
  		ASPrintF( &rdataStr, "%.4a", rdataPtr );
  		require_action( rdataStr, exit, err = kNoMemoryErr );
  	}
  	else if( inRDataType == kDNSServiceType_AAAA )
  	{
  		require_action_quiet( inRDataLen == 16, exit, err = kMalformedErr );
  		
  		ASPrintF( &rdataStr, "%.16a", rdataPtr );
  		require_action( rdataStr, exit, err = kNoMemoryErr );
  	}
  	else if( ( inRDataType == kDNSServiceType_PTR ) || ( inRDataType == kDNSServiceType_CNAME ) ||
  			( inRDataType == kDNSServiceType_NS ) )
  	{
  		if( inMsgPtr )
  		{
  			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, NULL );
  			require_noerr( err, exit );
  		}
  		else
  		{
  			err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, NULL );
  			require_noerr( err, exit );
  		}
  		
  		rdataStr = strdup( domainNameStr );
  		require_action( rdataStr, exit, err = kNoMemoryErr );
  	}
  	else if( inRDataType == kDNSServiceType_SRV )
  	{
  		uint16_t			priority, weight, port;
  		const uint8_t *		target;
  		
  		require_action_quiet( ( rdataPtr + 6 ) < rdataEnd, exit, err = kMalformedErr );
  		
  		priority	= ReadBig16( rdataPtr );
  		weight		= ReadBig16( rdataPtr + 2 );
  		port		= ReadBig16( rdataPtr + 4 );
  		target		= rdataPtr + 6;
  		
  		if( inMsgPtr )
  		{
  			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, target, domainNameStr, NULL );
  			require_noerr( err, exit );
  		}
  		else
  		{
  			err = DomainNameToString( target, rdataEnd, domainNameStr, NULL );
  			require_noerr( err, exit );
  		}
  		
  		ASPrintF( &rdataStr, "%u %u %u %s", priority, weight, port, domainNameStr );
  		require_action( rdataStr, exit, err = kNoMemoryErr );
  	}
  	else if( inRDataType == kDNSServiceType_TXT )
  	{
  		require_action_quiet( inRDataLen > 0, exit, err = kMalformedErr );
  		
  		if( inRDataLen == 1 )
  		{
  			ASPrintF( &rdataStr, "%#H", rdataPtr, (int) inRDataLen, INT_MAX );
  			require_action( rdataStr, exit, err = kNoMemoryErr );
  		}
  		else
  		{
  			ASPrintF( &rdataStr, "%#{txt}", rdataPtr, inRDataLen );
  			require_action( rdataStr, exit, err = kNoMemoryErr );
  		}
  	}
  	else if( inRDataType == kDNSServiceType_SOA )
  	{
  		uint32_t		serial, refresh, retry, expire, minimum;
  		
  		if( inMsgPtr )
  		{
  			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
  			require_noerr( err, exit );
  			
  			require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
  			
  			rdataStr = strdup( domainNameStr );
  			require_action( rdataStr, exit, err = kNoMemoryErr );
  			
  			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, domainNameStr, &ptr );
  			require_noerr( err, exit );
  		}
  		else
  		{
  			err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
  			require_noerr( err, exit );
  			
  			rdataStr = strdup( domainNameStr );
  			require_action( rdataStr, exit, err = kNoMemoryErr );
  			
  			err = DomainNameToString( ptr, rdataEnd, domainNameStr, &ptr );
  			require_noerr( err, exit );
  		}
  		
  		require_action_quiet( ( ptr + 20 ) == rdataEnd, exit, err = kMalformedErr );
  		
  		serial	= ReadBig32( ptr );
  		refresh	= ReadBig32( ptr +  4 );
  		retry	= ReadBig32( ptr +  8 );
  		expire	= ReadBig32( ptr + 12 );
  		minimum	= ReadBig32( ptr + 16 );
  		
  		n = AppendPrintF( &rdataStr, " %s %u %u %u %u %u\n", domainNameStr, serial, refresh, retry, expire, minimum );
  		require_action( n > 0, exit, err = kUnknownErr );
  	}
  	else if( inRDataType == kDNSServiceType_NSEC )
  	{
  		unsigned int		windowBlock, bitmapLen, i, recordType;
  		const uint8_t *		bitmapPtr;
  		
  		if( inMsgPtr )
  		{
  			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
  			require_noerr( err, exit );
  		}
  		else
  		{
  			err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
  			require_noerr( err, exit );
  		}
  		
  		require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
  		
  		rdataStr = strdup( domainNameStr );
  		require_action( rdataStr, exit, err = kNoMemoryErr );
  		
  		for( ; ptr < rdataEnd; ptr += ( 2 + bitmapLen ) )
  		{
  			require_action_quiet( ( ptr + 2 ) < rdataEnd, exit, err = kMalformedErr );
  			
  			windowBlock	= ptr[ 0 ];
  			bitmapLen	= ptr[ 1 ];
  			bitmapPtr	= &ptr[ 2 ];
  			
  			require_action_quiet( ( bitmapLen >= 1 ) && ( bitmapLen <= 32 ) , exit, err = kMalformedErr );
  			require_action_quiet( ( bitmapPtr + bitmapLen ) <= rdataEnd, exit, err = kMalformedErr );
  			
  			for( i = 0; i < BitArray_MaxBits( bitmapLen ); ++i )
  			{
  				if( BitArray_GetBit( bitmapPtr, bitmapLen, i ) )
  				{
  					recordType = ( windowBlock * 256 ) + i;
  					n = AppendPrintF( &rdataStr, " %s", RecordTypeToString( recordType ) );
  					require_action( n > 0, exit, err = kUnknownErr );
  				}
  			}
  		}
  	}
  	else if( inRDataType == kDNSServiceType_MX )
  	{
  		uint16_t			preference;
  		const uint8_t *		exchange;
  		
  		require_action_quiet( ( rdataPtr + 2 ) < rdataEnd, exit, err = kMalformedErr );
  		
  		preference	= ReadBig16( rdataPtr );
  		exchange	= &rdataPtr[ 2 ];
  		
  		if( inMsgPtr )
  		{
  			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, exchange, domainNameStr, NULL );
  			require_noerr( err, exit );
  		}
  		else
  		{
  			err = DomainNameToString( exchange, rdataEnd, domainNameStr, NULL );
  			require_noerr( err, exit );
  		}
  		
  		n = ASPrintF( &rdataStr, "%u %s", preference, domainNameStr );
  		require_action( n > 0, exit, err = kUnknownErr );
  	}
  	else
  	{
  		err = kNotHandledErr;
  		goto exit;
  	}
  	
  	check( rdataStr );
  	*outString = rdataStr;
  	rdataStr = NULL;
  	err = kNoErr;
  	
  exit:
  	FreeNullSafe( rdataStr );
  	return( err );
  }
  
  //===========================================================================================================================
  //	DomainNameAppendString
  //===========================================================================================================================
  
  static OSStatus
  	DomainNameAppendString(
  		uint8_t			inDomainName[ kDomainNameLengthMax ],
  		const char *	inString,
  		uint8_t **		outEndPtr )
  {
  	OSStatus					err;
  	const char *				src;
  	uint8_t *					dst;
  	const uint8_t * const		nameLimit = inDomainName + kDomainNameLengthMax;
  	
  	// Find the root label.
  	
  	for( dst = inDomainName; ( dst < nameLimit ) && *dst; dst += ( 1 + *dst ) ) {}
  	require_action_quiet( dst < nameLimit, exit, err = kMalformedErr );
  	
  	// Append the string's labels one label at a time.
  	
  	src = inString;
  	while( *src )
  	{
  		uint8_t * const				label		= dst++;
  		const uint8_t * const		labelLimit	= Min( dst + kDomainLabelLengthMax, nameLimit - 1 );
  		
  		// If the first character is a label separator, then the label is empty. Empty non-root labels are not allowed.
  		
  		require_action_quiet( *src != '.', exit, err = kMalformedErr );
  		
  		// Write the label characters until the end of the label, a separator or NUL character, is encountered, or until no
  		// more space is available.
  		
  		while( ( *src != '.' ) && ( *src != '\0' ) && ( dst < labelLimit ) )
  		{
  			uint8_t		value;
  			
  			value = (uint8_t) *src++;
  			if( value == '\\' )
  			{
  				if( *src == '\0' ) break;
  				value = (uint8_t) *src++;
  				if( isdigit_safe( value ) && isdigit_safe( src[ 0 ] ) && isdigit_safe( src[ 1 ] ) )
  				{
  					int		decimalValue;
  					
  					decimalValue = ( ( value - '0' ) * 100 ) + ( ( src[ 0 ] - '0' ) * 10 ) + ( src[ 1 ] - '0' );
  					if( decimalValue <= 255 )
  					{
  						value = (uint8_t) decimalValue;
  						src += 2;
  					}
  				}
  			}
  			*dst++ = value;
  		}
  		if( ( *src == '.' ) || ( *src == '\0' ) )
  		{
  			label[ 0 ] = (uint8_t)( dst - &label[ 1 ] );	// Write the label length.
  			if( *src == '.' ) ++src;						// Advance the pointer past the label separator.
  		}
  		else
  		{
  			label[ 0 ] = 0;
  			err = kOverrunErr;
  			goto exit;
  		}
  	}
  	
  	*dst++ = 0;	// Write the empty root label.
  	if( outEndPtr ) *outEndPtr = dst;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	DomainNameEqual
  //===========================================================================================================================
  
  static Boolean	DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 )
  {
  	const uint8_t *		p1 = inName1;
  	const uint8_t *		p2 = inName2;
  	unsigned int		len;
  	
  	for( ;; )
  	{
  		if( ( len = *p1++ ) != *p2++ ) return( false );
  		if( len == 0 ) break;
  		for( ; len > 0; ++p1, ++p2, --len )
  		{
  			if( tolower_safe( *p1 ) != tolower_safe( *p2 ) ) return( false );
  		}
  	}
  	return( true );
  }
  
  //===========================================================================================================================
  //	DomainNameToString
  //===========================================================================================================================
  
  static OSStatus
  	DomainNameToString(
  		const uint8_t *		inDomainName,
  		const uint8_t *		inEnd,
  		char				inBuf[ kDNSServiceMaxDomainName ],
  		const uint8_t **	outNextPtr )
  {
  	OSStatus			err;
  	const uint8_t *		label;
  	uint8_t				labelLen;
  	const uint8_t *		nextLabel;
  	char *				dst;
  	const uint8_t *		src;
  	
  	require_action( !inEnd || ( inDomainName < inEnd ), exit, err = kUnderrunErr );
  	
  	// Convert each label up until the root label, i.e., the zero-length label.
  	
  	dst = inBuf;
  	for( label = inDomainName; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
  	{
  		require_action( labelLen <= kDomainLabelLengthMax, exit, err = kMalformedErr );
  		
  		nextLabel = &label[ 1 ] + labelLen;
  		require_action( ( nextLabel - inDomainName ) < kDomainNameLengthMax, exit, err = kMalformedErr );
  		require_action( !inEnd || ( nextLabel < inEnd ), exit, err = kUnderrunErr );
  		
  		for( src = &label[ 1 ]; src < nextLabel; ++src )
  		{
  			if( isprint_safe( *src ) )
  			{
  				if( ( *src == '.' ) || ( *src == '\\' ) ||  ( *src == ' ' ) ) *dst++ = '\\';
  				*dst++ = (char) *src;
  			}
  			else
  			{
  				*dst++ = '\\';
  				*dst++ = '0' + (   *src / 100 );
  				*dst++ = '0' + ( ( *src /  10 ) % 10 );
  				*dst++ = '0' + (   *src         % 10 );
  			}
  		}
  		*dst++ = '.';
  	}
  	
  	// At this point, label points to the root label.
  	// If the root label was the only label, then write a dot for it.
  	
  	if( label == inDomainName ) *dst++ = '.';
  	*dst = '\0';
  	if( outNextPtr ) *outNextPtr = label + 1;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	PrintDNSMessage
  //===========================================================================================================================
  
  #define DNSFlagsOpCodeToString( X ) (					\
  	( (X) == kDNSOpCode_Query )			? "Query"	:	\
  	( (X) == kDNSOpCode_InverseQuery )	? "IQuery"	:	\
  	( (X) == kDNSOpCode_Status )		? "Status"	:	\
  	( (X) == kDNSOpCode_Notify )		? "Notify"	:	\
  	( (X) == kDNSOpCode_Update )		? "Update"	:	\
  										  "Unassigned" )
  
  #define DNSFlagsRCodeToString( X ) (						\
  	( (X) == kDNSRCode_NoError )		? "NoError"		:	\
  	( (X) == kDNSRCode_FormatError )	? "FormErr"		:	\
  	( (X) == kDNSRCode_ServerFailure )	? "ServFail"	:	\
  	( (X) == kDNSRCode_NXDomain )		? "NXDomain"	:	\
  	( (X) == kDNSRCode_NotImplemented )	? "NotImp"		:	\
  	( (X) == kDNSRCode_Refused )		? "Refused"		:	\
  										  "???" )
  
  #define DNSFlagsGetOpCode( X )		( ( (X) >> 11 ) & 0x0F )
  #define DNSFlagsGetRCode( X )		(   (X)         & 0x0F )
  
  static OSStatus	PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, const Boolean inIsMDNS, const Boolean inPrintRaw )
  {
  	OSStatus					err;
  	const DNSHeader *			hdr;
  	const uint8_t * const		msgEnd = inMsgPtr + inMsgLen;
  	const uint8_t *				ptr;
  	unsigned int				id, flags, opcode, rcode;
  	unsigned int				questionCount, answerCount, authorityCount, additionalCount, i, totalRRCount;
  	char						nameStr[ kDNSServiceMaxDomainName ];
  	
  	require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
  	
  	hdr				= (DNSHeader *) inMsgPtr;
  	id				= DNSHeaderGetID( hdr );
  	flags			= DNSHeaderGetFlags( hdr );
  	questionCount	= DNSHeaderGetQuestionCount( hdr );
  	answerCount		= DNSHeaderGetAnswerCount( hdr );
  	authorityCount	= DNSHeaderGetAuthorityCount( hdr );
  	additionalCount	= DNSHeaderGetAdditionalCount( hdr );
  	opcode			= DNSFlagsGetOpCode( flags );
  	rcode			= DNSFlagsGetRCode( flags );
  	
  	FPrintF( stdout, "ID:               0x%04X (%u)\n", id, id );
  	FPrintF( stdout, "Flags:            0x%04X %c/%s %cAA%cTC%cRD%cRA %s\n",
  		flags,
  		( flags & kDNSHeaderFlag_Response )				? 'R' : 'Q', DNSFlagsOpCodeToString( opcode ),
  		( flags & kDNSHeaderFlag_AuthAnswer )			? ' ' : '!',
  		( flags & kDNSHeaderFlag_Truncation )			? ' ' : '!',
  		( flags & kDNSHeaderFlag_RecursionDesired )		? ' ' : '!',
  		( flags & kDNSHeaderFlag_RecursionAvailable )	? ' ' : '!',
  		DNSFlagsRCodeToString( rcode ) );
  	FPrintF( stdout, "Question count:   %u\n", questionCount );
  	FPrintF( stdout, "Answer count:     %u\n", answerCount );
  	FPrintF( stdout, "Authority count:  %u\n", authorityCount );
  	FPrintF( stdout, "Additional count: %u\n", additionalCount );
  	
  	ptr = (uint8_t *)( hdr + 1 );
  	for( i = 0; i < questionCount; ++i )
  	{
  		unsigned int		qType, qClass;
  		Boolean				isQU;
  		
  		err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, nameStr, &ptr );
  		require_noerr( err, exit );
  		
  		if( ( msgEnd - ptr ) < 4 )
  		{
  			err = kUnderrunErr;
  			goto exit;
  		}
  		
  		qType = ReadBig16( ptr );
  		ptr += 2;
  		qClass = ReadBig16( ptr );
  		ptr += 2;
  		
  		isQU = ( inIsMDNS && ( qClass & kQClassUnicastResponseBit ) ) ? true : false;
  		if( inIsMDNS ) qClass &= ~kQClassUnicastResponseBit;
  		
  		if( i == 0 ) FPrintF( stdout, "\nQUESTION SECTION\n" );
  		
  		FPrintF( stdout, "%s %2s %?2s%?2u %-5s\n",
  			nameStr, inIsMDNS ? ( isQU ? "QU" : "QM" ) : "",
  			( qClass == kDNSServiceClass_IN ), "IN", ( qClass != kDNSServiceClass_IN ), qClass,
  			RecordTypeToString( qType ) );
  	}
  	
  	totalRRCount = answerCount + authorityCount + additionalCount;
  	for( i = 0; i < totalRRCount; ++i )
  	{
  		uint16_t			type;
  		uint16_t			class;
  		uint32_t			ttl;
  		const uint8_t *		rdataPtr;
  		size_t				rdataLen;
  		char *				rdataStr;
  		Boolean				cacheFlush;
  		uint8_t				name[ kDomainNameLengthMax ];
  		
  		err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, &ttl, &rdataPtr, &rdataLen, &ptr );
  		require_noerr( err, exit );
  		
  		err = DomainNameToString( name, NULL, nameStr, NULL );
  		require_noerr( err, exit );
  		
  		cacheFlush = ( inIsMDNS && ( class & kRRClassCacheFlushBit ) ) ? true : false;
  		if( inIsMDNS ) class &= ~kRRClassCacheFlushBit;
  		
  		rdataStr = NULL;
  		if( !inPrintRaw ) DNSRecordDataToString( rdataPtr, rdataLen, type, inMsgPtr, inMsgLen, &rdataStr );
  		if( !rdataStr )
  		{
  			ASPrintF( &rdataStr, "%#H", rdataPtr, (int) rdataLen, INT_MAX );
  			require_action( rdataStr, exit, err = kNoMemoryErr );
  		}
  		
  		if(      answerCount     && ( i ==   0                              ) ) FPrintF( stdout, "\nANSWER SECTION\n" );
  		else if( authorityCount  && ( i ==   answerCount                    ) ) FPrintF( stdout, "\nAUTHORITY SECTION\n" );
  		else if( additionalCount && ( i == ( answerCount + authorityCount ) ) ) FPrintF( stdout, "\nADDITIONAL SECTION\n" );
  		
  		FPrintF( stdout, "%-42s %6u %2s %?2s%?2u %-5s %s\n",
  			nameStr, ttl, cacheFlush ? "CF" : "",
  			( class == kDNSServiceClass_IN ), "IN", ( class != kDNSServiceClass_IN ), class,
  			RecordTypeToString( type ), rdataStr );
  		free( rdataStr );
  	}
  	FPrintF( stdout, "\n" );
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	WriteDNSQueryMessage
  //===========================================================================================================================
  
  static OSStatus
  	WriteDNSQueryMessage(
  		uint8_t			inMsg[ kDNSQueryMessageMaxLen ],
  		uint16_t		inMsgID,
  		uint16_t		inFlags,
  		const char *	inQName,
  		uint16_t		inQType,
  		uint16_t		inQClass,
  		size_t *		outMsgLen )
  {
  	OSStatus				err;
  	DNSHeader * const		hdr = (DNSHeader *) inMsg;
  	uint8_t *				ptr;
  	size_t					msgLen;
  	
  	WriteBig16( hdr->id,				inMsgID );
  	WriteBig16( hdr->flags,				inFlags );
  	WriteBig16( hdr->questionCount,		1 );
  	WriteBig16( hdr->answerCount,		0 );
  	WriteBig16( hdr->authorityCount,	0 );
  	WriteBig16( hdr->additionalCount,	0 );
  	
  	ptr = (uint8_t *)( hdr + 1 );
  	ptr[ 0 ] = 0;
  	err = DomainNameAppendString( ptr, inQName, &ptr );
  	require_noerr_quiet( err, exit );
  	
  	WriteBig16( ptr, inQType );
  	ptr += 2;
  	WriteBig16( ptr, inQClass );
  	ptr += 2;
  	msgLen = (size_t)( ptr - inMsg );
  	check( msgLen <= kDNSQueryMessageMaxLen );
  	
  	if( outMsgLen ) *outMsgLen = msgLen;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	DispatchSignalSourceCreate
  //===========================================================================================================================
  
  static OSStatus
  	DispatchSignalSourceCreate(
  		int					inSignal,
  		DispatchHandler		inEventHandler,
  		void *				inContext,
  		dispatch_source_t *	outSource )
  {
  	OSStatus				err;
  	dispatch_source_t		source;
  	
  	source = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) inSignal, 0, dispatch_get_main_queue() );
  	require_action( source, exit, err = kUnknownErr );
  	
  	dispatch_set_context( source, inContext );
  	dispatch_source_set_event_handler_f( source, inEventHandler );
  	
  	*outSource = source;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	DispatchReadSourceCreate
  //===========================================================================================================================
  
  static OSStatus
  	DispatchReadSourceCreate(
  		SocketRef			inSock,
  		DispatchHandler		inEventHandler,
  		DispatchHandler		inCancelHandler,
  		void *				inContext,
  		dispatch_source_t *	outSource )
  {
  	OSStatus				err;
  	dispatch_source_t		source;
  	
  	source = dispatch_source_create( DISPATCH_SOURCE_TYPE_READ, (uintptr_t) inSock, 0, dispatch_get_main_queue() );
  	require_action( source, exit, err = kUnknownErr );
  	
  	dispatch_set_context( source, inContext );
  	dispatch_source_set_event_handler_f( source, inEventHandler );
  	dispatch_source_set_cancel_handler_f( source, inCancelHandler );
  	
  	*outSource = source;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	DispatchTimerCreate
  //===========================================================================================================================
  
  static OSStatus
  	DispatchTimerCreate(
  		dispatch_time_t		inStart,
  		uint64_t			inIntervalNs,
  		uint64_t			inLeewayNs,
  		DispatchHandler		inEventHandler,
  		DispatchHandler		inCancelHandler,
  		void *				inContext,
  		dispatch_source_t *	outTimer )
  {
  	OSStatus				err;
  	dispatch_source_t		timer;
  	
  	timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue() );
  	require_action( timer, exit, err = kUnknownErr );
  	
  	dispatch_source_set_timer( timer, inStart, inIntervalNs, inLeewayNs );
  	dispatch_set_context( timer, inContext );
  	dispatch_source_set_event_handler_f( timer, inEventHandler );
  	dispatch_source_set_cancel_handler_f( timer, inCancelHandler );
  	
  	*outTimer = timer;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	ServiceTypeDescription
  //===========================================================================================================================
  
  typedef struct
  {
  	const char *		name;			// Name of the service type in two-label "_service._proto" format.
  	const char *		description;	// Description of the service type.
  	
  }	ServiceType;
  
  // A Non-comprehensive table of DNS-SD service types
  
  static const ServiceType		kServiceTypes[] =
  {
  	{ "_acp-sync._tcp",			"AirPort Base Station Sync" },
  	{ "_adisk._tcp",			"Automatic Disk Discovery" },
  	{ "_afpovertcp._tcp",		"Apple File Sharing" },
  	{ "_airdrop._tcp",			"AirDrop" },
  	{ "_airplay._tcp",			"AirPlay" },
  	{ "_airport._tcp",			"AirPort Base Station" },
  	{ "_daap._tcp",				"Digital Audio Access Protocol (iTunes)" },
  	{ "_eppc._tcp",				"Remote AppleEvents" },
  	{ "_ftp._tcp",				"File Transfer Protocol" },
  	{ "_home-sharing._tcp",		"Home Sharing" },
  	{ "_homekit._tcp",			"HomeKit" },
  	{ "_http._tcp",				"World Wide Web HTML-over-HTTP" },
  	{ "_https._tcp",			"HTTP over SSL/TLS" },
  	{ "_ipp._tcp",				"Internet Printing Protocol" },
  	{ "_ldap._tcp",				"Lightweight Directory Access Protocol" },
  	{ "_mediaremotetv._tcp",	"Media Remote" },
  	{ "_net-assistant._tcp",	"Apple Remote Desktop" },
  	{ "_od-master._tcp",		"OpenDirectory Master" },
  	{ "_nfs._tcp",				"Network File System" },
  	{ "_presence._tcp",			"Peer-to-peer messaging / Link-Local Messaging" },
  	{ "_pdl-datastream._tcp",	"Printer Page Description Language Data Stream" },
  	{ "_raop._tcp",				"Remote Audio Output Protocol" },
  	{ "_rfb._tcp",				"Remote Frame Buffer" },
  	{ "_scanner._tcp",			"Bonjour Scanning" },
  	{ "_smb._tcp",				"Server Message Block over TCP/IP" },
  	{ "_sftp-ssh._tcp",			"Secure File Transfer Protocol over SSH" },
  	{ "_sleep-proxy._udp",		"Sleep Proxy Server" },
  	{ "_ssh._tcp",				"SSH Remote Login Protocol" },
  	{ "_teleport._tcp",			"teleport" },
  	{ "_tftp._tcp",				"Trivial File Transfer Protocol" },
  	{ "_workstation._tcp",		"Workgroup Manager" },
  	{ "_webdav._tcp",			"World Wide Web Distributed Authoring and Versioning (WebDAV)" },
  	{ "_webdavs._tcp",			"WebDAV over SSL/TLS" }
  };
  
  static const char *	ServiceTypeDescription( const char *inName )
  {
  	const ServiceType *				serviceType;
  	const ServiceType * const		end = kServiceTypes + countof( kServiceTypes );
  	
  	for( serviceType = kServiceTypes; serviceType < end; ++serviceType )
  	{
  		if( strcasecmp( inName, serviceType->name ) == 0 ) return( serviceType->description );
  	}
  	return( NULL );
  }
  
  //===========================================================================================================================
  //	SocketContextCancelHandler
  //===========================================================================================================================
  
  static void	SocketContextCancelHandler( void *inContext )
  {
  	SocketContext * const		context = (SocketContext *) inContext;
  	
  	ForgetSocket( &context->sock );
  	free( context );
  }
  
  //===========================================================================================================================
  //	StringToInt32
  //===========================================================================================================================
  
  static OSStatus	StringToInt32( const char *inString, int32_t *outValue )
  {
  	OSStatus		err;
  	long			value;
  	char *			endPtr;
  	
  	value = strtol( inString, &endPtr, 0 );
  	require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
  	require_action_quiet( ( value >= INT32_MIN ) && ( value <= INT32_MAX ), exit, err = kRangeErr );
  	
  	*outValue = (int32_t) value;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	StringToUInt32
  //===========================================================================================================================
  
  static OSStatus	StringToUInt32( const char *inString, uint32_t *outValue )
  {
  	OSStatus		err;
  	uint32_t		value;
  	char *			endPtr;
  	
  	value = (uint32_t) strtol( inString, &endPtr, 0 );
  	require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
  	
  	*outValue = value;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  #if( TARGET_OS_DARWIN )
  //===========================================================================================================================
  //	GetDefaultDNSServer
  //===========================================================================================================================
  
  static OSStatus	GetDefaultDNSServer( sockaddr_ip *outAddr )
  {
  	OSStatus				err;
  	dns_config_t *			config;
  	struct sockaddr *		addr;
  	int32_t					i;
  	
  	config = dns_configuration_copy();
  	require_action( config, exit, err = kUnknownErr );
  	
  	addr = NULL;
  	for( i = 0; i < config->n_resolver; ++i )
  	{
  		const dns_resolver_t * const		resolver = config->resolver[ i ];
  		
  		if( !resolver->domain && ( resolver->n_nameserver > 0 ) )
  		{
  			addr = resolver->nameserver[ 0 ];
  			break;
  		}
  	}
  	require_action_quiet( addr, exit, err = kNotFoundErr );
  	
  	SockAddrCopy( addr, outAddr );
  	err = kNoErr;
  	
  exit:
  	if( config ) dns_configuration_free( config );
  	return( err );
  }
  #endif
  
  //===========================================================================================================================
  //	SocketWriteAll
  //
  //	Note: This was copied from CoreUtils because the SocketWriteAll function is currently not exported in the framework.
  //===========================================================================================================================
  
  OSStatus	SocketWriteAll( SocketRef inSock, const void *inData, size_t inSize, int32_t inTimeoutSecs )
  {
  	OSStatus			err;
  	const uint8_t *		src;
  	const uint8_t *		end;
  	fd_set				writeSet;
  	struct timeval		timeout;
  	ssize_t				n;
  	
  	FD_ZERO( &writeSet );
  	src = (const uint8_t *) inData;
  	end = src + inSize;
  	while( src < end )
  	{
  		FD_SET( inSock, &writeSet );
  		timeout.tv_sec 	= inTimeoutSecs;
  		timeout.tv_usec = 0;
  		n = select( (int)( inSock + 1 ), NULL, &writeSet, NULL, &timeout );
  		if( n == 0 ) { err = kTimeoutErr; goto exit; }
  		err = map_socket_value_errno( inSock, n > 0, n );
  		require_noerr( err, exit );
  		
  		n = send( inSock, (char *) src, (size_t)( end - src ), 0 );
  		err = map_socket_value_errno( inSock, n >= 0, n );
  		if( err == EINTR ) continue;
  		require_noerr( err, exit );
  		
  		src += n;
  	}
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	ParseEscapedString
  //
  //	Note: This was copied from CoreUtils because the ParseEscapedString function is currently not exported in the framework.
  //===========================================================================================================================
  
  OSStatus
  	ParseEscapedString( 
  		const char *	inSrc, 
  		const char *	inEnd, 
  		char			inDelimiter, 
  		char *			inBuf, 
  		size_t			inMaxLen, 
  		size_t *		outCopiedLen, 
  		size_t *		outTotalLen, 
  		const char **	outSrc )
  {
  	OSStatus		err;
  	char			c;
  	char *			dst;
  	char *			lim;
  	size_t			len;
  	
  	dst = inBuf;
  	lim = dst + ( ( inMaxLen > 0 ) ? ( inMaxLen - 1 ) : 0 ); // Leave room for null terminator.
  	len = 0;
  	while( ( inSrc < inEnd ) && ( ( c = *inSrc++ ) != inDelimiter ) )
  	{
  		if( c == '\\' )
  		{
  			require_action_quiet( inSrc < inEnd, exit, err = kUnderrunErr );
  			c = *inSrc++;
  		}
  		if( dst < lim )
  		{
  			if( inBuf ) *dst = c;
  			++dst;
  		}
  		++len;
  	}
  	if( inBuf && ( inMaxLen > 0 ) ) *dst = '\0';
  	err = kNoErr;
  	
  exit:
  	if( outCopiedLen )	*outCopiedLen	= (size_t)( dst - inBuf );
  	if( outTotalLen )	*outTotalLen	= len;
  	if( outSrc )		*outSrc			= inSrc;
  	return( err );
  }
  
  //===========================================================================================================================
  //	ParseIPv4Address
  //
  //	Warning: "inBuffer" may be modified even in error cases.
  //
  //	Note: This was copied from CoreUtils because the StringToIPv4Address function is currently not exported in the framework.
  //===========================================================================================================================
  
  static OSStatus	ParseIPv4Address( const char *inStr, uint8_t inBuffer[ 4 ], const char **outStr )
  {
  	OSStatus		err;
  	uint8_t *		dst;
  	int				segments;
  	int				sawDigit;
  	int				c;
  	int				v;
  	
  	check( inBuffer );
  	check( outStr );
  	
  	dst		 = inBuffer;
  	*dst	 = 0;
  	sawDigit = 0;
  	segments = 0;
  	for( ; ( c = *inStr ) != '\0'; ++inStr )
  	{
  		if( isdigit_safe( c ) )
  		{
  			v = ( *dst * 10 ) + ( c - '0' );
  			require_action_quiet( v <= 255, exit, err = kRangeErr );
  			*dst = (uint8_t) v;
  			if( !sawDigit )
  			{
  				++segments;
  				require_action_quiet( segments <= 4, exit, err = kOverrunErr );
  				sawDigit = 1;
  			}
  		}
  		else if( ( c == '.' ) && sawDigit )
  		{
  			require_action_quiet( segments < 4, exit, err = kMalformedErr );
  			*++dst = 0;
  			sawDigit = 0;
  		}
  		else
  		{
  			break;
  		}
  	}
  	require_action_quiet( segments == 4, exit, err = kUnderrunErr );
  	
  	*outStr = inStr;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	StringToIPv4Address
  //
  //	Note: This was copied from CoreUtils because the StringToIPv4Address function is currently not exported in the framework.
  //===========================================================================================================================
  
  OSStatus
  	StringToIPv4Address( 
  		const char *			inStr, 
  		StringToIPAddressFlags	inFlags, 
  		uint32_t *				outIP, 
  		int *					outPort, 
  		uint32_t *				outSubnet, 
  		uint32_t *				outRouter, 
  		const char **			outStr )
  {
  	OSStatus			err;
  	uint8_t				buf[ 4 ];
  	int					c;
  	uint32_t			ip;
  	int					hasPort;
  	int					port;
  	int					hasPrefix;
  	int					prefix;
  	uint32_t			subnetMask;
  	uint32_t			router;
  	
  	require_action( inStr, exit, err = kParamErr );
  	
  	// Parse the address-only part of the address (e.g. "1.2.3.4").
  	
  	err = ParseIPv4Address( inStr, buf, &inStr );
  	require_noerr_quiet( err, exit );
  	ip = (uint32_t)( ( buf[ 0 ] << 24 ) | ( buf[ 1 ] << 16 ) | ( buf[ 2 ] << 8 ) | buf[ 3 ] );
  	c = *inStr;
  	
  	// Parse the port (if any).
  	
  	hasPort = 0;
  	port    = 0;
  	if( c == ':' )
  	{
  		require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPort ), exit, err = kUnexpectedErr );
  		while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) port = ( port * 10 ) + ( c - '0' );
  		require_action_quiet( port <= 65535, exit, err = kRangeErr );
  		hasPort = 1;
  	}
  	
  	// Parse the prefix length (if any).
  	
  	hasPrefix  = 0;
  	prefix     = 0;
  	subnetMask = 0;
  	router     = 0;
  	if( c == '/' )
  	{
  		require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPrefix ), exit, err = kUnexpectedErr );
  		while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) prefix = ( prefix * 10 ) + ( c - '0' );
  		require_action_quiet( ( prefix >= 0 ) && ( prefix <= 32 ), exit, err = kRangeErr );
  		hasPrefix = 1;
  		
  		subnetMask = ( prefix > 0 ) ? ( UINT32_C( 0xFFFFFFFF ) << ( 32 - prefix ) ) : 0;
  		router	   = ( ip & subnetMask ) | 1;
  	}
  	
  	// Return the results. Only fill in port/prefix/router results if the info was found to allow for defaults.
  	
  	if( outIP )					 *outIP		= ip;
  	if( outPort   && hasPort )	 *outPort	= port;
  	if( outSubnet && hasPrefix ) *outSubnet	= subnetMask;
  	if( outRouter && hasPrefix ) *outRouter	= router;
  	if( outStr )				 *outStr	= inStr;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	ParseIPv6Address
  //
  //	Note: Parsed according to the rules specified in RFC 3513.
  //	Warning: "inBuffer" may be modified even in error cases.
  //
  //	Note: This was copied from CoreUtils because the StringToIPv6Address function is currently not exported in the framework.
  //===========================================================================================================================
  
  static OSStatus	ParseIPv6Address( const char *inStr, int inAllowV4Mapped, uint8_t inBuffer[ 16 ], const char **outStr )
  {
  													// Table to map uppercase hex characters - '0' to their numeric values.
  													// 0  1  2  3  4  5  6  7  8  9  :  ;  <  =  >  ?  @  A   B   C   D   E   F
  	static const uint8_t		kASCIItoHexTable[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15 };
  	OSStatus					err;
  	const char *				ptr;
  	uint8_t *					dst;
  	uint8_t *					lim;
  	uint8_t *					colonPtr;
  	int							c;
  	int							sawDigit;
  	unsigned int				v;
  	int							i;
  	int							n;
  	
  	// Pre-zero the address to simplify handling of compressed addresses (e.g. "::1").
  	
  	for( i = 0; i < 16; ++i ) inBuffer[ i ] = 0;
  	
  	// Special case leading :: (e.g. "::1") to simplify processing later.
  	
  	if( *inStr == ':' )
  	{
  		++inStr;
  		require_action_quiet( *inStr == ':', exit, err = kMalformedErr );
  	}
  	
  	// Parse the address.
  	
  	ptr		 = inStr;
  	dst		 = inBuffer;
  	lim		 = dst + 16;
  	colonPtr = NULL;
  	sawDigit = 0;
  	v		 = 0;
  	while( ( ( c = *inStr++ ) != '\0' ) && ( c != '%' ) && ( c != '/' ) && ( c != ']' ) )
  	{
  		if(   ( c >= 'a' ) && ( c <= 'f' ) ) c -= ( 'a' - 'A' );
  		if( ( ( c >= '0' ) && ( c <= '9' ) ) || ( ( c >= 'A' ) && ( c <= 'F' ) ) )
  		{
  			c -= '0';
  			check( c < (int) countof( kASCIItoHexTable ) );
  			v = ( v << 4 ) | kASCIItoHexTable[ c ];
  			require_action_quiet( v <= 0xFFFF, exit, err = kRangeErr );
  			sawDigit = 1;
  			continue;
  		}
  		if( c == ':' )
  		{
  			ptr = inStr;
  			if( !sawDigit )
  			{
  				require_action_quiet( !colonPtr, exit, err = kMalformedErr );
  				colonPtr = dst;
  				continue;
  			}
  			require_action_quiet( *inStr != '\0', exit, err = kUnderrunErr );
  			require_action_quiet( ( dst + 2 ) <= lim, exit, err = kOverrunErr );
  			*dst++ = (uint8_t)( ( v >> 8 ) & 0xFF );
  			*dst++ = (uint8_t)(   v        & 0xFF );
  			sawDigit = 0;
  			v = 0;
  			continue;
  		}
  		
  		// Handle IPv4-mapped/compatible addresses (e.g. ::FFFF:1.2.3.4).
  		
  		if( inAllowV4Mapped && ( c == '.' ) && ( ( dst + 4 ) <= lim ) )
  		{
  			err = ParseIPv4Address( ptr, dst, &inStr );
  			require_noerr_quiet( err, exit );
  			dst += 4;
  			sawDigit = 0;
  			++inStr; // Increment because the code below expects the end to be at "inStr - 1".
  		}
  		break;
  	}
  	if( sawDigit )
  	{
  		require_action_quiet( ( dst + 2 ) <= lim, exit, err = kOverrunErr );
  		*dst++ = (uint8_t)( ( v >> 8 ) & 0xFF );
  		*dst++ = (uint8_t)(   v        & 0xFF );
  	}
  	check( dst <= lim );
  	if( colonPtr )
  	{
  		require_action_quiet( dst < lim, exit, err = kOverrunErr );
  		n = (int)( dst - colonPtr );
  		for( i = 1; i <= n; ++i )
  		{
  			lim[ -i ] = colonPtr[ n - i ];
  			colonPtr[ n - i ] = 0;
  		}
  		dst = lim;
  	}
  	require_action_quiet( dst == lim, exit, err = kUnderrunErr );
  	
  	*outStr = inStr - 1;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	ParseIPv6Scope
  //
  //	Note: This was copied from CoreUtils because the StringToIPv6Address function is currently not exported in the framework.
  //===========================================================================================================================
  
  static OSStatus	ParseIPv6Scope( const char *inStr, uint32_t *outScope, const char **outStr )
  {
  #if( TARGET_OS_POSIX )
  	OSStatus			err;
  	char				scopeStr[ 64 ];
  	char *				dst;
  	char *				lim;
  	int					c;
  	uint32_t			scope;
  	const char *		ptr;
  	
  	// Copy into a local NULL-terminated string since that is what if_nametoindex expects.
  	
  	dst = scopeStr;
  	lim = dst + ( countof( scopeStr ) - 1 );
  	while( ( ( c = *inStr ) != '\0' ) && ( c != ':' ) && ( c != '/' ) && ( c != ']' ) && ( dst < lim ) )
  	{
  		*dst++ = *inStr++;
  	}
  	*dst = '\0';
  	check( dst <= lim );
  	
  	// First try to map as a name and if that fails, treat it as a numeric scope.
  	
  	scope = if_nametoindex( scopeStr );
  	if( scope == 0 )
  	{
  		for( ptr = scopeStr; ( ( c = *ptr ) >= '0' ) && ( c <= '9' ); ++ptr )
  		{
  			scope = ( scope * 10 ) + ( ( (uint8_t) c ) - '0' );
  		}
  		require_action_quiet( c == '\0', exit, err = kMalformedErr );
  		require_action_quiet( ( ptr != scopeStr ) && ( ( (int)( ptr - scopeStr ) ) <= 10 ), exit, err = kMalformedErr );
  	}
  	
  	*outScope = scope;
  	*outStr   = inStr;
  	err = kNoErr;
  	
  exit:
  	return( err );
  #else
  	OSStatus			err;
  	uint32_t			scope;
  	const char *		start;
  	int					c;
  	
  	scope = 0;
  	for( start = inStr; ( ( c = *inStr ) >= '0' ) && ( c <= '9' ); ++inStr )
  	{
  		scope = ( scope * 10 ) + ( c - '0' );
  	}
  	require_action_quiet( ( inStr != start ) && ( ( (int)( inStr - start ) ) <= 10 ), exit, err = kMalformedErr );
  	
  	*outScope = scope;
  	*outStr   = inStr;
  	err = kNoErr;
  	
  exit:
  	return( err );
  #endif
  }
  
  //===========================================================================================================================
  //	StringToIPv6Address
  //
  //	Note: This was copied from CoreUtils because the StringToIPv6Address function is currently not exported in the framework.
  //===========================================================================================================================
  
  OSStatus
  	StringToIPv6Address( 
  		const char *			inStr, 
  		StringToIPAddressFlags	inFlags, 
  		uint8_t					outIPv6[ 16 ], 
  		uint32_t *				outScope, 
  		int *					outPort, 
  		int *					outPrefix, 
  		const char **			outStr )
  {
  	OSStatus		err;
  	uint8_t			ipv6[ 16 ];
  	int				c;
  	int				hasScope;
  	uint32_t		scope;
  	int				hasPort;
  	int				port;
  	int				hasPrefix;
  	int				prefix;
  	int				hasBracket;
  	int				i;
  	
  	require_action( inStr, exit, err = kParamErr );
  	
  	if( *inStr == '[' ) ++inStr; // Skip a leading bracket for []-wrapped addresses (e.g. "[::1]:80").
  	
  	// Parse the address-only part of the address (e.g. "1::1").
  	
  	err = ParseIPv6Address( inStr, !( inFlags & kStringToIPAddressFlagsNoIPv4Mapped ), ipv6, &inStr );
  	require_noerr_quiet( err, exit );
  	c = *inStr;
  	
  	// Parse the scope, port, or prefix length.
  	
  	hasScope	= 0;
  	scope		= 0;
  	hasPort		= 0;
  	port		= 0;
  	hasPrefix	= 0;
  	prefix		= 0;
  	hasBracket	= 0;
  	for( ;; )
  	{
  		if( c == '%' )		// Scope (e.g. "%en0" or "%5")
  		{
  			require_action_quiet( !hasScope, exit, err = kMalformedErr );
  			require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoScope ), exit, err = kUnexpectedErr );
  			++inStr;
  			err = ParseIPv6Scope( inStr, &scope, &inStr );
  			require_noerr_quiet( err, exit );
  			hasScope = 1;
  			c = *inStr;
  		}
  		else if( c == ':' )	// Port (e.g. ":80")
  		{
  			require_action_quiet( !hasPort, exit, err = kMalformedErr );
  			require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPort ), exit, err = kUnexpectedErr );
  			while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) port = ( port * 10 ) + ( c - '0' );
  			require_action_quiet( port <= 65535, exit, err = kRangeErr );
  			hasPort = 1;
  		}
  		else if( c == '/' )	// Prefix Length (e.g. "/64")
  		{
  			require_action_quiet( !hasPrefix, exit, err = kMalformedErr );
  			require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPrefix ), exit, err = kUnexpectedErr );
  			while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) prefix = ( prefix * 10 ) + ( c - '0' );
  			require_action_quiet( ( prefix >= 0 ) && ( prefix <= 128 ), exit, err = kRangeErr );
  			hasPrefix = 1;
  		}
  		else if( c == ']' )
  		{
  			require_action_quiet( !hasBracket, exit, err = kMalformedErr );
  			hasBracket = 1;
  			c = *( ++inStr );
  		}
  		else
  		{
  			break;
  		}
  	}
  	
  	// Return the results. Only fill in scope/port/prefix results if the info was found to allow for defaults.
  	
  	if( outIPv6 )				 for( i = 0; i < 16; ++i ) outIPv6[ i ] = ipv6[ i ];
  	if( outScope  && hasScope )  *outScope	= scope;
  	if( outPort   && hasPort )   *outPort	= port;
  	if( outPrefix && hasPrefix ) *outPrefix	= prefix;
  	if( outStr )				 *outStr	= inStr;
  	err = kNoErr;
  	
  exit:
  	return( err );
  }
  
  //===========================================================================================================================
  //	StringArray_Append
  //
  //	Note: This was copied from CoreUtils because the StringArray_Append function is currently not exported in the framework.
  //===========================================================================================================================
  
  OSStatus	StringArray_Append( char ***ioArray, size_t *ioCount, const char *inStr )
  {
  	OSStatus		err;
  	char *			newStr;
  	size_t			oldCount;
  	size_t			newCount;
  	char **			oldArray;
  	char **			newArray;
  	
  	newStr = strdup( inStr );
  	require_action( newStr, exit, err = kNoMemoryErr );
  	
  	oldCount = *ioCount;
  	newCount = oldCount + 1;
  	newArray = (char **) malloc( newCount * sizeof( *newArray ) );
  	require_action( newArray, exit, err = kNoMemoryErr );
  	
  	if( oldCount > 0 )
  	{
  		oldArray = *ioArray;
  		memcpy( newArray, oldArray, oldCount * sizeof( *oldArray ) );
  		free( oldArray );
  	}
  	newArray[ oldCount ] = newStr;
  	newStr = NULL;
  	
  	*ioArray = newArray;
  	*ioCount = newCount;
  	err = kNoErr;
  	
  exit:
  	if( newStr ) free( newStr );
  	return( err );
  }
  
  //===========================================================================================================================
  //	StringArray_Free
  //
  //	Note: This was copied from CoreUtils because the StringArray_Free function is currently not exported in the framework.
  //===========================================================================================================================
  
  void	StringArray_Free( char **inArray, size_t inCount )
  {
  	size_t		i;
  	
  	for( i = 0; i < inCount; ++i )
  	{
  		free( inArray[ i ] );
  	}
  	if( inCount > 0 ) free( inArray );
  }
  
  //===========================================================================================================================
  //	ParseQuotedEscapedString
  //
  //	Note: This was copied from CoreUtils because it's currently not exported in the framework.
  //===========================================================================================================================
  
  Boolean
  	ParseQuotedEscapedString( 
  		const char *	inSrc, 
  		const char *	inEnd, 
  		const char *	inDelimiters, 
  		char *			inBuf, 
  		size_t			inMaxLen, 
  		size_t *		outCopiedLen, 
  		size_t *		outTotalLen, 
  		const char **	outSrc )
  {
  	const unsigned char *		src;
  	const unsigned char *		end;
  	unsigned char *				dst;
  	unsigned char *				lim;
  	unsigned char				c;
  	unsigned char				c2;
  	size_t						totalLen;
  	Boolean						singleQuote;
  	Boolean						doubleQuote;
  	
  	if( inEnd == NULL ) inEnd = inSrc + strlen( inSrc );
  	src = (const unsigned char *) inSrc;
  	end = (const unsigned char *) inEnd;
  	dst = (unsigned char *) inBuf;
  	lim = dst + inMaxLen;
  	while( ( src < end ) && isspace_safe( *src ) ) ++src; // Skip leading spaces.
  	if( src >= end ) return( false );
  	
  	// Parse each argument from the string.
  	//
  	// See <http://resources.mpi-inf.mpg.de/departments/rg1/teaching/unixffb-ss98/quoting-guide.html> for details.
  	
  	totalLen = 0;
  	singleQuote = false;
  	doubleQuote = false;
  	while( src < end )
  	{
  		c = *src++;
  		if( singleQuote )
  		{
  			// Single quotes protect everything (even backslashes, newlines, etc.) except single quotes.
  			
  			if( c == '\'' )
  			{
  				singleQuote = false;
  				continue;
  			}
  		}
  		else if( doubleQuote )
  		{
  			// Double quotes protect everything except double quotes and backslashes. A backslash can be 
  			// used to protect " or \ within double quotes. A backslash-newline pair disappears completely.
  			// A backslash followed by x or X and 2 hex digits (e.g. "\x1f") is stored as that hex byte.
  			// A backslash followed by 3 octal digits (e.g. "\377") is stored as that octal byte.
  			// A backslash that does not precede ", \, x, X, or a newline is taken literally.
  			
  			if( c == '"' )
  			{
  				doubleQuote = false;
  				continue;
  			}
  			else if( c == '\\' )
  			{
  				if( src < end )
  				{
  					c2 = *src;
  					if( ( c2 == '"' ) || ( c2 == '\\' ) )
  					{
  						++src;
  						c = c2;
  					}
  					else if( c2 == '\n' )
  					{
  						++src;
  						continue;
  					}
  					else if( ( c2 == 'x' ) || ( c2 == 'X' ) )
  					{
  						++src;
  						c = c2;
  						if( ( ( end - src ) >= 2 ) && IsHexPair( src ) )
  						{
  							c = HexPairToByte( src );
  							src += 2;
  						}
  					}
  					else if( isoctal_safe( c2 ) )
  					{
  						if( ( ( end - src ) >= 3 ) && IsOctalTriple( src ) )
  						{
  							c = OctalTripleToByte( src );
  							src += 3;
  						}
  					}
  				}
  			}
  		}
  		else if( strchr( inDelimiters, c ) )
  		{
  			break;
  		}
  		else if( c == '\\' )
  		{
  			// A backslash protects the next character, except a newline, x, X and 2 hex bytes or 3 octal bytes. 
  			// A backslash followed by a newline disappears completely.
  			// A backslash followed by x or X and 2 hex digits (e.g. "\x1f") is stored as that hex byte.
  			// A backslash followed by 3 octal digits (e.g. "\377") is stored as that octal byte.
  			
  			if( src < end )
  			{
  				c = *src;
  				if( c == '\n' )
  				{
  					++src;
  					continue;
  				}
  				else if( ( c == 'x' ) || ( c == 'X' ) )
  				{
  					++src;
  					if( ( ( end - src ) >= 2 ) && IsHexPair( src ) )
  					{
  						c = HexPairToByte( src );
  						src += 2;
  					}
  				}
  				else if( isoctal_safe( c ) )
  				{
  					if( ( ( end - src ) >= 3 ) && IsOctalTriple( src ) )
  					{
  						c = OctalTripleToByte( src );
  						src += 3;
  					}
  					else
  					{
  						++src;
  					}
  				}
  				else
  				{
  					++src;
  				}
  			}
  		}
  		else if( c == '\'' )
  		{
  			singleQuote = true;
  			continue;
  		}
  		else if( c == '"' )
  		{
  			doubleQuote = true;
  			continue;
  		}
  		
  		if( dst < lim )
  		{
  			if( inBuf ) *dst = c;
  			++dst;
  		}
  		++totalLen;
  	}
  	
  	if( outCopiedLen )	*outCopiedLen	= (size_t)( dst - ( (unsigned char *) inBuf ) );
  	if( outTotalLen )	*outTotalLen	= totalLen;
  	if( outSrc )		*outSrc			= (const char *) src;
  	return( true );
  }
  