Bonjour/MYBonjourService.m
author Jens Alfke <jens@mooseyard.com>
Tue Apr 28 10:36:28 2009 -0700 (2009-04-28)
changeset 29 59689fbdcf77
parent 26 cb9cdf247239
child 31 1d6924779df7
permissions -rw-r--r--
Fixed two CF memory leaks. (Fixes issue #5)
jens@26
     1
//
jens@26
     2
//  MYBonjourService.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 "MYBonjourService.h"
jens@28
    10
#import "MYBonjourQuery.h"
jens@28
    11
#import "MYAddressLookup.h"
jens@26
    12
#import "IPAddress.h"
jens@26
    13
#import "ConcurrentOperation.h"
jens@26
    14
#import "Test.h"
jens@26
    15
#import "Logging.h"
jens@28
    16
#import "ExceptionUtils.h"
jens@28
    17
#import <dns_sd.h>
jens@26
    18
jens@26
    19
jens@26
    20
NSString* const kBonjourServiceResolvedAddressesNotification = @"BonjourServiceResolvedAddresses";
jens@26
    21
jens@26
    22
jens@26
    23
@interface MYBonjourService ()
jens@26
    24
@end
jens@26
    25
jens@26
    26
jens@26
    27
@implementation MYBonjourService
jens@26
    28
jens@26
    29
jens@28
    30
- (id) initWithName: (NSString*)serviceName
jens@28
    31
               type: (NSString*)type
jens@28
    32
             domain: (NSString*)domain
jens@28
    33
          interface: (uint32)interfaceIndex
jens@26
    34
{
jens@26
    35
    self = [super init];
jens@26
    36
    if (self != nil) {
jens@28
    37
        _name = [serviceName copy];
jens@28
    38
        _type = [type copy];
jens@28
    39
        _domain = [domain copy];
jens@28
    40
        _interfaceIndex = interfaceIndex;
jens@26
    41
    }
jens@26
    42
    return self;
jens@26
    43
}
jens@26
    44
jens@28
    45
- (void) dealloc {
jens@28
    46
    [_name release];
jens@28
    47
    [_type release];
jens@28
    48
    [_domain release];
jens@28
    49
    [_hostname release];
jens@28
    50
    [_txtQuery stop];
jens@28
    51
    [_txtQuery release];
jens@28
    52
    [_addressLookup stop];
jens@28
    53
    [_addressLookup release];
jens@26
    54
    [super dealloc];
jens@26
    55
}
jens@26
    56
jens@26
    57
jens@28
    58
@synthesize name=_name, type=_type, domain=_domain, interfaceIndex=_interfaceIndex;
jens@28
    59
jens@28
    60
jens@28
    61
- (NSString*) description {
jens@28
    62
    return $sprintf(@"%@['%@'.%@%@]", self.class,_name,_type,_domain);
jens@26
    63
}
jens@26
    64
jens@26
    65
jens@28
    66
- (NSComparisonResult) compare: (id)obj {
jens@28
    67
    return [_name caseInsensitiveCompare: [obj name]];
jens@26
    68
}
jens@26
    69
jens@28
    70
- (BOOL) isEqual: (id)obj {
jens@28
    71
    if ([obj isKindOfClass: [MYBonjourService class]]) {
jens@28
    72
        MYBonjourService *service = obj;
jens@28
    73
        return [_name caseInsensitiveCompare: [service name]] == 0
jens@28
    74
            && $equal(_type, service->_type)
jens@28
    75
            && $equal(_domain, service->_domain)
jens@28
    76
            && _interfaceIndex == service->_interfaceIndex;
jens@28
    77
    } else {
jens@28
    78
        return NO;
jens@28
    79
    }
jens@26
    80
}
jens@26
    81
jens@28
    82
- (NSUInteger) hash {
jens@28
    83
    return _name.hash ^ _type.hash ^ _domain.hash;
jens@28
    84
}
jens@28
    85
jens@28
    86
jens@28
    87
- (void) added {
jens@28
    88
    LogTo(Bonjour,@"Added %@",self);
jens@28
    89
}
jens@28
    90
jens@28
    91
- (void) removed {
jens@28
    92
    LogTo(Bonjour,@"Removed %@",self);
jens@28
    93
    [self stop];
jens@26
    94
    
jens@28
    95
    [_txtQuery stop];
jens@28
    96
    [_txtQuery release];
jens@28
    97
    _txtQuery = nil;
jens@28
    98
    
jens@28
    99
    [_addressLookup stop];
jens@28
   100
}
jens@28
   101
jens@28
   102
jens@28
   103
- (void) priv_finishResolve {
jens@28
   104
    // If I haven't finished my resolve yet, run it synchronously now so I can return a valid value:
jens@28
   105
    if (!_startedResolve )
jens@28
   106
        [self start];
jens@28
   107
    if (self.serviceRef)
jens@28
   108
        [self waitForReply];
jens@28
   109
}    
jens@28
   110
jens@28
   111
- (NSString*) fullName {
jens@28
   112
    if (!_fullName) [self priv_finishResolve];
jens@28
   113
    return _fullName;
jens@28
   114
}
jens@28
   115
jens@28
   116
- (NSString*) hostname {
jens@28
   117
    if (!_hostname) [self priv_finishResolve];
jens@28
   118
    return _hostname;
jens@28
   119
}
jens@28
   120
jens@28
   121
- (UInt16) port {
jens@28
   122
    if (!_port) [self priv_finishResolve];
jens@28
   123
    return _port;
jens@26
   124
}
jens@26
   125
jens@26
   126
jens@26
   127
#pragma mark -
jens@26
   128
#pragma mark TXT RECORD:
jens@26
   129
jens@26
   130
jens@28
   131
- (NSDictionary*) txtRecord {
jens@28
   132
    // If I haven't started my resolve yet, start it now. (_txtRecord will be nil till it finishes.)
jens@28
   133
    if (!_startedResolve)
jens@28
   134
        [self start];
jens@26
   135
    return _txtRecord;
jens@26
   136
}
jens@26
   137
jens@28
   138
- (void) txtRecordChanged {
jens@26
   139
    // no-op (this is here for subclassers to override)
jens@26
   140
}
jens@26
   141
jens@28
   142
- (NSString*) txtStringForKey: (NSString*)key {
jens@26
   143
    NSData *value = [self.txtRecord objectForKey: key];
jens@26
   144
    if( ! value )
jens@26
   145
        return nil;
jens@26
   146
    if( ! [value isKindOfClass: [NSData class]] ) {
jens@26
   147
        Warn(@"TXT dictionary has unexpected value type: %@",value.class);
jens@26
   148
        return nil;
jens@26
   149
    }
jens@26
   150
    NSString *str = [[NSString alloc] initWithData: value encoding: NSUTF8StringEncoding];
jens@26
   151
    if( ! str )
jens@26
   152
        str = [[NSString alloc] initWithData: value encoding: NSWindowsCP1252StringEncoding];
jens@26
   153
    return [str autorelease];
jens@26
   154
}
jens@26
   155
jens@28
   156
- (void) setTxtData: (NSData*)txtData {
jens@28
   157
    NSDictionary *txtRecord = txtData ?[NSNetService dictionaryFromTXTRecordData: txtData] :nil;
jens@28
   158
    if (!$equal(txtRecord,_txtRecord)) {
jens@28
   159
        LogTo(Bonjour,@"%@ TXT = %@", self,txtRecord);
jens@26
   160
        [self willChangeValueForKey: @"txtRecord"];
jens@28
   161
        setObj(&_txtRecord, txtRecord);
jens@26
   162
        [self didChangeValueForKey: @"txtRecord"];
jens@26
   163
        [self txtRecordChanged];
jens@26
   164
    }
jens@26
   165
}
jens@26
   166
jens@26
   167
jens@28
   168
- (void) queryDidUpdate: (MYBonjourQuery*)query {
jens@28
   169
    if (query==_txtQuery)
jens@28
   170
        [self setTxtData: query.recordData];
jens@26
   171
}
jens@26
   172
jens@26
   173
jens@28
   174
#pragma mark -
jens@28
   175
#pragma mark FULLNAME/HOSTNAME/PORT RESOLUTION:
jens@28
   176
jens@28
   177
jens@28
   178
- (void) priv_resolvedFullName: (NSString*)fullName
jens@28
   179
                      hostname: (NSString*)hostname
jens@28
   180
                          port: (uint16_t)port
jens@28
   181
                     txtRecord: (NSData*)txtData
jens@26
   182
{
jens@28
   183
    LogTo(Bonjour, @"%@: fullname='%@', hostname=%@, port=%u, txt=%u bytes", 
jens@28
   184
          self, fullName, hostname, port, txtData.length);
jens@28
   185
jens@28
   186
    // Don't call a setter method to set these properties: the getters are synchronous, so
jens@28
   187
    // I might already be blocked in a call to one of them, in which case creating a KV
jens@28
   188
    // notification could cause trouble...
jens@28
   189
    _fullName = fullName.copy;
jens@28
   190
    _hostname = hostname.copy;
jens@28
   191
    _port = port;
jens@28
   192
    
jens@28
   193
    // TXT getter is async, though, so I can use a setter to announce the data's availability:
jens@28
   194
    [self setTxtData: txtData];
jens@28
   195
    
jens@28
   196
    // Now that I know my full name, I can start a persistent query to track the TXT:
jens@28
   197
    _txtQuery = [[MYBonjourQuery alloc] initWithBonjourService: self 
jens@28
   198
                                                    recordType: kDNSServiceType_TXT];
jens@28
   199
    _txtQuery.continuous = YES;
jens@28
   200
    [_txtQuery start];
jens@26
   201
}
jens@26
   202
jens@28
   203
jens@28
   204
static void resolveCallback(DNSServiceRef                       sdRef,
jens@28
   205
                            DNSServiceFlags                     flags,
jens@28
   206
                            uint32_t                            interfaceIndex,
jens@28
   207
                            DNSServiceErrorType                 errorCode,
jens@28
   208
                            const char                          *fullname,
jens@28
   209
                            const char                          *hosttarget,
jens@28
   210
                            uint16_t                            port,
jens@28
   211
                            uint16_t                            txtLen,
jens@28
   212
                            const unsigned char                 *txtRecord,
jens@28
   213
                            void                                *context)
jens@26
   214
{
jens@28
   215
    MYBonjourService *service = context;
jens@28
   216
    @try{
jens@28
   217
        //LogTo(Bonjour, @"resolveCallback for %@ (err=%i)", service,errorCode);
jens@28
   218
        if (errorCode) {
jens@28
   219
            [service setError: errorCode];
jens@28
   220
        } else {
jens@28
   221
            NSData *txtData = nil;
jens@28
   222
            if (txtRecord)
jens@28
   223
                txtData = [NSData dataWithBytes: txtRecord length: txtLen];
jens@28
   224
            [service priv_resolvedFullName: [NSString stringWithUTF8String: fullname]
jens@28
   225
                                  hostname: [NSString stringWithUTF8String: hosttarget]
jens@28
   226
                                      port: ntohs(port)
jens@28
   227
                                 txtRecord: txtData];
jens@28
   228
        }
jens@28
   229
    }catchAndReport(@"MYBonjourResolver query callback");
jens@26
   230
}
jens@26
   231
jens@26
   232
jens@28
   233
- (DNSServiceRef) createServiceRef {
jens@28
   234
    _startedResolve = YES;
jens@28
   235
    DNSServiceRef serviceRef = NULL;
jens@28
   236
    self.error = DNSServiceResolve(&serviceRef, 0,
jens@28
   237
                                   _interfaceIndex, 
jens@28
   238
                                   _name.UTF8String, _type.UTF8String, _domain.UTF8String,
jens@28
   239
                                   &resolveCallback, self);
jens@28
   240
    return serviceRef;
jens@26
   241
}
jens@26
   242
jens@26
   243
jens@28
   244
- (MYAddressLookup*) addressLookup {
jens@28
   245
    if (!_addressLookup) {
jens@28
   246
        // Create the lookup the first time this is called:
jens@28
   247
        _addressLookup = [[MYAddressLookup alloc] initWithHostname: self.hostname];
jens@28
   248
        _addressLookup.port = _port;
jens@28
   249
        _addressLookup.interfaceIndex = _interfaceIndex;
jens@26
   250
    }
jens@28
   251
    // (Re)start the lookup if it's expired:
jens@28
   252
    if (_addressLookup && _addressLookup.timeToLive <= 0.0)
jens@28
   253
        [_addressLookup start];
jens@28
   254
    return _addressLookup;
jens@26
   255
}
jens@26
   256
jens@26
   257
jens@28
   258
- (MYBonjourQuery*) queryForRecord: (UInt16)recordType {
jens@28
   259
    MYBonjourQuery *query = [[[MYBonjourQuery alloc] initWithBonjourService: self recordType: recordType]
jens@28
   260
                                 autorelease];
jens@28
   261
    return [query start] ?query :nil;
jens@26
   262
}
jens@26
   263
jens@26
   264
jens@26
   265
@end
jens@26
   266
jens@26
   267
jens@26
   268
jens@26
   269
/*
jens@26
   270
 Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@26
   271
 
jens@26
   272
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@26
   273
 provided that the following conditions are met:
jens@26
   274
 
jens@26
   275
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@26
   276
 and the following disclaimer.
jens@26
   277
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@26
   278
 and the following disclaimer in the documentation and/or other materials provided with the
jens@26
   279
 distribution.
jens@26
   280
 
jens@26
   281
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@26
   282
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@26
   283
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@26
   284
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@26
   285
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@26
   286
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@26
   287
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@26
   288
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@26
   289
 */