Bonjour/MYBonjourService.m
author Jens Alfke <jens@mooseyard.com>
Wed Jul 01 14:14:32 2009 -0700 (2009-07-01)
changeset 50 63baa74c903f
parent 44 d8a559a39284
child 59 46c7844cb592
permissions -rw-r--r--
Fix to BLIPMessage for Chatty (mark new outgoing BLIPMessages as "complete".)
Lots of fixes for Bonjour stuff, including making the hostname lookup asynchronous in BonjourService.
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@50
    24
@property (copy) NSString *hostname;
jens@50
    25
@property UInt16 port;
jens@26
    26
@end
jens@26
    27
jens@26
    28
jens@26
    29
@implementation MYBonjourService
jens@26
    30
jens@26
    31
jens@28
    32
- (id) initWithName: (NSString*)serviceName
jens@28
    33
               type: (NSString*)type
jens@28
    34
             domain: (NSString*)domain
jens@31
    35
          interface: (UInt32)interfaceIndex
jens@26
    36
{
jens@31
    37
    Assert(serviceName);
jens@31
    38
    Assert(type);
jens@31
    39
    Assert(domain);
jens@26
    40
    self = [super init];
jens@26
    41
    if (self != nil) {
jens@28
    42
        _name = [serviceName copy];
jens@28
    43
        _type = [type copy];
jens@28
    44
        _domain = [domain copy];
jens@31
    45
        _fullName = [[[self class] fullNameOfService: _name ofType: _type inDomain: _domain] retain];
jens@28
    46
        _interfaceIndex = interfaceIndex;
jens@26
    47
    }
jens@26
    48
    return self;
jens@26
    49
}
jens@26
    50
jens@28
    51
- (void) dealloc {
jens@28
    52
    [_txtQuery stop];
jens@28
    53
    [_txtQuery release];
jens@28
    54
    [_addressLookup stop];
jens@28
    55
    [_addressLookup release];
jens@31
    56
    [_name release];
jens@31
    57
    [_type release];
jens@31
    58
    [_domain release];
jens@31
    59
    [_fullName release];
jens@31
    60
    [_hostname release];
jens@26
    61
    [super dealloc];
jens@26
    62
}
jens@26
    63
jens@26
    64
jens@50
    65
@synthesize name=_name, type=_type, domain=_domain, fullName=_fullName,
jens@50
    66
            hostname=_hostname, port=_port, interfaceIndex=_interfaceIndex;
jens@28
    67
jens@28
    68
jens@28
    69
- (NSString*) description {
jens@31
    70
    return $sprintf(@"%@[%@]", self.class,self.fullName);
jens@26
    71
}
jens@26
    72
jens@26
    73
jens@28
    74
- (NSComparisonResult) compare: (id)obj {
jens@28
    75
    return [_name caseInsensitiveCompare: [obj name]];
jens@26
    76
}
jens@26
    77
jens@28
    78
- (BOOL) isEqual: (id)obj {
jens@28
    79
    if ([obj isKindOfClass: [MYBonjourService class]]) {
jens@28
    80
        MYBonjourService *service = obj;
jens@28
    81
        return [_name caseInsensitiveCompare: [service name]] == 0
jens@28
    82
            && $equal(_type, service->_type)
jens@28
    83
            && $equal(_domain, service->_domain)
jens@28
    84
            && _interfaceIndex == service->_interfaceIndex;
jens@28
    85
    } else {
jens@28
    86
        return NO;
jens@28
    87
    }
jens@26
    88
}
jens@26
    89
jens@28
    90
- (NSUInteger) hash {
jens@28
    91
    return _name.hash ^ _type.hash ^ _domain.hash;
jens@28
    92
}
jens@28
    93
jens@28
    94
jens@28
    95
- (void) added {
jens@28
    96
    LogTo(Bonjour,@"Added %@",self);
jens@28
    97
}
jens@28
    98
jens@28
    99
- (void) removed {
jens@28
   100
    LogTo(Bonjour,@"Removed %@",self);
jens@28
   101
    [self stop];
jens@26
   102
    
jens@28
   103
    [_txtQuery stop];
jens@28
   104
    [_txtQuery release];
jens@28
   105
    _txtQuery = nil;
jens@28
   106
    
jens@28
   107
    [_addressLookup stop];
jens@28
   108
}
jens@28
   109
jens@28
   110
jens@50
   111
- (NSString*) hostname {
jens@28
   112
    if (!_startedResolve )
jens@28
   113
        [self start];
jens@28
   114
    return _hostname;
jens@28
   115
}
jens@28
   116
jens@28
   117
- (UInt16) port {
jens@50
   118
    if (!_startedResolve )
jens@50
   119
        [self start];
jens@28
   120
    return _port;
jens@26
   121
}
jens@26
   122
jens@26
   123
jens@26
   124
#pragma mark -
jens@26
   125
#pragma mark TXT RECORD:
jens@26
   126
jens@26
   127
jens@28
   128
