BonjourEvents.c   [plain text]


/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2010 Apple 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.
 */

#include <CoreFoundation/CoreFoundation.h>
#include <dns_sd.h>
#include <UserEventAgentInterface.h>
#include <stdio.h>
#include <stdlib.h>


#pragma mark -
#pragma mark Types
#pragma mark -
static const char*			sPluginIdentifier		= "com.apple.bonjour.events";

// PLIST Keys
static const CFStringRef	sServiceNameKey			= CFSTR("ServiceName");
static const CFStringRef	sServiceTypeKey			= CFSTR("ServiceType");
static const CFStringRef	sServiceDomainKey		= CFSTR("ServiceDomain");

static const CFStringRef	sOnServiceAddKey		= CFSTR("OnServiceAdd");
static const CFStringRef	sOnServiceRemoveKey		= CFSTR("OnServiceRemove");
static const CFStringRef    sWhileServiceExistsKey	= CFSTR("WhileServiceExists");

static const CFStringRef	sLaunchdTokenKey		= CFSTR("LaunchdToken");

static const CFStringRef	sPluginTimersKey		= CFSTR("PluginTimers");


/************************************************
 * Launch Event Dictionary (input from launchd)
 * Passed To: ManageEventsCallback
 *-----------------------------------------------
 * Typing in this dictionary is not enforced 
 * above us. So this may not be true. Type check
 * all input before using it.
 *-----------------------------------------------
 * sServiceNameKey		- CFString (Optional) 
 * sServiceTypeKey		- CFString
 * sServiceDomainKey	- CFString
 *
 * One or more of the following.
 *-----------------------------------
 * sOnServiceAddKey			- CFBoolean
 * sOnServiceRemoveKey		- CFBoolean 
 * sWhileServiceExistsKey	- CFBoolean
 ************************************************/

/************************************************
 * Browser Dictionary
 *-----------------------------------------------
 * sServiceDomainKey - CFString
 * sServiceTypeKey   - CFString
 ************************************************/

/************************************************
 * Event Dictionary
 *-----------------------------------------------
 * sServiceNameKey	 - CFString (Optional) 
 * sLaunchdTokenKey	 - CFNumber
 ************************************************/

typedef struct {
    UserEventAgentInterfaceStruct*		_UserEventAgentInterface;
    CFUUIDRef							_factoryID;
    UInt32								_refCount;
	
	void*								_pluginContext;
	
	CFMutableDictionaryRef				_tokenToBrowserMap;		// Maps a token to a browser that can be used to scan the remaining dictionaries.
	CFMutableDictionaryRef				_browsers;				// A Dictionary of "Browser Dictionarys" where the resposible browser is the key.
	CFMutableDictionaryRef				_onAddEvents;			// A Dictionary of "Event Dictionarys" that describe events to trigger on a service appearing.
	CFMutableDictionaryRef				_onRemoveEvents;		// A Dictionary of "Event Dictionarys" that describe events to trigger on a service disappearing.
	CFMutableDictionaryRef				_whileServiceExist;		// A Dictionary of "Event Dictionarys" that describe events to trigger on a service disappearing.

	
	CFMutableArrayRef					_timers;

} BonjourUserEventsPlugin;


typedef struct {
	
	CFIndex	 refCount;
	BonjourUserEventsPlugin* plugin;
	CFNumberRef token;
	
} TimerContextInfo;

typedef struct {
	CFIndex refCount;
	DNSServiceRef browserRef;
} NetBrowserInfo;

#pragma mark -
#pragma mark Prototypes
#pragma mark -
// COM Stuff
static HRESULT	QueryInterface(void *myInstance, REFIID iid, LPVOID *ppv);
static ULONG	AddRef(void* instance);
static ULONG	Release(void* instance);

static BonjourUserEventsPlugin* Alloc(CFUUIDRef factoryID);
static void Dealloc(BonjourUserEventsPlugin* plugin);

void * UserEventAgentFactory(CFAllocatorRef allocator, CFUUIDRef typeID);

// Plugin Management
static void Install(void* instance);
static void ManageEventsCallback(
						  UserEventAgentLaunchdAction action,
						  CFNumberRef                 token,
						  CFTypeRef                   eventMatchDict,
						  void                      * vContext);


// Plugin Guts
void AddEventToPlugin(BonjourUserEventsPlugin* plugin, CFNumberRef launchdToken, CFDictionaryRef eventParameters);
void RemoveEventFromPlugin(BonjourUserEventsPlugin* plugin, CFNumberRef	launchToken);

NetBrowserInfo* CreateBrowserForTypeAndDomain(BonjourUserEventsPlugin* plugin, CFStringRef type, CFStringRef domain);
NetBrowserInfo* BrowserForSDRef(BonjourUserEventsPlugin* plugin, DNSServiceRef sdRef);
void AddEventDictionary(CFDictionaryRef eventDict, CFMutableDictionaryRef allEventsDictionary, NetBrowserInfo* key);
void RemoveEventFromArray(CFMutableArrayRef array, CFNumberRef launchdToken);

