Bonjour/MYBonjourBrowser.m
author Jens Alfke <jens@mooseyard.com>
Wed May 06 09:21:57 2009 -0700 (2009-05-06)
changeset 44 d8a559a39284
parent 28 732576fa8a0d
child 45 8efb48eabd08
permissions -rw-r--r--
* Merged part of Jim Roepke's changes -- the MYAddressLookup fixes and updated iPhone project.
* Changed API of Jim Roepke's TCPListener improvement (made it a settable property, not a method to override.)
* Added more types to .hgignore.
     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 BonjourBrowser");
    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')", errorCode,serviceName);
   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 #pragma mark -
   204 #pragma mark TESTING:
   205 
   206 #if DEBUG
   207 
   208 #import "MYBonjourQuery.h"
   209 #import "MYAddressLookup.h"
   210 
   211 @interface BonjourTester : NSObject
   212 {
   213     MYBonjourBrowser *_browser;
   214 }
   215 @end
   216 
   217 @implementation BonjourTester
   218 
   219 - (id) init
   220 {
   221     self = [super init];
   222     if (self != nil) {
   223         _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_presence._tcp"];
   224         [_browser addObserver: self forKeyPath: @"services" options: NSKeyValueObservingOptionNew context: NULL];
   225         [_browser addObserver: self forKeyPath: @"browsing" options: NSKeyValueObservingOptionNew context: NULL];
   226         [_browser start];
   227         
   228         MYBonjourRegistration *myReg = _browser.myRegistration;
   229         myReg.port = 12346;
   230         Assert([myReg start]);
   231     }
   232     return self;
   233 }
   234 
   235 - (void) dealloc
   236 {
   237     [_browser stop];
   238     [_browser removeObserver: self forKeyPath: @"services"];
   239     [_browser removeObserver: self forKeyPath: @"browsing"];
   240     [_browser release];
   241     [super dealloc];
   242 }
   243 
   244 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
   245 {
   246     LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change);
   247     if( $equal(keyPath,@"services") ) {
   248         if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) {
   249             NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey];
   250             for( MYBonjourService *service in newServices ) {
   251                 NSString *hostname = service.hostname;  // block till it's resolved
   252                 Log(@"##### %@ : at %@:%hu, TXT=%@", 
   253                       service, hostname, service.port, service.txtRecord);
   254                 service.addressLookup.continuous = YES;
   255                 [service queryForRecord: kDNSServiceType_NULL];
   256             }
   257         }
   258     }
   259 }
   260 
   261 @end
   262 
   263 TestCase(Bonjour) {
   264     EnableLogTo(Bonjour,YES);
   265     EnableLogTo(DNS,YES);
   266     [NSRunLoop currentRunLoop]; // create runloop
   267     BonjourTester *tester = [[BonjourTester alloc] init];
   268     [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1500]];
   269     [tester release];
   270 }
   271 
   272 #endif
   273 
   274 
   275 /*
   276  Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   277  
   278  Redistribution and use in source and binary forms, with or without modification, are permitted
   279  provided that the following conditions are met:
   280  
   281  * Redistributions of source code must retain the above copyright notice, this list of conditions
   282  and the following disclaimer.
   283  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   284  and the following disclaimer in the documentation and/or other materials provided with the
   285  distribution.
   286  
   287  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   288  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   289  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   290  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   291  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   292   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   293  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   294  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   295  */