Bonjour/MYBonjourService.m
author Jens Alfke <jens@mooseyard.com>
Wed Apr 29 13:29:31 2009 -0700 (2009-04-29)
changeset 31 1d6924779df7
parent 28 732576fa8a0d
child 43 aab592ac36fc
permissions -rw-r--r--
More work on Bonjour classes. They now support registering services.
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@31
    33
          interface: (UInt32)interfaceIndex
jens@26
    34
{
jens@31
    35
    Assert(serviceName);
jens@31
    36
    Assert(type);
jens@31
    37
    Assert(domain);
jens@26
    38
    self = [super init];
jens@26
    39
    if (self != nil) {
jens@28
    40
        _name = [serviceName copy];
jens@28
    41
        _type = [type copy];
jens@28
    42
        _domain = [domain copy];
jens@31
    43
        _fullName = [[[self class] fullNameOfService: _name ofType: _type inDomain: _domain] retain];
jens@28
    44
        _interfaceIndex = interfaceIndex;
jens@26
    45
    }
jens@26
    46
    return self;
jens@26
    47
}
jens@26
    48
jens@28
    49
- (void) dealloc {
jens@28
    50
    [_txtQuery stop];
jens@28
    51
    [_txtQuery release];
jens@28
    52
    [_addressLookup stop];
jens@28
    53
    [_addressLookup release];
jens@31
    54
    [_name release];
jens@31
    55
    [_type release];
jens@31
    56
    [_domain release];
jens@31
    57
    [_fullName release];
jens@31
    58
    [_hostname release];
jens@26
    59
    [super dealloc];
jens@26
    60
}
jens@26
    61
jens@26
    62
jens@31
    63
@synthesize name=_name, type=_type, domain=_domain, fullName=_fullName, interfaceIndex=_interfaceIndex;
jens@28
    64
jens@28
    65
jens@28
    66
- (NSString*) description {
jens@31
    67
    return $sprintf(@"%@[%@]", self.class,self.fullName);
jens@26
    68
}
jens@26
    69
jens@26
    70
jens@28
    71
- (NSComparisonResult) compare: (id)obj {
jens@28
    72
    return [_name caseInsensitiveCompare: [obj name]];
jens@26
    73
}
jens@26
    74
jens@28
    75
- (BOOL) isEqual: (id)obj {
jens@28
    76
    if ([obj isKindOfClass: [MYBonjourService class]]) {
jens@28
    77
        MYBonjourService *service = obj;
jens@28
    78
        return [_name caseInsensitiveCompare: [service name]] == 0
jens@28
    79
            && $equal(_type, service->_type)
jens@28
    80
            && $equal(_domain, service->_domain)
jens@28
    81
            && _interfaceIndex == service->_interfaceIndex;
jens@28
    82
    } else {
jens@28
    83
        return NO;
jens@28
    84
    }
jens@26
    85
}
jens@26
    86
jens@28
    87
- (NSUInteger) hash {
jens@28
    88
    return _name.hash ^ _type.hash ^ _domain.hash;
jens@28
    89
}
jens@28
    90
jens@28
    91
jens@28
    92
- (void) added {
jens@28
    93
    LogTo(Bonjour,@"Added %@",self);
jens@28
    94
}
jens@28
    95
jens@28
    96
- (void) removed {
jens@28
    97
    LogTo(Bonjour,@"Removed %@",self);
jens@28
    98
    [self stop];
jens@26
    99
    
jens@28
   100
    [_txtQuery stop];
jens@28
   101
    [_txtQuery release];
jens@28
   102
    _txtQuery = nil;
jens@28
   103
    
jens@28
   104
    [_addressLookup stop];
jens@28
   105
}
jens@28
   106
jens@28
   107
jens@28
   108
- (void) priv_finishResolve {
jens@31
   109
    // If I haven't finished my resolve yet, run it *synchronously* now so I can return a valid value:
jens@28
   110
    if (!_startedResolve )
jens@28
   111
        [self start];
jens@28
   112
    if (self.serviceRef)
jens@28
   113
        [self waitForReply];
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@31
   132
    if (!_txtQuery) {
jens@31
   133
        _txtQuery = [[MYBonjourQuery alloc] initWithBonjourService: self 
jens@31
   134
                                                        recordType: kDNSServiceType_TXT];
jens@31
   135
        _txtQuery.continuous = YES;
jens@31
   136
        [_txtQuery start];
jens@31
   137
    }
jens@26
   138
    return _txtRecord;
jens@26
   139
}
jens@26
   140
jens@28
   141
- (void) txtRecordChanged {
jens@26
   142
    // no-op (this is here for subclassers to override)
jens@26
   143
}
jens@26
   144
jens@28
   145