// Net Service Browser Stuff
void ServiceBrowserCallback (DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char* serviceName, const char* regtype, const char* replyDomain, void* context);
void HandleTemporaryEventsForService(BonjourUserEventsPlugin* plugin, NetBrowserInfo* browser, CFStringRef serviceName, CFMutableDictionaryRef eventsDictionary);
void HandleStateEventsForService(BonjourUserEventsPlugin* plugin, NetBrowserInfo* browser,  CFStringRef serviceName, Boolean didAppear);
void TemporaryEventTimerCallout ( CFRunLoopTimerRef timer, void *info );

// Convence Stuff
const char* CStringFromCFString(CFStringRef string);


// TimerContextInfo "Object"
TimerContextInfo* TimerContextInfoCreate(BonjourUserEventsPlugin* plugin, CFNumberRef token);
const void* TimerContextInfoRetain(const void* info);
void TimerContextInfoRelease(const void* info);
CFStringRef TimerContextInfoCopyDescription(const void* info);

// NetBrowserInfo "Object"
NetBrowserInfo* NetBrowserInfoCreate(CFStringRef serviceType, CFStringRef domain, void* context);
const void* NetBrowserInfoRetain(CFAllocatorRef allocator, const void* info);
void NetBrowserInfoRelease(CFAllocatorRef allocator, const void* info);
Boolean	NetBrowserInfoEqual(const void *value1, const void *value2);
CFHashCode	NetBrowserInfoHash(const void *value);
CFStringRef	NetBrowserInfoCopyDescription(const void *value);


static const CFDictionaryKeyCallBacks kNetBrowserInfoDictionaryKeyCallbacks = { 
	0, 
	NetBrowserInfoRetain, 
	NetBrowserInfoRelease,
	NetBrowserInfoCopyDescription,
	NetBrowserInfoEqual,
	NetBrowserInfoHash
};

static const CFDictionaryValueCallBacks kNetBrowserInfoDictionaryValueCallbacks = { 
	0, 
	NetBrowserInfoRetain, 
	NetBrowserInfoRelease,
	NetBrowserInfoCopyDescription,
	NetBrowserInfoEqual	
};

// COM type definition goop.
static UserEventAgentInterfaceStruct UserEventAgentInterfaceFtbl = {
	NULL,                   // Required padding for COM
	QueryInterface,			// Query Interface
	AddRef,					// AddRef()
	Release,				// Release()
	Install					// Install 
}; 

#pragma mark -
#pragma mark COM Management
#pragma mark -

/*****************************************************************************
 *****************************************************************************/
static HRESULT QueryInterface(void *myInstance, REFIID iid, LPVOID *ppv) 
{
	CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes(NULL, iid);
	
	// Test the requested ID against the valid interfaces.
	if(CFEqual(interfaceID, kUserEventAgentInterfaceID)) 
	{
		((BonjourUserEventsPlugin *) myInstance)->_UserEventAgentInterface->AddRef(myInstance);
		*ppv = myInstance;
		CFRelease(interfaceID);
		return S_OK;
	} 
	else if(CFEqual(interfaceID, IUnknownUUID)) 
	{
		((BonjourUserEventsPlugin *) myInstance)->_UserEventAgentInterface->AddRef(myInstance);
		*ppv = myInstance;
		CFRelease(interfaceID);
		return S_OK;
    } 
	else //  Requested interface unknown, bail with error.
	{ 
		*ppv = NULL;
		CFRelease(interfaceID);
		return E_NOINTERFACE;
	}
}

/*****************************************************************************
 *****************************************************************************/
static ULONG AddRef(void* instance)
{
	BonjourUserEventsPlugin* plugin = (BonjourUserEventsPlugin*)instance;
	return 	++plugin->_refCount;
}

/*****************************************************************************
 *****************************************************************************/
static ULONG Release(void* instance)
{
	BonjourUserEventsPlugin* plugin = (BonjourUserEventsPlugin*)instance;

	if (plugin->_refCount != 0)
		--plugin->_refCount;
	
	if (plugin->_refCount == 0)
	{
		Dealloc(instance);
		return 0;
	}
	
	return plugin->_refCount;
} 

/*****************************************************************************
 * Alloc
 * - 
 * Functionas as both +[alloc] and -[init] for the plugin. Add any 
 * initalization of member variables here.
 *****************************************************************************/
