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

    Change History (most recent first):

$Log: DNSServiceDiscovery.c,v $
Revision 1.9  2006/08/14 23:26:01  cheshire
Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0

Revision 1.8  2004/09/17 01:08:58  cheshire
Renamed mDNSClientAPI.h to mDNSEmbeddedAPI.h
  The name "mDNSClientAPI.h" is misleading to new developers looking at this code. The interfaces
  declared in that file are ONLY appropriate to single-address-space embedded applications.
  For clients on general-purpose computers, the interfaces defined in dns_sd.h should be used.

Revision 1.7  2004/05/08 12:24:48  bradley
Removed trailing character from zero value to fix compile error.

Revision 1.6  2004/05/06 18:42:58  ksekar
General dns_sd.h API cleanup, including the following radars:
<rdar://problem/3592068>: Remove flags with zero value
<rdar://problem/3479569>: Passing in NULL causes a crash.

Revision 1.5  2004/01/30 02:56:34  bradley
Updated to support full Unicode display. Added support for all services on www.dns-sd.org.

Revision 1.4  2003/11/14 20:59:10  cheshire
Clients can't use AssignDomainName macro because mDNSPlatformMemCopy is defined in mDNSPlatformFunctions.h.
Best solution is just to combine mDNSEmbeddedAPI.h and mDNSPlatformFunctions.h into a single file.

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

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

Revision 1.1  2003/08/20 06:04:45  bradley
Platform-neutral DNSServices-based emulation layer for the Mac OS X DNSServiceDiscovery API.
		
*/

#include	<stddef.h>
#include	<stdlib.h>
#include	<string.h>

#if( macintosh || __MACH__ )

	#include	<sys/types.h>
	#include	<sys/socket.h>
	#include	<netinet/in.h>
	
#elif( defined( _MSC_VER ) || defined( __MWERKS__ ) )
	
	#pragma warning( disable:4054 )		// Disable "type cast : from function pointer to data pointer".
	#pragma warning( disable:4055 )		// Disable "type cast : from data pointer to function pointer".
	#pragma warning( disable:4127 )		// Disable "conditional expression is constant" warning for debug macros.
	#pragma warning( disable:4152 )		// Disable "nonstandard extension, function/data pointer conversion in expression".

	#define	WIN32_LEAN_AND_MEAN			// Needed to avoid redefinitions by Windows interfaces.
	
	#include	<winsock2.h>
	
#endif

#include	"mDNSEmbeddedAPI.h"
#include	"DNSServices.h"

#include	"DNSServiceDiscovery.h"

