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