static BonjourUserEventsPlugin* Alloc(CFUUIDRef factoryID)
{
	BonjourUserEventsPlugin* plugin = malloc(sizeof(BonjourUserEventsPlugin));
	
	plugin->_UserEventAgentInterface = &UserEventAgentInterfaceFtbl;
	plugin->_pluginContext = NULL;
		
	if (factoryID) 
	{
		plugin->_factoryID = (CFUUIDRef)CFRetain(factoryID);
		CFPlugInAddInstanceForFactory(factoryID);
	}
	
	plugin->_refCount = 1;
	plugin->_tokenToBrowserMap = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kNetBrowserInfoDictionaryValueCallbacks);
	plugin->_browsers = CFDictionaryCreateMutable(NULL, 0, &kNetBrowserInfoDictionaryKeyCallbacks, &kCFTypeDictionaryValueCallBacks);
	plugin->_onAddEvents = CFDictionaryCreateMutable(NULL, 0, &kNetBrowserInfoDictionaryKeyCallbacks, &kCFTypeDictionaryValueCallBacks);
	plugin->_onRemoveEvents = CFDictionaryCreateMutable(NULL, 0, &kNetBrowserInfoDictionaryKeyCallbacks, &kCFTypeDictionaryValueCallBacks);
	plugin->_whileServiceExist = CFDictionaryCreateMutable(NULL, 0, &kNetBrowserInfoDictionaryKeyCallbacks, &kCFTypeDictionaryValueCallBacks);
	
	plugin->_timers = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
	
	return plugin;
}

/*****************************************************************************
 * Dealloc
 * - 
 * Much like Obj-C dealloc this method is responsible for releasing any object
 * this plugin is holding. Unlike ObjC, you call directly free() instead of 
 * [super dalloc].
 *****************************************************************************/
static void Dealloc(BonjourUserEventsPlugin* plugin)
{
	CFUUIDRef factoryID = plugin->_factoryID;
	
	if (factoryID)
	{
		CFPlugInRemoveInstanceForFactory(factoryID);
		CFRelease(factoryID);
	}
	
	if (plugin->_tokenToBrowserMap)
		CFRelease(plugin->_tokenToBrowserMap);
	
	if (plugin->_browsers)
		CFRelease(plugin->_browsers);
	
	if (plugin->_onAddEvents)
		CFRelease(plugin->_onAddEvents);
	
	if (plugin->_onRemoveEvents)
		CFRelease(plugin->_onRemoveEvents);
	
	if (plugin->_whileServiceExist)
		CFRelease(plugin->_whileServiceExist);
	
	if (plugin->_timers)
	{
		CFIndex i;
		CFIndex count = CFArrayGetCount(plugin->_timers);
		CFRunLoopRef crl = CFRunLoopGetCurrent();
		
		for (i = 0; i < count; ++i)
		{
			CFRunLoopTimerRef timer = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(plugin->_timers, i);
			CFRunLoopRemoveTimer(crl, timer, kCFRunLoopCommonModes);
		}
		
		CFRelease(plugin->_timers);
	}
	
	free(plugin);
}

/*******************************************************************************
 *******************************************************************************/
void * UserEventAgentFactory(CFAllocatorRef allocator, CFUUIDRef typeID)
{
	(void)allocator;
    BonjourUserEventsPlugin * result = NULL;
	
    if (typeID && CFEqual(typeID, kUserEventAgentTypeID)) {
        result = Alloc(kUserEventAgentFactoryID);
    }
	
    return (void *)result;
}

#pragma mark -
#pragma mark Plugin Management
#pragma mark -
/*****************************************************************************
 * Install
 * -
 * This is invoked once when the plugin is loaded to do initial setup and
 * allow us to register with launchd. If UserEventAgent crashes, the plugin 
 * will need to be reloaded, and hence this will get invoked again.
 *****************************************************************************/
static void Install(void *instance)
{
	BonjourUserEventsPlugin* plugin = (BonjourUserEventsPlugin*)instance;
	
	plugin->_pluginContext = UserEventAgentRegisterForLaunchEvents(sPluginIdentifier, &ManageEventsCallback, plugin);
	
	if (!plugin->_pluginContext)
	{
		fprintf(stderr, "%s: failed to register for launch events.\n", sPluginIdentifier);
		return;
	}
	
}

/*****************************************************************************
 * ManageEventsCallback
 * - 
 * This is invoked when launchd loads a event dictionary and needs to inform 
 * us what a daemon / agent is looking for.
 *****************************************************************************/
static void ManageEventsCallback(UserEventAgentLaunchdAction action, CFNumberRef token, CFTypeRef eventMatchDict, void* vContext)
{
	
	if (!eventMatchDict || CFGetTypeID(eventMatchDict) != CFDictionaryGetTypeID())
	{
		fprintf(stderr, "%s given non-dictionary for event dictionary\n", sPluginIdentifier);
		return;
	}
	
	if (action == kUserEventAgentLaunchdAdd)
	{
		// Launchd wants us to add a launch event for this token and matching dictionary.
		AddEventToPlugin((BonjourUserEventsPlugin*)vContext, token, (CFDictionaryRef)eventMatchDict);
	}
	else if (action == kUserEventAgentLaunchdRemove)
	{
		// Launchd wants us to remove the event hook we setup for this token / matching dictionary.
		RemoveEventFromPlugin((BonjourUserEventsPlugin*)vContext, token);
	}
	else
	{
		fprintf(stderr, "%s got unknown UserEventAction: %d\n", sPluginIdentifier, action);
	}
}


#pragma mark -
#pragma mark Plugin Guts
#pragma mark -

