/* -*- 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 #include #include #include #include #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); }