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