/*****************************************************************************
 * AddEventToPlugin
 * - 
 * This method is invoked when launchd wishes the plugin to setup a launch 
 * event matching the parameters in the dictionary.
 *****************************************************************************/
void AddEventToPlugin(BonjourUserEventsPlugin* plugin, CFNumberRef launchdToken, CFDictionaryRef eventParameters)
{
	CFStringRef		domain = CFDictionaryGetValue(eventParameters, sServiceDomainKey);
	CFStringRef		type = CFDictionaryGetValue(eventParameters, sServiceTypeKey);
	CFStringRef		name = CFDictionaryGetValue(eventParameters, sServiceNameKey);
	CFBooleanRef	cfOnAdd = CFDictionaryGetValue(eventParameters, sOnServiceAddKey);
	CFBooleanRef	cfOnRemove = CFDictionaryGetValue(eventParameters, sOnServiceRemoveKey);
	CFBooleanRef    cfWhileSericeExists = CFDictionaryGetValue(eventParameters, sWhileServiceExistsKey);
	
	Boolean			onAdd = false;
	Boolean			onRemove = false;
	Boolean			whileExists = false;
	
	if (cfOnAdd && CFGetTypeID(cfOnRemove) == CFBooleanGetTypeID() && CFBooleanGetValue(cfOnAdd))
		onAdd = true;
	
	if (cfOnRemove && CFGetTypeID(cfOnRemove) == CFBooleanGetTypeID() && CFBooleanGetValue(cfOnRemove))
		onRemove = true;
	
	if (cfWhileSericeExists && CFGetTypeID(cfWhileSericeExists) == CFBooleanGetTypeID() && CFBooleanGetValue(cfWhileSericeExists))
		whileExists = true;
	
	// A type is required. If none is specified, BAIL
	if (!type || CFGetTypeID(type) != CFStringGetTypeID()) 
	{
		fprintf(stderr, "%s, a LaunchEvent is missing a service type.\n", sPluginIdentifier);
		return;
	}
	
	// If we aren't suppose to launch on services appearing or disappearing, this service does nothing. Ignore.
	if ((!onAdd && !onRemove && !whileExists) || (onAdd && onRemove && whileExists))
	{
		fprintf(stderr, "%s, a LaunchEvent is missing both onAdd/onRemove/existance or has both.\n", sPluginIdentifier);
		return;
	}

	// If no domain is specified, assume local.
	if (!domain)
	{
		domain = CFSTR("local"); 
	}
	else if (CFGetTypeID(domain) != CFStringGetTypeID() ) // If the domain is not a string, fai;
	{
		fprintf(stderr, "%s, a LaunchEvent has a domain that is not a string.\n", sPluginIdentifier);
		return;
	}

	
	// If we have a name filter, but it's not a string. This event it broken, bail.
	if (name && CFGetTypeID(name) != CFStringGetTypeID())
	{
		fprintf(stderr, "%s, a LaunchEvent has a domain that is not a string.\n", sPluginIdentifier);
		return;
	}
	
	// Get us a browser
	NetBrowserInfo* browser = CreateBrowserForTypeAndDomain(plugin, type, domain);
	
	if (!browser)
	{
		fprintf(stderr, "%s, a LaunchEvent has a domain that is not a string.\n", sPluginIdentifier);
		return;
	}
	
	// Create Event Dictionary
	CFMutableDictionaryRef eventDictionary = CFDictionaryCreateMutable(NULL, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	
	
	CFDictionarySetValue(eventDictionary, sLaunchdTokenKey, launchdToken);
	
	if (name)
		CFDictionarySetValue(eventDictionary, sServiceNameKey, name);
	
	// Add to the correct dictionary.
	if (onAdd)
		AddEventDictionary(eventDictionary, plugin->_onAddEvents, browser);
	
	if (onRemove)
		AddEventDictionary(eventDictionary, plugin->_onRemoveEvents, browser);
	
	if (whileExists)
		AddEventDictionary(eventDictionary, plugin->_whileServiceExist, browser);
	
	// Add Token Mapping
	CFDictionarySetValue(plugin->_tokenToBrowserMap, launchdToken, browser);
	
	// Release Memory
	CFRelease(eventDictionary);
	NetBrowserInfoRelease(NULL, browser);
	
}



/*****************************************************************************
 * RemoveEventFromPlugin
 * - 
 * This method is invoked when launchd wishes the plugin to setup a launch 
 * event matching the parameters in the dictionary.
 *****************************************************************************/
void RemoveEventFromPlugin(BonjourUserEventsPlugin* plugin, CFNumberRef	launchdToken)
{
	NetBrowserInfo* browser = (NetBrowserInfo*)CFDictionaryGetValue(plugin->_tokenToBrowserMap, launchdToken);
	Boolean othersUsingBrowser = false;
	
	if (!browser)
	{
		long long value = 0;
		CFNumberGetValue(launchdToken, kCFNumberLongLongType, &value);
		fprintf(stderr, "%s, Launchd asked us to remove a token we did not register!\nToken:%lld\n", sPluginIdentifier, value);
		return;
	}
	
	CFMutableArrayRef onAddEvents = (CFMutableArrayRef)CFDictionaryGetValue(plugin->_onAddEvents, browser);
	CFMutableArrayRef onRemoveEvents = (CFMutableArrayRef)CFDictionaryGetValue(plugin->_onRemoveEvents, browser);
	
	if (onAddEvents)
	{
		RemoveEventFromArray(onAddEvents, launchdToken);
				
		// Is the array now empty, clean up 
		if (CFArrayGetCount(onAddEvents) == 0)
			CFDictionaryRemoveValue(plugin->_onAddEvents, browser);
	}

	if (onRemoveEvents)
	{
		RemoveEventFromArray(onRemoveEvents, launchdToken);
		
		// Is the array now empty, clean up 
		if (CFArrayGetCount(onRemoveEvents) == 0)
			CFDictionaryRemoveValue(plugin->_onRemoveEvents, browser);
	}
	
	// Remove ourselves from the token dictionary.
	CFDictionaryRemoveValue(plugin->_tokenToBrowserMap, launchdToken);
	
	// Check to see if anyone else is using this browser.
	CFIndex i;
	CFIndex count = CFDictionaryGetCount(plugin->_tokenToBrowserMap);
	NetBrowserInfo** browsers = malloc(count * sizeof(NetBrowserInfo*));

	// Fetch the values of the token dictionary
	CFDictionaryGetKeysAndValues(plugin->_tokenToBrowserMap, NULL, (const void**)browsers);

	for (i = 0; i < count; ++i)
	{
		if (NetBrowserInfoEqual(browsers[i], browser))
		{
			othersUsingBrowser = true;
			break;
		}
	}
	
	// If no one else is useing our browser, clean up!
	if (!othersUsingBrowser)
	{
		CFDictionaryRemoveValue(plugin->_tokenToBrowserMap, launchdToken); // This triggers release and dealloc of the browser
	}
	
	free(browsers);
}


/*****************************************************************************
 * CreateBrowserForTypeAndDomain
 * - 
 * This method returns a NetBrowserInfo that is looking for a type of 
 * service in a domain. If no browser exists, it will create one and return it.
 *****************************************************************************/
NetBrowserInfo* CreateBrowserForTypeAndDomain(BonjourUserEventsPlugin* plugin, CFStringRef type, CFStringRef domain)
{
	CFIndex i;
	CFIndex count = CFDictionaryGetCount(plugin->_browsers);
	NetBrowserInfo* browser = NULL;
	CFDictionaryRef* dicts = malloc(count * sizeof(CFDictionaryRef));
	NetBrowserInfo** browsers = malloc(count * sizeof(NetBrowserInfo*));
	
	// Fetch the values of the browser dictionary
	CFDictionaryGetKeysAndValues(plugin->_browsers, (const void**)browsers, (const void**)dicts);
	
	// Loop thru the browsers list and see if we can find a matching one.
	for (i = 0; i < count; ++i)
	{
		CFDictionaryRef browserDict = dicts[i];

		CFStringRef browserType = CFDictionaryGetValue(browserDict, sServiceTypeKey);
		CFStringRef browserDomain = CFDictionaryGetValue(browserDict, sServiceDomainKey);
		
		// If we have a matching browser, break
		if (CFStringCompare(browserType, type, kCFCompareCaseInsensitive) &&
			CFStringCompare(browserDomain, domain, kCFCompareCaseInsensitive))
		{
			browser = browsers[i];
			NetBrowserInfoRetain(NULL, browser);
			break;
		}
	}
	
	// No match found, lets create one!
	if (!browser)
	{
		
		browser = NetBrowserInfoCreate(type, domain, plugin);
		
		if (!browser)
		{			
			fprintf(stderr, "%s, failed to search for %s.%s", sPluginIdentifier, CStringFromCFString(type) , CStringFromCFString(domain));
			free(dicts);
			free(browsers);
			return NULL;
		}
		
		// Service browser created, lets add this to ourselves to the dictionary.
		CFMutableDictionaryRef browserDict = CFDictionaryCreateMutable(NULL, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
		
		CFDictionarySetValue(browserDict, sServiceTypeKey, type);
		CFDictionarySetValue(browserDict, sServiceDomainKey, domain);
		
		// Add the dictionary to the browsers dictionary.
		CFDictionarySetValue(plugin->_browsers, browser, browserDict);
		
		// Release Memory
		CFRelease(browserDict);
	}
	
	free(dicts);
	free(browsers);
	
	return browser;
}

/*****************************************************************************
 * BrowserForSDRef
 * - 
 * This method returns a NetBrowserInfo that matches the calling SDRef passed
 * in via the callback.
 *****************************************************************************/
NetBrowserInfo* BrowserForSDRef(BonjourUserEventsPlugin* plugin, DNSServiceRef sdRef)
{
	CFIndex i;
	CFIndex count = CFDictionaryGetCount(plugin->_browsers);
	NetBrowserInfo* browser = NULL;
	NetBrowserInfo** browsers = malloc(count * sizeof(NetBrowserInfo*));
	
	// Fetch the values of the browser dictionary
	CFDictionaryGetKeysAndValues(plugin->_browsers, (const void**)browsers, NULL);
	
	// Loop thru the browsers list and see if we can find a matching one.
	for (i = 0; i < count; ++i)
	{
		NetBrowserInfo* currentBrowser = browsers[i];
		
		if (currentBrowser->browserRef == sdRef)
		{
			browser = currentBrowser;
			break;
		}
	}
	
	
	free(browsers);

	return browser;
}

/*****************************************************************************
 * AddEventDictionary
 * - 
 * Adds a event to a browser's event dictionary
 *****************************************************************************/

void AddEventDictionary(CFDictionaryRef eventDict, CFMutableDictionaryRef allEventsDictionary, NetBrowserInfo* key)
{
	CFMutableArrayRef eventsForBrowser = (CFMutableArrayRef)CFDictionaryGetValue(allEventsDictionary, key);
	
	if (!eventsForBrowser) // We have no events for this browser yet, lets add him.
	{
		eventsForBrowser = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
		CFDictionarySetValue(allEventsDictionary, key, eventsForBrowser); 
	}
	else 
	{
		CFRetain(eventsForBrowser);
	}

	CFArrayAppendValue(eventsForBrowser, eventDict);
	CFRelease(eventsForBrowser);
}

/*****************************************************************************
 * RemoveEventFromArray
 * - 
 * Searches a Array of Event Dictionaries to find one with a matching launchd
 * token and remove it.
 *****************************************************************************/

void RemoveEventFromArray(CFMutableArrayRef array, CFNumberRef launchdToken)
{
	CFIndex i;
	CFIndex count = CFArrayGetCount(array);
	// Loop thru looking for us.
	for (i = 0; i < count; )
	{
		CFDictionaryRef eventDict = CFArrayGetValueAtIndex(array, i);
		CFNumberRef token = CFDictionaryGetValue(eventDict, sLaunchdTokenKey);
		
		if (CFEqual(token, launchdToken)) // This is the same event?
		{
			CFArrayRemoveValueAtIndex(array, i);	// Remove the event,
			break; // The token should only exist once, so it make no sense to continue.
		}
		else
		{
			++i; // If it's not us, advance.
		}
	}	
}

#pragma mark -
#pragma mark Net Service Browser Stuff
#pragma mark -

/*****************************************************************************
 * ServiceBrowserCallback
 * - 
 * This method is the heart of the plugin. It's the runloop callback annoucing 
 * the appearence and disappearance of network services.
 *****************************************************************************/

void ServiceBrowserCallback (DNSServiceRef				sdRef,
							 DNSServiceFlags            flags,
							 uint32_t                   interfaceIndex,
							 DNSServiceErrorType        errorCode,
							 const char*                serviceName,
							 const char*                regtype,
							 const char*                replyDomain,
							 void*                      context )
{
	(void)interfaceIndex;
	(void)regtype;
	(void)replyDomain;
	BonjourUserEventsPlugin* plugin = (BonjourUserEventsPlugin*)context;
	NetBrowserInfo* browser = BrowserForSDRef(plugin, sdRef);
	
	if (!browser) // Missing browser?
		return;
	
	if (errorCode != kDNSServiceErr_NoError)
		return;
	
	CFStringRef cfServiceName = CFStringCreateWithCString(NULL, serviceName, kCFStringEncodingUTF8);
	
	if (flags & kDNSServiceFlagsAdd)
	{
		HandleTemporaryEventsForService(plugin, browser, cfServiceName, plugin->_onAddEvents);
		HandleStateEventsForService(plugin, browser, cfServiceName, true);
	}
	else 
	{
		HandleTemporaryEventsForService(plugin, browser, cfServiceName, plugin->_onRemoveEvents);
		HandleStateEventsForService(plugin, browser, cfServiceName, false);
	}

	CFRelease(cfServiceName);
}

/*****************************************************************************
 * HandleTemporaryEventsForService
 * - 
 * This method handles the firing of one shot events. Aka. Events that are 
 * signaled when a service appears / disappears. They have a temporarly 
 * signaled state. 
 *****************************************************************************/
void HandleTemporaryEventsForService(BonjourUserEventsPlugin* plugin, NetBrowserInfo* browser, CFStringRef serviceName, CFMutableDictionaryRef eventsDictionary)
{
	CFArrayRef events = (CFArrayRef)CFDictionaryGetValue(eventsDictionary, browser); // Get events for the browser we passed in.
	CFIndex i;
	CFIndex count;
	
	if (!events)  // Somehow we have a orphan browser...
		return;
	
	count = CFArrayGetCount(events);
	
	// Go thru the events and run filters, notifity if they pass.
	for (i = 0; i < count; ++i)
	{
		CFDictionaryRef eventDict = (CFDictionaryRef)CFArrayGetValueAtIndex(events, i);
		CFStringRef eventServiceName = (CFStringRef)CFDictionaryGetValue(eventDict, sServiceNameKey);
		CFNumberRef token = (CFNumberRef) CFDictionaryGetValue(eventDict, sLaunchdTokenKey);
				
		// Currently we only filter on service name, that makes this as simple as... 
		if (!eventServiceName || CFEqual(serviceName, eventServiceName))
		{
			// Create Context Info
			CFRunLoopTimerContext context; 
			TimerContextInfo* info = TimerContextInfoCreate(plugin, token);

			context.version = 0;
			context.info = info;
			context.retain = TimerContextInfoRetain;
			context.release = TimerContextInfoRelease;
			context.copyDescription = TimerContextInfoCopyDescription;
			
			// Create and add one shot timer to flip the event off after a second
			CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + 1.0, 0, 0, 0, TemporaryEventTimerCallout, &context);
			CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes);
			
			// Signal Event
			UserEventAgentSetLaunchEventState(plugin->_pluginContext, token, true);
			
			// Clean Up
			TimerContextInfoRelease(info);
			CFRelease(timer);
			
		}
	}
	
}

