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