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