/*****************************************************************************
 * HandleStateEventsForService
 * - 
 * This method handles the toggling the state of a while exists event to 
 * reflect the network. 
 *****************************************************************************/
void HandleStateEventsForService(BonjourUserEventsPlugin* plugin, NetBrowserInfo* browser, CFStringRef serviceName, Boolean didAppear)
{
	CFArrayRef events = (CFArrayRef)CFDictionaryGetValue(plugin->_whileServiceExist, browser); // Get the _whileServiceExist events that are interested in this browser.
	CFIndex i;
	CFIndex count;
	
	if (!events)  // Somehow we have a orphan browser...
		return;
	
	count = CFArrayGetCount(events);
	
	// Go thru the events and run filters, notifity if they pass.
	for (i = 0; i < count; ++i)
	{
		CFDictionaryRef eventDict = (CFDictionaryRef)CFArrayGetValueAtIndex(events, i);
		CFStringRef eventServiceName = (CFStringRef)CFDictionaryGetValue(eventDict, sServiceNameKey);
		CFNumberRef token = (CFNumberRef) CFDictionaryGetValue(eventDict, sLaunchdTokenKey);
		
		// Currently we only filter on service name, that makes this as simple as... 
		if (!eventServiceName || CFEqual(serviceName, eventServiceName))
			UserEventAgentSetLaunchEventState(plugin->_pluginContext, token, didAppear);
	}
}

