jens@26: // jens@26: // MYBonjourBrowser.m jens@26: // MYNetwork jens@26: // jens@26: // Created by Jens Alfke on 1/22/08. jens@26: // Copyright 2008 Jens Alfke. All rights reserved. jens@26: // jens@26: jens@26: #import "MYBonjourBrowser.h" jens@26: #import "MYBonjourService.h" jens@31: #import "MYBonjourRegistration.h" jens@28: #import "ExceptionUtils.h" jens@26: #import "Test.h" jens@26: #import "Logging.h" jens@28: #import jens@26: jens@26: jens@28: static void browseCallback (DNSServiceRef sdRef, jens@28: DNSServiceFlags flags, jens@28: uint32_t interfaceIndex, jens@28: DNSServiceErrorType errorCode, jens@28: const char *serviceName, jens@28: const char *regtype, jens@28: const char *replyDomain, jens@28: void *context); jens@28: jens@26: @interface MYBonjourBrowser () jens@26: @property BOOL browsing; jens@28: - (void) _updateServiceList; jens@26: @end jens@26: jens@26: jens@26: @implementation MYBonjourBrowser jens@26: jens@26: jens@26: - (id) initWithServiceType: (NSString*)serviceType jens@26: { jens@26: Assert(serviceType); jens@26: self = [super init]; jens@26: if (self != nil) { jens@28: self.continuous = YES; jens@26: _serviceType = [serviceType copy]; jens@26: _services = [[NSMutableSet alloc] init]; jens@26: _addServices = [[NSMutableSet alloc] init]; jens@26: _rmvServices = [[NSMutableSet alloc] init]; jens@26: _serviceClass = [MYBonjourService class]; jens@26: } jens@26: return self; jens@26: } jens@26: jens@26: jens@26: - (void) dealloc jens@26: { jens@50: LogTo(Bonjour,@"DEALLOC MYBonjourBrowser"); jens@31: [_myRegistration cancel]; jens@31: [_myRegistration release]; jens@26: [_serviceType release]; jens@26: [_services release]; jens@26: [_addServices release]; jens@26: [_rmvServices release]; jens@26: [super dealloc]; jens@26: } jens@26: jens@26: jens@28: @synthesize browsing=_browsing, services=_services, serviceClass=_serviceClass; jens@26: jens@26: jens@28: - (NSString*) description jens@26: { jens@28: return $sprintf(@"%@[%@]", self.class,_serviceType); jens@26: } jens@26: jens@28: jens@31: - (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr { jens@31: return DNSServiceBrowse(sdRefPtr, jens@31: kDNSServiceFlagsShareConnection, jens@31: 0, jens@31: _serviceType.UTF8String, NULL, jens@31: &browseCallback, self); jens@26: } jens@26: jens@26: jens@28: - (void) priv_gotError: (DNSServiceErrorType)errorCode { jens@28: LogTo(Bonjour,@"%@ got error %i", self,errorCode); jens@28: self.error = errorCode; jens@26: } jens@26: jens@28: - (void) priv_gotServiceName: (NSString*)serviceName jens@28: type: (NSString*)regtype jens@28: domain: (NSString*)domain jens@28: interface: (uint32_t)interfaceIndex jens@28: flags: (DNSServiceFlags)flags jens@26: { jens@28: // Create (or reuse existing) MYBonjourService object: jens@28: MYBonjourService *service = [[_serviceClass alloc] initWithName: serviceName jens@28: type: regtype jens@28: domain: domain jens@28: interface: interfaceIndex]; jens@31: if ([_myRegistration isSameAsService: service]) { jens@31: // This is an echo of my own registration, so ignore it jens@31: LogTo(Bonjour,@"%@ ignoring echo %@", self,service); jens@31: [service release]; jens@31: return; jens@31: } jens@28: MYBonjourService *existingService = [_services member: service]; jens@28: if( existingService ) { jens@31: // Use existing service object instead of creating a new one jens@28: [service release]; jens@28: service = [existingService retain]; jens@28: } jens@28: jens@28: // Add it to the add/remove sets: jens@28: NSMutableSet *addTo, *removeFrom; jens@28: if (flags & kDNSServiceFlagsAdd) { jens@28: addTo = _addServices; jens@28: removeFrom = _rmvServices; jens@28: } else { jens@28: addTo = _rmvServices; jens@28: removeFrom = _addServices; jens@28: } jens@28: if( [removeFrom containsObject: service] ) jens@28: [removeFrom removeObject: service]; jens@28: else jens@28: [addTo addObject: service]; jens@28: [service release]; jens@28: jens@31: // Schedule a (single) call to _updateServiceList: jens@31: if (!_pendingUpdate) { jens@31: [self performSelector: @selector(_updateServiceList) withObject: nil afterDelay: 0]; jens@31: _pendingUpdate = YES; jens@31: } jens@26: } jens@26: jens@26: jens@26: - (void) _updateServiceList jens@26: { jens@31: _pendingUpdate = NO; jens@26: if( _rmvServices.count ) { jens@26: [self willChangeValueForKey: @"services" jens@26: withSetMutation: NSKeyValueMinusSetMutation jens@26: usingObjects: _rmvServices]; jens@26: [_services minusSet: _rmvServices]; jens@26: [self didChangeValueForKey: @"services" jens@26: withSetMutation: NSKeyValueMinusSetMutation jens@26: usingObjects: _rmvServices]; jens@26: [_rmvServices makeObjectsPerformSelector: @selector(removed)]; jens@26: [_rmvServices removeAllObjects]; jens@26: } jens@26: if( _addServices.count ) { jens@26: [_addServices makeObjectsPerformSelector: @selector(added)]; jens@26: [self willChangeValueForKey: @"services" jens@26: withSetMutation: NSKeyValueUnionSetMutation jens@26: usingObjects: _addServices]; jens@26: [_services unionSet: _addServices]; jens@26: [self didChangeValueForKey: @"services" jens@26: withSetMutation: NSKeyValueUnionSetMutation jens@26: usingObjects: _addServices]; jens@26: [_addServices removeAllObjects]; jens@26: } jens@26: } jens@26: jens@26: jens@50: static void browseCallback (DNSServiceRef sdRef, jens@50: DNSServiceFlags flags, jens@50: uint32_t interfaceIndex, jens@50: DNSServiceErrorType errorCode, jens@50: const char *serviceName, jens@50: const char *regtype, jens@50: const char *replyDomain, jens@50: void *context) jens@26: { jens@31: MYBonjourBrowser *browser = context; jens@28: @try{ jens@50: LogTo(Bonjour,@"browseCallback (error=%i, name='%s', intf=%u)", errorCode,serviceName,interfaceIndex); jens@31: if (!errorCode) jens@31: [browser priv_gotServiceName: [NSString stringWithUTF8String: serviceName] jens@31: type: [NSString stringWithUTF8String: regtype] jens@31: domain: [NSString stringWithUTF8String: replyDomain] jens@31: interface: interfaceIndex jens@31: flags: flags]; jens@28: }catchAndReport(@"Bonjour"); jens@31: [browser gotResponse: errorCode]; jens@31: } jens@31: jens@31: jens@31: - (void) cancel { jens@31: [_myRegistration stop]; jens@31: [super cancel]; jens@31: } jens@31: jens@31: jens@31: - (MYBonjourRegistration *) myRegistration { jens@31: if (!_myRegistration) jens@31: _myRegistration = [[MYBonjourRegistration alloc] initWithServiceType: _serviceType port: 0]; jens@31: return _myRegistration; jens@26: } jens@26: jens@26: jens@26: @end jens@26: jens@26: jens@26: jens@45: jens@26: #pragma mark - jens@26: #pragma mark TESTING: jens@26: jens@28: #if DEBUG jens@28: jens@28: #import "MYBonjourQuery.h" jens@28: #import "MYAddressLookup.h" jens@28: jens@26: @interface BonjourTester : NSObject jens@26: { jens@26: MYBonjourBrowser *_browser; jens@26: } jens@26: @end jens@26: jens@26: @implementation BonjourTester jens@26: jens@26: - (id) init jens@26: { jens@26: self = [super init]; jens@26: if (self != nil) { jens@28: _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_presence._tcp"]; jens@45: [_browser addObserver: self forKeyPath: @"services" jens@45: options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew jens@45: context: NULL]; jens@45: [_browser addObserver: self forKeyPath: @"browsing" jens@45: options: NSKeyValueObservingOptionNew jens@45: context: NULL]; jens@26: [_browser start]; jens@31: jens@31: MYBonjourRegistration *myReg = _browser.myRegistration; jens@31: myReg.port = 12346; jens@31: Assert([myReg start]); jens@26: } jens@26: return self; jens@26: } jens@26: jens@26: - (void) dealloc jens@26: { jens@26: [_browser stop]; jens@26: [_browser removeObserver: self forKeyPath: @"services"]; jens@26: [_browser removeObserver: self forKeyPath: @"browsing"]; jens@26: [_browser release]; jens@26: [super dealloc]; jens@26: } jens@26: jens@26: - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context jens@26: { jens@45: Log(@"Observed change in %@: %@",keyPath,change); jens@26: if( $equal(keyPath,@"services") ) { jens@26: if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) { jens@26: NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey]; jens@26: for( MYBonjourService *service in newServices ) { jens@28: Log(@"##### %@ : at %@:%hu, TXT=%@", jens@50: service, service.hostname, service.port, service.txtRecord); jens@28: service.addressLookup.continuous = YES; jens@45: [service.addressLookup addObserver: self jens@45: forKeyPath: @"addresses" jens@45: options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew jens@45: context: NULL]; jens@28: [service queryForRecord: kDNSServiceType_NULL]; jens@26: } jens@45: } else if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeRemoval ) { jens@45: NSSet *oldServices = [change objectForKey: NSKeyValueChangeOldKey]; jens@45: for( MYBonjourService *service in oldServices ) { jens@45: Log(@"##### REMOVED: %@", service); jens@45: [service.addressLookup removeObserver: self forKeyPath: @"addresses"]; jens@45: } jens@26: } jens@26: } jens@26: } jens@26: jens@26: @end jens@26: jens@26: TestCase(Bonjour) { jens@28: EnableLogTo(Bonjour,YES); jens@28: EnableLogTo(DNS,YES); jens@26: [NSRunLoop currentRunLoop]; // create runloop jens@26: BonjourTester *tester = [[BonjourTester alloc] init]; jens@28: [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1500]]; jens@26: [tester release]; jens@26: } jens@26: jens@28: #endif jens@26: jens@26: jens@26: /* jens@26: Copyright (c) 2008-2009, Jens Alfke . All rights reserved. jens@26: jens@26: Redistribution and use in source and binary forms, with or without modification, are permitted jens@26: provided that the following conditions are met: jens@26: jens@26: * Redistributions of source code must retain the above copyright notice, this list of conditions jens@26: and the following disclaimer. jens@26: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions jens@26: and the following disclaimer in the documentation and/or other materials provided with the jens@26: distribution. jens@26: jens@26: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR jens@26: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND jens@26: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- jens@26: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES jens@26: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR jens@26: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN jens@26: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF jens@26: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jens@26: */