Bonjour/MYBonjourBrowser.m
author Jens Alfke <jens@mooseyard.com>
Sun May 24 15:03:39 2009 -0700 (2009-05-24)
changeset 49 20cccc7c26ee
parent 31 1d6924779df7
child 50 63baa74c903f
permissions -rw-r--r--
Misc. tweaks made while porting Chatty to use MYNetwork.
* Allow -[BLIPConnection sendRequest:] to re-send an already-sent or received request.
* Allow use of the basic -init method for BLIPConnection.
* Some new convenience factory methods.
* Broke dependencies on Security.framework out into new TCPEndpoint+Certs.m source file, so client apps aren't forced to link against Security.
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@26
    54
    LogTo(Bonjour,@"DEALLOC BonjourBrowser");
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@28
    65
@synthesize 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@28
    95
    MYBonjourService *service = [[_serviceClass alloc] initWithName: serviceName
jens@28
    96
                                                               type: regtype
jens@28
    97
                                                             domain: domain
jens@28
    98
                                                          interface: interfaceIndex];
jens@31
    99
    if ([_myRegistration isSameAsService: service]) {
jens@31
   100
        // This is an echo of my own registration, so ignore it
jens@31
   101
        LogTo(Bonjour,@"%@ ignoring echo %@", self,service);
jens@31
   102
        [service release];
jens@31
   103
        return;
jens@31
   104
    }
jens@28
   105
    MYBonjourService *existingService = [_services member: service];
jens@28
   106
    if( existingService ) {
jens@31
   107
        // Use existing service object instead of creating a new one
jens@28
   108
        [service release];
jens@28
   109
        service = [existingService retain];
jens@28
   110
    }
jens@28
   111
    
jens@28
   112
    // Add it to the add/remove sets:
jens@28
   113
    NSMutableSet *addTo, *removeFrom;
jens@28
   114
    if (flags & kDNSServiceFlagsAdd) {
jens@28
   115
        addTo = _addServices;
jens@28
   116
        removeFrom = _rmvServices;
jens@28
   117
    } else {
jens@28
   118
        addTo = _rmvServices;
jens@28
   119
        removeFrom = _addServices;
jens@28
   120
    }
jens@28
   121
    if( [removeFrom containsObject: service] )
jens@28
   122
        [removeFrom removeObject: service];
jens@28
   123
    else
jens@28
   124
        [addTo addObject: service];
jens@28
   125
    [service release];
jens@28
   126
    
jens@31
   127
    // Schedule a (single) call to _updateServiceList:
jens@31
   128
    if (!_pendingUpdate) {
jens@31
   129
        [self performSelector: @selector(_updateServiceList) withObject: nil afterDelay: 0];
jens@31
   130
        _pendingUpdate = YES;
jens@31
   131
    }
jens@26
   132
}
jens@26
   133
jens@26
   134
jens@26
   135
- (void) _updateServiceList
jens@26
   136
{
jens@31
   137
    _pendingUpdate = NO;
jens@26
   138
    if( _rmvServices.count ) {
jens@26
   139
        [self willChangeValueForKey: @"services" 
jens@26
   140
                    withSetMutation: NSKeyValueMinusSetMutation
jens@26
   141
                       usingObjects: _rmvServices];
jens@26
   142
        [_services minusSet: _rmvServices];
jens@26
   143
        [self didChangeValueForKey: @"services" 
jens@26
   144
                   withSetMutation: NSKeyValueMinusSetMutation
jens@26
   145
                      usingObjects: _rmvServices];
jens@26
   146
        [_rmvServices makeObjectsPerformSelector: @selector(removed)];
jens@26
   147
        [_rmvServices removeAllObjects];
jens@26
   148
    }
jens@26
   149
    if( _addServices.count ) {
jens@26
   150
        [_addServices makeObjectsPerformSelector: @selector(added)];
jens@26
   151
        [self willChangeValueForKey: @"services" 
jens@26
   152
                    withSetMutation: NSKeyValueUnionSetMutation
jens@26
   153
                       usingObjects: _addServices];
jens@26
   154
        [_services unionSet: _addServices];
jens@26
   155
        [self didChangeValueForKey: @"services" 
jens@26
   156
                   withSetMutation: NSKeyValueUnionSetMutation
jens@26
   157
                      usingObjects: _addServices];
jens@26
   158
        [_addServices removeAllObjects];
jens@26
   159
    }
jens@26
   160
}
jens@26
   161
jens@26
   162
jens@28
   163