/*****************************************************************************
 * TemporaryEventTimerCallout
 * - 
 * This method is invoked a second after a watched service appears / disappears
 * to toggle the state of the launch event back to false.
 *****************************************************************************/
void TemporaryEventTimerCallout ( CFRunLoopTimerRef timer, void *info )
{
	TimerContextInfo* contextInfo = (TimerContextInfo*)info;

	UserEventAgentSetLaunchEventState(contextInfo->plugin->_pluginContext, contextInfo->token, false);
	
	// Remove from pending timers array.
	CFIndex i;
	CFIndex count = CFArrayGetCount(contextInfo->plugin->_timers);
	
	for (i = 0; i < count; ++i)
	{
		CFRunLoopTimerRef item = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(contextInfo->plugin->_timers, i);
		
		if (item == timer)
			break;
	}
	
	if (i != count)
		CFArrayRemoveValueAtIndex(contextInfo->plugin->_timers, i);
}

#pragma mark -
#pragma mark Convenence
#pragma mark -

/*****************************************************************************
 * CStringFromCFString
 * - 
 * Silly convenence function for dealing with non-critical CFSTR -> cStr
 * conversions.
 *****************************************************************************/

const char* CStringFromCFString(CFStringRef string)
{
	const char* defaultString = "??????";
	const char* cstring;

	if (!string)
		return defaultString;
	
	cstring = CFStringGetCStringPtr(string, kCFStringEncodingUTF8);
	
	return (cstring) ? cstring : defaultString;
	
}