#ifdef	__cplusplus
	extern "C" {
#endif

#if 0
#pragma mark == Constants & Types ==
#endif

//===========================================================================================================================
//	Constants & Types
//===========================================================================================================================

#define DEBUG_NAME		"[DNSServiceDiscovery] "

typedef enum
{
	kDNSServiceDiscoveryObjectTypeRegistration 			= 1, 
	kDNSServiceDiscoveryObjectTypeDomainEnumeration		= 2, 
	kDNSServiceDiscoveryObjectTypeBrowser				= 3, 
	kDNSServiceDiscoveryObjectTypeResolver				= 4 

}	DNSServiceDiscoveryObjectType;

typedef struct _dns_service_discovery_t		_dns_service_discovery_t;
struct	_dns_service_discovery_t
{
	DNSServiceDiscoveryObjectType		type;
	void *								ref;
	void *								callback;
	void *								context;
};

#if 0
#pragma mark == Macros ==
#endif

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

// Emulate Mac OS debugging macros for non-Mac platforms.

#if( !TARGET_OS_MAC )
	#define check(assertion)
	#define check_string( assertion, cstring )
	#define check_noerr(err)
	#define check_noerr_string( error, cstring )
	#define debug_string( cstring )
	#define require( assertion, label )									do { if( !(assertion) ) goto label; } while(0)
	#define require_string( assertion, label, string )					require(assertion, label)
	#define require_quiet( assertion, label )							require( assertion, label )
	#define require_noerr( error, label )								do { if( (error) != 0 ) goto label; } while(0)
	#define require_noerr_quiet( assertion, label )						require_noerr( assertion, label )
	#define require_noerr_action( error, label, action )				do { if( (error) != 0 ) { {action;}; goto label; } } while(0)
	#define require_noerr_action_quiet( assertion, label, action )		require_noerr_action( assertion, label, action )
	#define require_action( assertion, label, action )					do { if( !(assertion) ) { {action;}; goto label; } } while(0)
	#define require_action_quiet( assertion, label, action )			require_action( assertion, label, action )
	#define require_action_string( assertion, label, action, cstring )	do { if( !(assertion) ) { {action;}; goto label; } } while(0)
#endif

#if 0
#pragma mark == Prototypes ==
#endif

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

DNS_LOCAL void
	DNSServiceRegistrationPrivateCallBack( 
		void *							inContext, 
		DNSRegistrationRef				inRef, 
		DNSStatus						inStatusCode, 
		const DNSRegistrationEvent *	inEvent );

DNS_LOCAL void
	DNSServiceDomainEnumerationPrivateCallBack( 
		void *					inContext, 
		DNSBrowserRef 			inRef, 
		DNSStatus 				inStatusCode, 
		const DNSBrowserEvent *	inEvent );

DNS_LOCAL void
	DNSServiceBrowserPrivateCallBack( 
		void *					inContext, 
		DNSBrowserRef 			inRef, 
		DNSStatus 				inStatusCode, 
		const DNSBrowserEvent *	inEvent );

DNS_LOCAL void
	DNSServiceResolverPrivateCallBack( 
		void *						inContext, 
		DNSResolverRef 				inRef, 
		DNSStatus 					inStatusCode, 
		const DNSResolverEvent *	inEvent );

#if 0
#pragma mark -
#endif

//===========================================================================================================================
//	DNSServiceRegistrationCreate
//===========================================================================================================================

dns_service_discovery_ref
	DNSServiceRegistrationCreate(
		const char *				inName,
		const char *				inType,
		const char *				inDomain,
		uint16_t					inPort,
		const char *				inTextRecord,
		DNSServiceRegistrationReply	inCallBack,
		void *						inContext )
{
	DNSStatus						err;
	dns_service_discovery_ref		result;
	dns_service_discovery_ref		obj;
	void *							txt;
	size_t							txtSize;
	DNSOpaque16						port;
	DNSRegistrationRef				registration;
	
	result 	= NULL;
	txt		= NULL;
	txtSize	= 0;
	
	// Allocate and initialize the object.
	
	obj = (dns_service_discovery_ref) malloc( sizeof( *obj ) );
	require_action( obj, exit, err = kDNSNoMemoryErr );
	
	obj->type 		= kDNSServiceDiscoveryObjectTypeRegistration;
	obj->ref 		= NULL;
	obj->callback	= inCallBack;
	obj->context	= inContext;
	
	// Create the underlying registration. Build a \001-escaped text record if needed.
	
	if( inTextRecord )
	{
		err = DNSDynamicTextRecordBuildEscaped( inTextRecord, &txt, &txtSize );
		require_noerr( err, exit );
	}
	
	port.v8[ 0 ] = (DNSUInt8)( inPort >> 8 );
	port.v8[ 1 ] = (DNSUInt8)( inPort & 0xFF );
	err = DNSRegistrationCreate( kDNSRegistrationFlagPreFormattedTextRecord, inName, inType, inDomain, port.v16, txt, 
								 (DNSCount) txtSize, NULL, NULL, DNSServiceRegistrationPrivateCallBack, obj, &registration );
	require_noerr( err, exit ); 
	obj->ref = registration;
	
	// Success!
	
	result	= obj;
	obj		= NULL;
	
exit:
	if( txt )
	{
		DNSDynamicTextRecordRelease( txt );
	}
	if( obj )
	{
		DNSServiceDiscoveryDeallocate( obj );
	}
	return( result );
}

//===========================================================================================================================
//	DNSServiceRegistrationPrivateCallBack
//===========================================================================================================================

DNS_LOCAL void
	DNSServiceRegistrationPrivateCallBack( 
		void *							inContext, 
		DNSRegistrationRef				inRef, 
		DNSStatus						inStatusCode, 
		const DNSRegistrationEvent *	inEvent )
{
	dns_service_discovery_ref		obj;
	DNSServiceRegistrationReply		callback;
	
	DNS_UNUSED( inRef );
	DNS_UNUSED( inStatusCode );
	
	check( inContext );
	obj = (dns_service_discovery_ref) inContext;
	check( obj->callback );
	callback = (DNSServiceRegistrationReply) obj->callback;
	
	switch( inEvent->type )
	{
		case kDNSRegistrationEventTypeRegistered:
			debugf( DEBUG_NAME "name registered and active\n" );
			
			if( callback )
			{
				callback( kDNSServiceDiscoveryNoError, obj->context );
			}
			break;

		case kDNSRegistrationEventTypeNameCollision:
			debugf( DEBUG_NAME "name in use, please choose another name\n" );
			
			if( callback )
			{
				callback( kDNSServiceDiscoveryNameConflict, obj->context );
			}
			break;
		
		default:
			break;
	}
}

//===========================================================================================================================
//	DNSServiceRegistrationAddRecord
//===========================================================================================================================

DNSRecordReference
	DNSServiceRegistrationAddRecord( 
		dns_service_discovery_ref	inRef, 
		uint16_t 					inRRType, 
		uint16_t 					inRDLength, 
		const char *				inRData, 
		uint32_t 					inTTL )
{
	DNS_UNUSED( inRef );
	DNS_UNUSED( inRRType );
	DNS_UNUSED( inRDLength );
	DNS_UNUSED( inRData );
	DNS_UNUSED( inTTL );
	
	debugf( DEBUG_NAME "DNSServiceRegistrationAddRecord is currently not supported\n" );
	return( 0 );
}

//===========================================================================================================================
//	DNSServiceRegistrationUpdateRecord
//===========================================================================================================================

DNSServiceRegistrationReplyErrorType
	DNSServiceRegistrationUpdateRecord(
		dns_service_discovery_ref 	inRef, 
		DNSRecordReference			inRecordRef, 
		uint16_t 					inRDLength, 
		const char *				inRData, 
		uint32_t 					inTTL )
{
	DNS_UNUSED( inRef );
	DNS_UNUSED( inRecordRef );
	DNS_UNUSED( inRDLength );
	DNS_UNUSED( inRData );
	DNS_UNUSED( inTTL );
	
	debugf( DEBUG_NAME "DNSServiceRegistrationUpdateRecord is currently not supported\n" );
	return( kDNSServiceDiscoveryUnsupportedErr );
}

//===========================================================================================================================
//	DNSServiceRegistrationRemoveRecord
//===========================================================================================================================

DNSServiceRegistrationReplyErrorType
	DNSServiceRegistrationRemoveRecord( 
		dns_service_discovery_ref 	inRef, 
		DNSRecordReference 			inRecordRef )
{
	DNS_UNUSED( inRef );
	DNS_UNUSED( inRecordRef );
	
	debugf( DEBUG_NAME "DNSServiceRegistrationRemoveRecord is currently not supported\n" );
	return( kDNSServiceDiscoveryUnsupportedErr );
}

//===========================================================================================================================
//	DNSServiceDomainEnumerationCreate
//===========================================================================================================================

dns_service_discovery_ref
	DNSServiceDomainEnumerationCreate(
		int 								inRegistrationDomains,
		DNSServiceDomainEnumerationReply	inCallBack,
		void *								inContext )
{
	DNSStatus						err;
	dns_service_discovery_ref		result;
	dns_service_discovery_ref		obj;
	DNSBrowserRef					browser;
	DNSBrowserFlags					flags;
	
	result 	= NULL;
	browser	= NULL;
	
	// Allocate and initialize the object.
	
	obj = (dns_service_discovery_ref) malloc( sizeof( *obj ) );
	require_action( obj, exit, err = kDNSNoMemoryErr );
	
	obj->type 		= kDNSServiceDiscoveryObjectTypeDomainEnumeration;
	obj->ref 		= NULL;
	obj->callback	= inCallBack;
	obj->context	= inContext;
	
	// Create the underlying browser and start searching for domains.
	
	err = DNSBrowserCreate( 0, DNSServiceDomainEnumerationPrivateCallBack, obj, &browser );
	require_noerr( err, exit ); 
	obj->ref = browser;
	
	if( inRegistrationDomains )
	{
		flags = kDNSBrowserFlagRegistrationDomainsOnly;
	}
	else
	{
		flags = 0;
	}
	err = DNSBrowserStartDomainSearch( browser, flags );
	require_noerr( err, exit ); 
		
	// Success!
	
	result		= obj;
	browser		= NULL;
	obj			= NULL;
	
exit:
	if( browser )
	{
		DNSBrowserRelease( browser, 0 );
	}
	if( obj )
	{
		DNSServiceDiscoveryDeallocate( obj );
	}
	return( result );
}

//===========================================================================================================================
//	DNSServiceDomainEnumerationPrivateCallBack
//===========================================================================================================================

DNS_LOCAL void
	DNSServiceDomainEnumerationPrivateCallBack( 
		void *					inContext, 
		DNSBrowserRef 			inRef, 
		DNSStatus 				inStatusCode, 
		const DNSBrowserEvent *	inEvent )
{
	dns_service_discovery_ref				obj;
	DNSServiceDomainEnumerationReply		callback;
	
	DNS_UNUSED( inRef );
	DNS_UNUSED( inStatusCode );
	
	check( inContext );
	obj = (dns_service_discovery_ref) inContext;
	check( obj->callback );
	callback = (DNSServiceDomainEnumerationReply) obj->callback;
	
	switch( inEvent->type )
	{
		case kDNSBrowserEventTypeAddDomain:
			debugf( DEBUG_NAME "add domain \"%s\"\n", inEvent->data.addDomain.domain );
			
			if( callback )
			{
				callback( DNSServiceDomainEnumerationReplyAddDomain, inEvent->data.addDomain.domain, 
						  0, obj->context );
			}
			break;
		
		case kDNSBrowserEventTypeAddDefaultDomain:
			debugf( DEBUG_NAME "add default domain \"%s\"\n", inEvent->data.addDefaultDomain.domain );
			
			if( callback )
			{
				callback( DNSServiceDomainEnumerationReplyAddDomainDefault, inEvent->data.addDefaultDomain.domain, 
						  0, obj->context );
			}
			break;
		
		case kDNSBrowserEventTypeRemoveDomain:
			debugf( DEBUG_NAME "add default domain \"%s\"\n", inEvent->data.removeDomain.domain );
			
			if( callback )
			{
				callback( DNSServiceDomainEnumerationReplyRemoveDomain, inEvent->data.removeDomain.domain, 
						  0, obj->context );
			}
			break;
		
		default:
			break;
	}
}

//===========================================================================================================================
//	DNSServiceBrowserCreate
//===========================================================================================================================

dns_service_discovery_ref
	DNSServiceBrowserCreate(
		const char *			inType,
		const char *			inDomain,
		DNSServiceBrowserReply	inCallBack,
		void *					inContext )
{
	DNSStatus						err;
	dns_service_discovery_ref		result;
	dns_service_discovery_ref		obj;
	DNSBrowserRef					browser;
	
	result 	= NULL;
	browser	= NULL;
	
	// Allocate and initialize the object.
	
	obj = (dns_service_discovery_ref) malloc( sizeof( *obj ) );
	require_action( obj, exit, err = kDNSNoMemoryErr );
	
	obj->type 		= kDNSServiceDiscoveryObjectTypeBrowser;
	obj->ref 		= NULL;
	obj->callback	= inCallBack;
	obj->context	= inContext;
	
	// Create the underlying browser and start searching for domains.
	
	err = DNSBrowserCreate( 0, DNSServiceBrowserPrivateCallBack, obj, &browser );
	require_noerr( err, exit ); 
	obj->ref = browser;
	
	err = DNSBrowserStartServiceSearch( browser, 0, inType, inDomain );
	require_noerr( err, exit ); 
		
	// Success!
	
	result		= obj;
	browser		= NULL;
	obj			= NULL;
	
exit:
	if( browser )
	{
		DNSBrowserRelease( browser, 0 );
	}
	if( obj )
	{
		DNSServiceDiscoveryDeallocate( obj );
	}
	return( result );
}

//===========================================================================================================================
//	DNSServiceBrowserPrivateCallBack
//===========================================================================================================================

DNS_LOCAL void
	DNSServiceBrowserPrivateCallBack( 
		void *					inContext, 
		DNSBrowserRef 			inRef, 
		DNSStatus 				inStatusCode, 
		const DNSBrowserEvent *	inEvent )
{
	dns_service_discovery_ref		obj;
	DNSServiceBrowserReply			callback;
	
	DNS_UNUSED( inRef );
	DNS_UNUSED( inStatusCode );
	
	check( inContext );
	obj = (dns_service_discovery_ref) inContext;
	check( obj->callback );
	callback = (DNSServiceBrowserReply) obj->callback;
	
	switch( inEvent->type )
	{
		case kDNSBrowserEventTypeAddService:
			debugf( DEBUG_NAME "add service \"%s.%s%s\"\n", 
				  inEvent->data.addService.name, 
				  inEvent->data.addService.type, 
				  inEvent->data.addService.domain );
			
			if( callback )
			{
				callback( DNSServiceBrowserReplyAddInstance, 
						  inEvent->data.addService.name, 
						  inEvent->data.addService.type, 
						  inEvent->data.addService.domain, 
						  0, 
						  obj->context );
			}
			break;
		
		case kDNSBrowserEventTypeRemoveService:
			debugf( DEBUG_NAME "remove service \"%s.%s%s\"\n", 
				  inEvent->data.removeService.name, 
				  inEvent->data.removeService.type, 
				  inEvent->data.removeService.domain );
			
			if( callback )
			{
				callback( DNSServiceBrowserReplyRemoveInstance, 
						  inEvent->data.removeService.name, 
						  inEvent->data.removeService.type, 
						  inEvent->data.removeService.domain, 
						  0, 
						  obj->context );
			}
			break;
		
		default:
			break;
	}
}

//===========================================================================================================================
//	DNSServiceResolverResolve
//===========================================================================================================================

dns_service_discovery_ref
	DNSServiceResolverResolve(
		const char *			inName,
		const char *			inType,
		const char *			inDomain,
		DNSServiceResolverReply	inCallBack,
		void *					inContext )
{
	DNSStatus						err;
	dns_service_discovery_ref		result;
	dns_service_discovery_ref		obj;
	DNSResolverRef					resolver;
	
	result = NULL;
	
	// Allocate and initialize the object.
	
	obj = (dns_service_discovery_ref) malloc( sizeof( *obj ) );
	require_action( obj, exit, err = kDNSNoMemoryErr );
	
	obj->type 		= kDNSServiceDiscoveryObjectTypeResolver;
	obj->ref 		= NULL;
	obj->callback	= inCallBack;
	obj->context	= inContext;
	
	// Create the underlying resolver and start searching for domains.
	
	err = DNSResolverCreate( 0, inName, inType, inDomain, DNSServiceResolverPrivateCallBack, obj, NULL, &resolver );
	require_noerr( err, exit ); 
	obj->ref = resolver;
		
	// Success!
	
	result 	= obj;
	obj		= NULL;
	
exit:
	if( obj )
	{
		DNSServiceDiscoveryDeallocate( obj );
	}
	return( result );
}

//===========================================================================================================================
//	DNSServiceResolverPrivateCallBack
//===========================================================================================================================

DNS_LOCAL void
	DNSServiceResolverPrivateCallBack( 
		void *						inContext, 
		DNSResolverRef 				inRef, 
		DNSStatus 					inStatusCode, 
		const DNSResolverEvent *	inEvent )
{
	dns_service_discovery_ref		obj;
	DNSServiceResolverReply			callback;
	struct sockaddr_in				interfaceAddr;
	struct sockaddr_in				addr;
	
	DNS_UNUSED( inRef );
	DNS_UNUSED( inStatusCode );
	
	check( inContext );
	obj = (dns_service_discovery_ref) inContext;
	check( obj->callback );
	callback = (DNSServiceResolverReply) obj->callback;
	
	switch( inEvent->type )
	{
		case kDNSResolverEventTypeResolved:
			debugf( DEBUG_NAME "resolved \"%s.%s%s\"\n", 
				  inEvent->data.resolved.name, 
				  inEvent->data.resolved.type, 
				  inEvent->data.resolved.domain );
			
			memset( &interfaceAddr, 0, sizeof( interfaceAddr ) );
			interfaceAddr.sin_family		= AF_INET;
			interfaceAddr.sin_port			= 0;
			interfaceAddr.sin_addr.s_addr	= inEvent->data.resolved.interfaceIP.u.ipv4.addr.v32;
			
			memset( &addr, 0, sizeof( addr ) );
			addr.sin_family			= AF_INET;
			addr.sin_port			= inEvent->data.resolved.address.u.ipv4.port.v16;
			addr.sin_addr.s_addr	= inEvent->data.resolved.address.u.ipv4.addr.v32;
			
			if( callback )
			{
				callback( (struct sockaddr *) &interfaceAddr, (struct sockaddr *) &addr, inEvent->data.resolved.textRecord, 
						  0, obj->context );
			}
			break;
				
		default:
			break;
	}
}

//===========================================================================================================================
//	DNSServiceDiscoveryMachPort
//===========================================================================================================================

mach_port_t	DNSServiceDiscoveryMachPort( dns_service_discovery_ref inRef )
{
	DNS_UNUSED( inRef );
	
	debugf( DEBUG_NAME "DNSServiceDiscoveryMachPort is not supported\n" );
	return( 0 );
}

//===========================================================================================================================
//	DNSServiceDiscoveryDeallocate
//===========================================================================================================================

void	DNSServiceDiscoveryDeallocate( dns_service_discovery_ref inRef )
{
	_dns_service_discovery_t *		obj;
	DNSStatus						err;
	
	check( inRef );
	check( inRef->ref );
	
	obj = (_dns_service_discovery_t *) inRef;
	switch( obj->type )
	{
		case kDNSServiceDiscoveryObjectTypeRegistration:
			if( inRef->ref )
			{
				err = DNSRegistrationRelease( (DNSRegistrationRef) inRef->ref, 0 );
				check_noerr( err );
			}
			free( inRef );
			break;
		
		case kDNSServiceDiscoveryObjectTypeDomainEnumeration:
			if( inRef->ref )
			{
				err = DNSBrowserRelease( (DNSBrowserRef) inRef->ref, 0 );
				check_noerr( err );
			}
			free( inRef );
			break;
		
		case kDNSServiceDiscoveryObjectTypeBrowser:
			if( inRef->ref )
			{
				err = DNSBrowserRelease( (DNSBrowserRef) inRef->ref, 0 );
				check_noerr( err );
			}
			free( inRef );
			break;
		
		case kDNSServiceDiscoveryObjectTypeResolver:
			if( inRef->ref )
			{
				err = DNSResolverRelease( (DNSResolverRef) inRef->ref, 0 );
				check_noerr( err );
			}
			free( inRef );
			break;
		
		default:
			debugf( DEBUG_NAME "unknown object type (%d)\n", obj->type );
			break;
	}
}

//===========================================================================================================================
//	DNSServiceDiscovery_handleReply
//===========================================================================================================================

void	DNSServiceDiscovery_handleReply( void *inReplyMessage )
{
	DNS_UNUSED( inReplyMessage );
	
	debugf( DEBUG_NAME "DNSServiceDiscovery_handleReply is not supported\n" );
}

#ifdef	__cplusplus
	}
#endif