- (NSString*) txtStringForKey: (NSString*)key {
jens@26
   146
    NSData *value = [self.txtRecord objectForKey: key];
jens@26
   147
    if( ! value )
jens@26
   148
        return nil;
jens@26
   149
    if( ! [value isKindOfClass: [NSData class]] ) {
jens@26
   150
        Warn(@"TXT dictionary has unexpected value type: %@",value.class);
jens@26
   151
        return nil;
jens@26
   152
    }
jens@26
   153
    NSString *str = [[NSString alloc] initWithData: value encoding: NSUTF8StringEncoding];
jens@26
   154
    if( ! str )
jens@26
   155
        str = [[NSString alloc] initWithData: value encoding: NSWindowsCP1252StringEncoding];
jens@26
   156
    return [str autorelease];
jens@26
   157
}
jens@26
   158
jens@28
   159
- (void) setTxtData: (NSData*)txtData {
jens@28
   160
    NSDictionary *txtRecord = txtData ?[NSNetService dictionaryFromTXTRecordData: txtData] :nil;
jens@28
   161
    if (!$equal(txtRecord,_txtRecord)) {
jens@28
   162
        LogTo(Bonjour,@"%@ TXT = %@", self,txtRecord);
jens@26
   163
        [self willChangeValueForKey: @"txtRecord"];
jens@28
   164
        setObj(&_txtRecord, txtRecord);
jens@26
   165
        [self didChangeValueForKey: @"txtRecord"];
jens@26
   166
        [self txtRecordChanged];
jens@26
   167
    }
jens@26
   168
}
jens@26
   169
jens@26
   170
jens@28
   171
- (void) queryDidUpdate: (MYBonjourQuery*)query {
jens@28
   172
    if (query==_txtQuery)
jens@28
   173
        [self setTxtData: query.recordData];
jens@26
   174
}
jens@26
   175
jens@26
   176
jens@28
   177
#pragma mark -
jens@31
   178
#pragma mark HOSTNAME/PORT RESOLUTION:
jens@28
   179
jens@28
   180
jens@31
   181
- (void) priv_resolvedHostname: (NSString*)hostname
jens@28
   182
                          port: (uint16_t)port
jens@28
   183
                     txtRecord: (NSData*)txtData
jens@26
   184
{
jens@31
   185
    LogTo(Bonjour, @"%@: hostname=%@, port=%u, txt=%u bytes", 
jens@31
   186
          self, hostname, port, txtData.length);
jens@28
   187
jens@28
   188
    // Don't call a setter method to set these properties: the getters are synchronous, so
jens@28
   189
    // I might already be blocked in a call to one of them, in which case creating a KV
jens@28
   190
    // notification could cause trouble...
jens@28
   191
    _hostname = hostname.copy;
jens@28
   192
    _port = port;
jens@28
   193
    
jens@28
   194
    // TXT getter is async, though, so I can use a setter to announce the data's availability:
jens@28
   195
    [self setTxtData: txtData];
jens@26
   196
}
jens@26
   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@26
   235
jens@28
   236
- (MYAddressLookup*) addressLookup {
jens@28
   237
    if (!_addressLookup) {
jens@28
   238
        // Create the lookup the first time this is called:
jens@28
   239
        _addressLookup = [[MYAddressLookup alloc] initWithHostname: self.hostname];
jens@28
   240
        _addressLookup.port = _port;
jens@28
   241
        _addressLookup.interfaceIndex = _interfaceIndex;
jens@26
   242
    }
jens@28
   243
    // (Re)start the lookup if it's expired:
jens@28
   244
    if (_addressLookup && _addressLookup.timeToLive <= 0.0)
jens@28
   245
        [_addressLookup start];
jens@28
   246
    return _addressLookup;
jens@26
   247
}
jens@26
   248
jens@26
   249
jens@28
   250
- (MYBonjourQuery*) queryForRecord: (UInt16)recordType {
jens@28
   251
    MYBonjourQuery *query = [[[MYBonjourQuery alloc] initWithBonjourService: self recordType: recordType]
jens@28
   252
                                 autorelease];
jens@28
   253
    return [query start] ?query :nil;
jens@26
   254
}
jens@26
   255
jens@26
   256
jens@26
   257
@end
jens@26
   258
jens@26
   259
jens@26
   260
jens@26
   261
/*
jens@26
   262
 Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@26
   263
 
jens@26
   264
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@26
   265
 provided that the following conditions are met:
jens@26
   266
 
jens@26
   267
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@26
   268
 and the following disclaimer.
jens@26
   269
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@26
   270
 and the following disclaimer in the documentation and/or other materials provided with the
jens@26
   271
 distribution.
jens@26
   272
 
jens@26
   273
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@26
   274
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@26
   275
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@26
   276
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@26
   277
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@26
   278
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@26
   279
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@26
   280
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@26
   281
 */