#pragma mark -
#pragma mark TimerContextInfo "Object"
#pragma mark -

/*****************************************************************************
 * TimerContextInfoCreate
 * - 
 * Convenence for creating TimerContextInfo pseudo-objects
 *****************************************************************************/
TimerContextInfo* TimerContextInfoCreate(BonjourUserEventsPlugin* plugin, CFNumberRef token)
{
	TimerContextInfo* info = malloc(sizeof(TimerContextInfo));
	
	info->refCount = 1;
	info->plugin = plugin;
	info->token = (CFNumberRef)CFRetain(token);
	
	return info;
}

/*****************************************************************************
 * TimerContextInfoRetain
 * - 
 * Convenence for retaining TimerContextInfo pseudo-objects
 *****************************************************************************/
const void* TimerContextInfoRetain(const void* info)
{
	TimerContextInfo* context = (TimerContextInfo*)info;
	
	if (!context)
		return NULL;
	
	++context->refCount;
	
	return context;
}

/*****************************************************************************
 * TimerContextInfoRelease
 * - 
 * Convenence for releasing TimerContextInfo pseudo-objects
 *****************************************************************************/
void TimerContextInfoRelease(const void* info)
{
	TimerContextInfo* context = (TimerContextInfo*)info;
	
	if (!context)
		return;
	
	if (context->refCount == 1)
	{
		CFRelease(context->token);
		free(context);
		return;
	}
	else 
	{
		--context->refCount;
	}
}