- (NSDictionary*) txtRecord {
jens@31
   129
    if (!_txtQuery) {
jens@31
   130
        _txtQuery = [[MYBonjourQuery alloc] initWithBonjourService: self 
jens@31
   131
                                                        recordType: kDNSServiceType_TXT];
jens@31
   132
        _txtQuery.continuous = YES;
jens@31
   133
        [_txtQuery start];
jens@31
   134
    }
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@31
   175
#pragma mark HOSTNAME/PORT RESOLUTION:
jens@28
   176
jens@28
   177
jens@31
   178
- (void) priv_resolvedHostname: (NSString*)hostname
jens@28
   179
                          port: (uint16_t)port
jens@28
   180
                     txtRecord: (NSData*)txtData
jens@26
   181
{
jens@31
   182
    LogTo(Bonjour, @"%@: hostname=%@, port=%u, txt=%u bytes", 
jens@31
   183
          self, hostname, port, txtData.length);
jens@28
   184
jens@50
   185
    if (port!=_port || !$equal(hostname,_hostname)) {
jens@50
   186
        self.hostname = hostname;
jens@50
   187
        self.port = port;
jens@50
   188
    }
jens@28
   189
    
jens@28
   190
    [self setTxtData: txtData];
jens@26
   191
}
jens@26
   192
jens@50
   193
- (void) gotResponse: (DNSServiceErrorType)errorCode {
jens@50
   194
    [super gotResponse: errorCode];
jens@50
   195
    [_addressLookup _serviceGotResponse];
jens@50
   196
}
jens@50
   197
jens@28
   198
jens@28
   199
static void resolveCallback(DNSServiceRef                       sdRef,
jens@28
   200
                            DNSServiceFlags                     flags,
jens@28
   201
                            uint32_t                            interfaceIndex,
jens@28
   202
                            DNSServiceErrorType                 errorCode,
jens@28
   203
                            const char                          *fullname,
jens@28
   204
                            const char                          *hosttarget,
jens@28
   205
                            uint16_t                            port,
jens@28
   206
                            uint16_t                            txtLen,
jens@28
   207
                            const unsigned char                 *txtRecord,
jens@28
   208
                            void                                *context)
jens@26
   209
{
jens@28
   210
    MYBonjourService *service = context;
jens@28
   211
    @try{
jens@28
   212
        //LogTo(Bonjour, @"resolveCallback for %@ (err=%i)", service,errorCode);
jens@31
   213
        if (!errorCode) {
jens@28
   214
            NSData *txtData = nil;
jens@28
   215
            if (txtRecord)
jens@28
   216
                txtData = [NSData dataWithBytes: txtRecord length: txtLen];
jens@31
   217
            [service priv_resolvedHostname: [NSString stringWithUTF8String: hosttarget]
jens@28
   218
                                      port: ntohs(port)
jens@28
   219
                                 txtRecord: txtData];
jens@28
   220
        }
jens@28
   221
    }catchAndReport(@"MYBonjourResolver query callback");
jens@31
   222
    [service gotResponse: errorCode];
jens@26
   223
}
jens@26
   224
jens@26
   225
jens@31
   226
- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
jens@28
   227
    _startedResolve = YES;
jens@31
   228
    return DNSServiceResolve(sdRefPtr,
jens@31
   229
                             kDNSServiceFlagsShareConnection,
jens@31
   230
                             _interfaceIndex, 
jens@31
   231
                             _name.UTF8String, _type.UTF8String, _domain.UTF8String,
jens@31
   232
                             &resolveCallback, self);
jens@26
   233
}
jens@26
   234
jens@44
   235
jens@44
   236
- (MYAddressLookup*) addressLookup {
jens@28
   237
    if (!_addressLookup) {
jens@28
   238
        // Create the lookup the first time this is called:
jens@50
   239
        _addressLookup = [[MYAddressLookup alloc] _initWithBonjourService: self];
jens@28
   240
        _addressLookup.interfaceIndex = _interfaceIndex;
jens@26
   241
    }
jens@28
   242
    // (Re)start the lookup if it's expired:
jens@44
   243
    if (_addressLookup && _addressLookup.timeToLive <= 0.0)
jens@28
   244
        [_addressLookup start];
jens@28
   245
    return _addressLookup;
jens@26
   246
}
jens@26
   247
jens@26
   248
jens@28
   249
- (MYBonjourQuery*) queryForRecord: (UInt16)recordType {
jens@28
   250
    MYBonjourQuery *query = [[[MYBonjourQuery alloc] initWithBonjourService: self recordType: recordType]
jens@28
   251
                                 autorelease];
jens@28
   252
    return [query start] ?query :nil;
jens@26
   253
}
jens@26
   254
jens@26
   255
jens@26
   256
@end
jens@26
   257
jens@26
   258
jens@26
   259
jens@26
   260
/*
jens@26
   261
 Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@26
   262
 
jens@26
   263
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@26
   264
 provided that the following conditions are met:
jens@26
   265
 
jens@26
   266
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@26
   267
 and the following disclaimer.
jens@26
   268
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@26
   269
 and the following disclaimer in the documentation and/or other materials provided with the
jens@26
   270
 distribution.
jens@26
   271
 
jens@26
   272
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@26
   273
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@26
   274
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@26
   275
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@26
   276
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@26
   277
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@26
   278
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@26
   279
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@26
   280
 */