static void browseCallback (DNSServiceRef                       sdRef,
jens@28
   164
                            DNSServiceFlags                     flags,
jens@28
   165
                            uint32_t                            interfaceIndex,
jens@28
   166
                            DNSServiceErrorType                 errorCode,
jens@28
   167
                            const char                          *serviceName,
jens@28
   168
                            const char                          *regtype,
jens@28
   169
                            const char                          *replyDomain,
jens@28
   170
                            void                                *context)
jens@26
   171
{
jens@31
   172
    MYBonjourBrowser *browser = context;
jens@28
   173
    @try{
jens@28
   174
        //LogTo(Bonjour,@"browseCallback (error=%i, name='%s')", errorCode,serviceName);
jens@31
   175
        if (!errorCode)
jens@31
   176
            [browser priv_gotServiceName: [NSString stringWithUTF8String: serviceName]
jens@31
   177
                                    type: [NSString stringWithUTF8String: regtype]
jens@31
   178
                                  domain: [NSString stringWithUTF8String: replyDomain]
jens@31
   179
                               interface: interfaceIndex
jens@31
   180
                                   flags: flags];
jens@28
   181
    }catchAndReport(@"Bonjour");
jens@31
   182
    [browser gotResponse: errorCode];
jens@31
   183
}
jens@31
   184
jens@31
   185
jens@31
   186
- (void) cancel {
jens@31
   187
    [_myRegistration stop];
jens@31
   188
    [super cancel];
jens@31
   189
}
jens@31
   190
jens@31
   191
jens@31
   192
- (MYBonjourRegistration *) myRegistration {
jens@31
   193
    if (!_myRegistration)
jens@31
   194
        _myRegistration = [[MYBonjourRegistration alloc] initWithServiceType: _serviceType port: 0];
jens@31
   195
    return _myRegistration;
jens@26
   196
}
jens@26
   197
jens@26
   198
jens@26
   199
@end
jens@26
   200
jens@26
   201
jens@26
   202
jens@45
   203
jens@26
   204
#pragma mark -
jens@26
   205
#pragma mark TESTING:
jens@26
   206
jens@28
   207
#if DEBUG
jens@28
   208
jens@28
   209
#import "MYBonjourQuery.h"
jens@28
   210
#import "MYAddressLookup.h"
jens@28
   211
jens@26
   212
@interface BonjourTester : NSObject
jens@26
   213
{
jens@26
   214
    MYBonjourBrowser *_browser;
jens@26
   215
}
jens@26
   216
@end
jens@26
   217
jens@26
   218
@implementation BonjourTester
jens@26
   219
jens@26
   220
- (id) init
jens@26
   221
{
jens@26
   222
    self = [super init];
jens@26
   223
    if (self != nil) {
jens@28
   224
        _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_presence._tcp"];
jens@45
   225
        [_browser addObserver: self forKeyPath: @"services" 
jens@45
   226
                      options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew 
jens@45
   227
                      context: NULL];
jens@45
   228
        [_browser addObserver: self forKeyPath: @"browsing" 
jens@45
   229
                      options: NSKeyValueObservingOptionNew
jens@45
   230
                      context: NULL];
jens@26
   231
        [_browser start];
jens@31
   232
        
jens@31
   233
        MYBonjourRegistration *myReg = _browser.myRegistration;
jens@31
   234
        myReg.port = 12346;
jens@31
   235
        Assert([myReg start]);
jens@26
   236
    }
jens@26
   237
    return self;
jens@26
   238
}
jens@26
   239
jens@26
   240
- (void) dealloc
jens@26
   241
{
jens@26
   242
    [_browser stop];
jens@26
   243
    [_browser removeObserver: self forKeyPath: @"services"];
jens@26
   244
    [_browser removeObserver: self forKeyPath: @"browsing"];
jens@26
   245
    [_browser release];
jens@26
   246
    [super dealloc];
jens@26
   247
}
jens@26
   248
jens@26
   249
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
jens@26
   250
{
jens@45
   251
    Log(@"Observed change in %@: %@",keyPath,change);
jens@26
   252
    if( $equal(keyPath,@"services") ) {
jens@26
   253
        if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) {
jens@26
   254
            NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey];
jens@26
   255
            for( MYBonjourService *service in newServices ) {
jens@28
   256
                NSString *hostname = service.hostname;  // block till it's resolved
jens@28
   257
                Log(@"##### %@ : at %@:%hu, TXT=%@", 
jens@28
   258
                      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
 */