/*****************************************************************************
 * TimerContextInfoCopyDescription
 * - 
 * This method actually does nothing, but is just a stub so CF is happy.
 *****************************************************************************/
CFStringRef TimerContextInfoCopyDescription(const void* info)
{
	(void)info;
	return CFStringCreateWithCString(NULL, "TimerContextInfo: No useful description", kCFStringEncodingUTF8);
}


#pragma mark -
#pragma mark NetBrowserInfo "Object"
#pragma mark -
/*****************************************************************************
 * NetBrowserInfoCreate
 * - 
 * The method creates a NetBrowserInfo Object and initalizes it.
 *****************************************************************************/
NetBrowserInfo* NetBrowserInfoCreate(CFStringRef serviceType, CFStringRef domain, void* context)
{
	NetBrowserInfo* outObj = NULL;
	DNSServiceRef browserRef = NULL;
	char* cServiceType = NULL;
	char* cDomain = NULL;
	Boolean success = true;
	
	CFIndex serviceSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(serviceType), kCFStringEncodingUTF8);
	cServiceType = calloc(serviceSize, 1);
	success = CFStringGetCString(serviceType, cServiceType, serviceSize, kCFStringEncodingUTF8);
	
	if (domain)
	{
		CFIndex domainSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(domain), kCFStringEncodingUTF8);
		cDomain = calloc(serviceSize, 1);
		success = success && CFStringGetCString(domain, cDomain, domainSize, kCFStringEncodingUTF8);
	}
	
	if (!success)
	{
		fprintf(stderr, "LaunchEvent has badly encoded service type or domain.\n");
		free(cServiceType);

		if (cDomain)
			free(cDomain);
		
		return NULL;
	}
	
	DNSServiceErrorType err = DNSServiceBrowse(&browserRef, 0, 0, cServiceType, cDomain, ServiceBrowserCallback, context);

	if (err != kDNSServiceErr_NoError)
	{
		fprintf(stderr, "Failed to create browser for %s, %s\n", cServiceType, cDomain);
		free(cServiceType);
		
		if (cDomain)
			free(cDomain);
		
		return NULL;
	}

	DNSServiceSetDispatchQueue(browserRef, dispatch_get_main_queue());
	
	
	outObj = malloc(sizeof(NetBrowserInfo));
	
	outObj->refCount = 1;
	outObj->browserRef = browserRef;

	free(cServiceType);
								  
	if (cDomain)
		free(cDomain);
	
	return outObj;
}

/*****************************************************************************
 * NetBrowserInfoRetain
 * - 
 * The method retains a NetBrowserInfo object.
 *****************************************************************************/
const void* NetBrowserInfoRetain(CFAllocatorRef allocator, const void* info)
{
	(void)allocator;
	NetBrowserInfo* obj = (NetBrowserInfo*)info;
	
	if (!obj) 
		return NULL;
	
	++obj->refCount;
	
	return obj;
}

/*****************************************************************************
 * NetBrowserInfoRelease
 * - 
 * The method releases a NetBrowserInfo object.
 *****************************************************************************/
void NetBrowserInfoRelease(CFAllocatorRef allocator, const void* info)
{
	(void)allocator;
	NetBrowserInfo* obj = (NetBrowserInfo*)info;
	
	if (!obj)
		return;
	
	if (obj->refCount == 1)
	{
		DNSServiceRefDeallocate(obj->browserRef);
		free(obj);
	}
	else 
	{
		--obj->refCount;
	}

}

/*****************************************************************************
 * NetBrowserInfoEqual
 * - 
 * The method is used to compare two NetBrowserInfo objects for equality.
 *****************************************************************************/
Boolean	NetBrowserInfoEqual(const void *value1, const void *value2)
{
	NetBrowserInfo* obj1 = (NetBrowserInfo*)value1;
	NetBrowserInfo* obj2 = (NetBrowserInfo*)value2;
	
	if (obj1->browserRef == obj2->browserRef)
		return true;
	
	return false;
}

/*****************************************************************************
 * NetBrowserInfoHash
 * - 
 * The method is used to make a hash for the object. We can cheat and use the 
 * browser pointer.
 *****************************************************************************/
CFHashCode	NetBrowserInfoHash(const void *value)
{
	return (CFHashCode)((NetBrowserInfo*)value)->browserRef;
}


/*****************************************************************************
 * NetBrowserInfoCopyDescription
 * - 
 * Make CF happy.
 *****************************************************************************/
CFStringRef	NetBrowserInfoCopyDescription(const void *value)
{
	(void)value;
	return CFStringCreateWithCString(NULL, "NetBrowserInfo: No useful description", kCFStringEncodingUTF8);
}