Bonjour/MYBonjourBrowser.m
author Jens Alfke <jens@mooseyard.com>
Tue Jul 21 10:04:10 2009 -0700 (2009-07-21)
changeset 61 981f9d604c88
parent 50 63baa74c903f
permissions -rw-r--r--
Prevent crash if MYBonjourQuery is released during response handling
     1 //
     2 //  MYBonjourBrowser.m
     3 //  MYNetwork
     4 //
     5 //  Created by Jens Alfke on 1/22/08.
     6 //  Copyright 2008 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "MYBonjourBrowser.h"
    10 #import "MYBonjourService.h"
    11 #import "MYBonjourRegistration.h"
    12 #import "ExceptionUtils.h"
    13 #import "Test.h"
    14 #import "Logging.h"
    15 #import <dns_sd.h>
    16 
    17 
    18 static void browseCallback (DNSServiceRef                       sdRef,
    19                             DNSServiceFlags                     flags,
    20                             uint32_t                            interfaceIndex,
    21                             DNSServiceErrorType                 errorCode,
    22                             const char                          *serviceName,
    23                             const char                          *regtype,
    24                             const char                          *replyDomain,
    25                             void                                *context);
    26 
    27 @interface MYBonjourBrowser ()
    28 @property BOOL browsing;
    29 - (void) _updateServiceList;
    30 @end
    31 
    32 
    33 @implementation MYBonjourBrowser
    34 
    35 
    36 - (id) initWithServiceType: (NSString*)serviceType
    37 {
    38     Assert(serviceType);
    39     self = [super init];
    40     if (self != nil) {
    41         self.continuous = YES;
    42         _serviceType = [serviceType copy];
    43         _services = [[NSMutableSet alloc] init];
    44         _addServices = [[NSMutableSet alloc] init];
    45         _rmvServices = [[NSMutableSet alloc] init];
    46         _serviceClass = [MYBonjourService class];
    47     }
    48     return self;
    49 }
    50 
    51 
    52 - (void) dealloc
    53 {
    54     LogTo(Bonjour,@"DEALLOC MYBonjourBrowser");
    55     [_myRegistration cancel];
    56     [_myRegistration release];
    57     [_serviceType release];
    58     [_services release];
    59     [_addServices release];
    60     [_rmvServices release];
    61     [super dealloc];
    62 }
    63 
    64 
    65 @synthesize delegate=_delegate, browsing=_browsing, services=_services, serviceClass=_serviceClass;
    66 
    67 
    68 - (NSString*) description
    69 {
    70     return $sprintf(@"%@[%@]", self.class,_serviceType);
    71 }
    72 
    73 
    74 - (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
    75     return DNSServiceBrowse(sdRefPtr,
    76                             kDNSServiceFlagsShareConnection, 
    77                             0,
    78                             _serviceType.UTF8String, NULL,
    79                             &browseCallback, self);
    80 }
    81 
    82 
    83 - (void) priv_gotError: (DNSServiceErrorType)errorCode {
    84     LogTo(Bonjour,@"%@ got error %i", self,errorCode);
    85     self.error = errorCode;
    86 }
    87 
    88 - (void) priv_gotServiceName: (NSString*)serviceName
    89                         type: (NSString*)regtype
    90                       domain: (NSString*)domain
    91                    interface: (uint32_t)interfaceIndex
    92                        flags: (DNSServiceFlags)flags
    93 {
    94     // Create (or reuse existing) MYBonjourService object:
    95     MYBonjourService *service = [[_serviceClass alloc] initWithBrowser: self
    96                                                                   name: serviceName
    97                                                                   type: regtype
    98                                                                 domain: domain
    99                                                              interface: interfaceIndex];
   100     if ([_myRegistration isSameAsService: service]) {
   101         // This is an echo of my own registration, so ignore it
   102         LogTo(Bonjour,@"%@ ignoring echo %@", self,service);
   103         [service release];
   104         return;
   105     }
   106     MYBonjourService *existingService = [_services member: service];
   107     if( existingService ) {
   108         // Use existing service object instead of creating a new one
   109         [service release];
   110         service = [existingService retain];
   111     }
   112     
   113     // Add it to the add/remove sets:
   114     NSMutableSet *addTo, *removeFrom;
   115     if (flags & kDNSServiceFlagsAdd) {
   116         addTo = _addServices;
   117         removeFrom = _rmvServices;
   118     } else {
   119         addTo = _rmvServices;
   120         removeFrom = _addServices;
   121     }
   122     if( [removeFrom containsObject: service] )
   123         [removeFrom removeObject: service];
   124     else
   125         [addTo addObject: service];
   126     [service release];
   127     
   128     // Schedule a (single) call to _updateServiceList:
   129     if (!_pendingUpdate) {
   130         [self performSelector: @selector(_updateServiceList) withObject: nil afterDelay: 0];
   131         _pendingUpdate = YES;
   132     }
   133 }
   134 
   135 
   136 - (void) _updateServiceList
   137 {
   138     _pendingUpdate = NO;
   139     if( _rmvServices.count ) {
   140         [self willChangeValueForKey: @"services" 
   141                     withSetMutation: NSKeyValueMinusSetMutation
   142                        usingObjects: _rmvServices];
   143         [_services minusSet: _rmvServices];
   144         [self didChangeValueForKey: @"services" 
   145                    withSetMutation: NSKeyValueMinusSetMutation
   146                       usingObjects: _rmvServices];
   147         [_rmvServices makeObjectsPerformSelector: @selector(removed)];
   148         [_rmvServices removeAllObjects];
   149     }
   150     if( _addServices.count ) {
   151         [_addServices makeObjectsPerformSelector: @selector(added)];
   152         [self willChangeValueForKey: @"services" 
   153                     withSetMutation: NSKeyValueUnionSetMutation
   154                        usingObjects: _addServices];
   155         [_services unionSet: _addServices];
   156         [self didChangeValueForKey: @"services" 
   157                    withSetMutation: NSKeyValueUnionSetMutation
   158                       usingObjects: _addServices];
   159         [_addServices removeAllObjects];
   160     }
   161 }
   162 
   163 
   164 static void browseCallback (DNSServiceRef        sdRef,
   165                             DNSServiceFlags      flags,
   166                             uint32_t             interfaceIndex,
   167                             DNSServiceErrorType  errorCode,
   168                             const char           *serviceName,
   169                             const char           *regtype,
   170                             const char           *replyDomain,
   171                             void                 *context)
   172 {
   173     MYBonjourBrowser *browser = context;
   174     @try{
   175         LogTo(Bonjour,@"browseCallback (error=%i, name='%s', intf=%u)", errorCode,serviceName,interfaceIndex);
   176         if (!errorCode)
   177             [browser priv_gotServiceName: [NSString stringWithUTF8String: serviceName]
   178                                     type: [NSString stringWithUTF8String: regtype]
   179                                   domain: [NSString stringWithUTF8String: replyDomain]
   180                                interface: interfaceIndex
   181                                    flags: flags];
   182     }catchAndReport(@"Bonjour");
   183     [browser gotResponse: errorCode];
   184 }
   185 
   186 
   187 - (void) cancel {
   188     [_myRegistration stop];
   189     [super cancel];
   190 }
   191 
   192 
   193 - (MYBonjourRegistration *) myRegistration {
   194     if (!_myRegistration)
   195         _myRegistration = [[MYBonjourRegistration alloc] initWithServiceType: _serviceType port: 0];
   196     return _myRegistration;
   197 }
   198 
   199 
   200 @end
   201 
   202 
   203 
   204 
   205 #pragma mark -
   206 #pragma mark TESTING:
   207 
   208 #if DEBUG
   209 
   210 #import "MYBonjourQuery.h"
   211 #import "MYAddressLookup.h"
   212 
   213 @interface BonjourTester : NSObject
   214 {
   215     MYBonjourBrowser *_browser;
   216 }
   217 @end
   218 
   219 @implementation BonjourTester
   220 
   221 - (id) init
   222 {
   223     self = [super init];
   224     if (self != nil) {
   225         _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_presence._tcp"];
   226         [_browser addObserver: self forKeyPath: @"services" 
   227                       options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew 
   228                       context: NULL];
   229         [_browser addObserver: self forKeyPath: @"browsing" 
   230                       options: NSKeyValueObservingOptionNew
   231                       context: NULL];
   232         [_browser start];
   233         
   234         MYBonjourRegistration *myReg = _browser.myRegistration;
   235         myReg.port = 12346;
   236         Assert([myReg start]);
   237     }
   238     return self;
   239 }
   240 
   241 - (void) dealloc
   242 {
   243     [_browser stop];
   244     [_browser removeObserver: self forKeyPath: @"services"];
   245     [_browser removeObserver: self forKeyPath: @"browsing"];
   246     [_browser release];
   247     [super dealloc];
   248 }
   249 
   250 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
   251 {
   252     Log(@"Observed change in %@: %@",keyPath,change);
   253     if( $equal(keyPath,@"services") ) {
   254         if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) {
   255             NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey];
   256             for( MYBonjourService *service in newServices ) {
   257                 Log(@"##### %@ : at %@:%hu, TXT=%@", 
   258                       service, service.hostname, service.port, service.txtRecord);
   259                 service.addressLookup.continuous = YES;
   260                 [service.addressLookup addObserver: self
   261                                         forKeyPath: @"addresses"
   262                                            options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
   263                                            context: NULL];
   264                 [service queryForRecord: kDNSServiceType_NULL];
   265             }
   266         } else if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeRemoval ) {
   267             NSSet *oldServices = [change objectForKey: NSKeyValueChangeOldKey];
   268             for( MYBonjourService *service in oldServices ) {
   269                 Log(@"##### REMOVED: %@", service);
   270                 [service.addressLookup removeObserver: self forKeyPath: @"addresses"];
   271             }
   272         }
   273     }
   274 }
   275 
   276 @end
   277 
   278 TestCase(Bonjour) {
   279     EnableLogTo(Bonjour,YES);
   280     EnableLogTo(DNS,YES);
   281     [NSRunLoop currentRunLoop]; // create runloop
   282     BonjourTester *tester = [[BonjourTester alloc] init];
   283     [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1500]];
   284     [tester release];
   285 }
   286 
   287 #endif
   288 
   289 
   290 /*
   291  Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   292  
   293  Redistribution and use in source and binary forms, with or without modification, are permitted
   294  provided that the following conditions are met:
   295  
   296  * Redistributions of source code must retain the above copyright notice, this list of conditions
   297  and the following disclaimer.
   298  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   299  and the following disclaimer in the documentation and/or other materials provided with the
   300  distribution.
   301  
   302  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   303  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   304  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   305  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   306  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   307   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   308  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   309  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   310  */