Bonjour/MYBonjourBrowser.m
author morrowa
Fri Jul 03 17:50:28 2009 -0700 (2009-07-03)
changeset 58 6577813acf12
parent 45 8efb48eabd08
child 59 46c7844cb592
permissions -rw-r--r--
Fixed bug which caused PyBLIP to stop sending responses while the connection was closing.
     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 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] initWithName: serviceName
    96                                                                type: regtype
    97                                                              domain: domain
    98                                                           interface: interfaceIndex];
    99     if ([_myRegistration isSameAsService: service]) {
   100         // This is an echo of my own registration, so ignore it
   101         LogTo(Bonjour,@"%@ ignoring echo %@", self,service);
   102         [service release];
   103         return;
   104     }
   105     MYBonjourService *existingService = [_services member: service];
   106     if( existingService ) {
   107         // Use existing service object instead of creating a new one
   108         [service release];
   109         service = [existingService retain];
   110     }
   111     
   112     // Add it to the add/remove sets:
   113     NSMutableSet *addTo, *removeFrom;
   114     if (flags & kDNSServiceFlagsAdd) {
   115         addTo = _addServices;
   116         removeFrom = _rmvServices;
   117     } else {
   118         addTo = _rmvServices;
   119         removeFrom = _addServices;
   120     }
   121     if( [removeFrom containsObject: service] )
   122         [removeFrom removeObject: service];
   123     else
   124         [addTo addObject: service];
   125     [service release];
   126     
   127     // Schedule a (single) call to _updateServiceList:
   128     if (!_pendingUpdate) {
   129         [self performSelector: @selector(_updateServiceList) withObject: nil afterDelay: 0];
   130         _pendingUpdate = YES;
   131     }
   132 }
   133 
   134 
   135 - (void) _updateServiceList
   136 {
   137     _pendingUpdate = NO;
   138     if( _rmvServices.count ) {
   139         [self willChangeValueForKey: @"services" 
   140                     withSetMutation: NSKeyValueMinusSetMutation
   141                        usingObjects: _rmvServices];
   142         [_services minusSet: _rmvServices];
   143         [self didChangeValueForKey: @"services" 
   144                    withSetMutation: NSKeyValueMinusSetMutation
   145                       usingObjects: _rmvServices];
   146         [_rmvServices makeObjectsPerformSelector: @selector(removed)];
   147         [_rmvServices removeAllObjects];
   148     }
   149     if( _addServices.count ) {
   150         [_addServices makeObjectsPerformSelector: @selector(added)];
   151         [self willChangeValueForKey: @"services" 
   152                     withSetMutation: NSKeyValueUnionSetMutation
   153                        usingObjects: _addServices];
   154         [_services unionSet: _addServices];
   155         [self didChangeValueForKey: @"services" 
   156                    withSetMutation: NSKeyValueUnionSetMutation
   157                       usingObjects: _addServices];
   158         [_addServices removeAllObjects];
   159     }
   160 }
   161 
   162 
   163 static void browseCallback (DNSServiceRef        sdRef,
   164                             DNSServiceFlags      flags,
   165                             uint32_t             interfaceIndex,
   166                             DNSServiceErrorType  errorCode,
   167                             const char           *serviceName,
   168                             const char           *regtype,
   169                             const char           *replyDomain,
   170                             void                 *context)
   171 {
   172     MYBonjourBrowser *browser = context;
   173     @try{
   174         LogTo(Bonjour,@"browseCallback (error=%i, name='%s', intf=%u)", errorCode,serviceName,interfaceIndex);
   175         if (!errorCode)
   176             [browser priv_gotServiceName: [NSString stringWithUTF8String: serviceName]
   177                                     type: [NSString stringWithUTF8String: regtype]
   178                                   domain: [NSString stringWithUTF8String: replyDomain]
   179                                interface: interfaceIndex
   180                                    flags: flags];
   181     }catchAndReport(@"Bonjour");
   182     [browser gotResponse: errorCode];
   183 }
   184 
   185 
   186 - (void) cancel {
   187     [_myRegistration stop];
   188     [super cancel];
   189 }
   190 
   191 
   192 - (MYBonjourRegistration *) myRegistration {
   193     if (!_myRegistration)
   194         _myRegistration = [[MYBonjourRegistration alloc] initWithServiceType: _serviceType port: 0];
   195     return _myRegistration;
   196 }
   197 
   198 
   199 @end
   200 
   201 
   202 
   203 
   204 #pragma mark -
   205 #pragma mark TESTING:
   206 
   207 #if DEBUG
   208 
   209 #import "MYBonjourQuery.h"
   210 #import "MYAddressLookup.h"
   211 
   212 @interface BonjourTester : NSObject
   213 {
   214     MYBonjourBrowser *_browser;
   215 }
   216 @end
   217 
   218 @implementation BonjourTester
   219 
   220 - (id) init
   221 {
   222     self = [super init];
   223     if (self != nil) {
   224         _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_presence._tcp"];
   225         [_browser addObserver: self forKeyPath: @"services" 
   226                       options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew 
   227                       context: NULL];
   228         [_browser addObserver: self forKeyPath: @"browsing" 
   229                       options: NSKeyValueObservingOptionNew
   230                       context: NULL];
   231         [_browser start];
   232         
   233         MYBonjourRegistration *myReg = _browser.myRegistration;
   234         myReg.port = 12346;
   235         Assert([myReg start]);
   236     }
   237     return self;
   238 }
   239 
   240 - (void) dealloc
   241 {
   242     [_browser stop];
   243     [_browser removeObserver: self forKeyPath: @"services"];
   244     [_browser removeObserver: self forKeyPath: @"browsing"];
   245     [_browser release];
   246     [super dealloc];
   247 }
   248 
   249 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
   250 {
   251     Log(@"Observed change in %@: %@",keyPath,change);
   252     if( $equal(keyPath,@"services") ) {
   253         if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) {
   254             NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey];
   255             for( MYBonjourService *service in newServices ) {
   256                 Log(@"##### %@ : at %@:%hu, TXT=%@", 
   257                       service, service.hostname, service.port, service.txtRecord);
   258                 service.addressLookup.continuous = YES;
   259                 [service.addressLookup addObserver: self
   260                                         forKeyPath: @"addresses"
   261                                            options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
   262                                            context: NULL];
   263                 [service queryForRecord: kDNSServiceType_NULL];
   264             }
   265         } else if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeRemoval ) {
   266             NSSet *oldServices = [change objectForKey: NSKeyValueChangeOldKey];
   267             for( MYBonjourService *service in oldServices ) {
   268                 Log(@"##### REMOVED: %@", service);
   269                 [service.addressLookup removeObserver: self forKeyPath: @"addresses"];
   270             }
   271         }
   272     }
   273 }
   274 
   275 @end
   276 
   277 TestCase(Bonjour) {
   278     EnableLogTo(Bonjour,YES);
   279     EnableLogTo(DNS,YES);
   280     [NSRunLoop currentRunLoop]; // create runloop
   281     BonjourTester *tester = [[BonjourTester alloc] init];
   282     [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1500]];
   283     [tester release];
   284 }
   285 
   286 #endif
   287 
   288 
   289 /*
   290  Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   291  
   292  Redistribution and use in source and binary forms, with or without modification, are permitted
   293  provided that the following conditions are met:
   294  
   295  * Redistributions of source code must retain the above copyright notice, this list of conditions
   296  and the following disclaimer.
   297  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   298  and the following disclaimer in the documentation and/or other materials provided with the
   299  distribution.
   300  
   301  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   302  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   303  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   304  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   305  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   306   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   307  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   308  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   309  */