1.1 --- a/Bonjour/MYBonjourService.m Wed Apr 22 16:45:39 2009 -0700
1.2 +++ b/Bonjour/MYBonjourService.m Tue Apr 28 10:36:28 2009 -0700
1.3 @@ -7,84 +7,120 @@
1.4 //
1.5
1.6 #import "MYBonjourService.h"
1.7 +#import "MYBonjourQuery.h"
1.8 +#import "MYAddressLookup.h"
1.9 #import "IPAddress.h"
1.10 #import "ConcurrentOperation.h"
1.11 #import "Test.h"
1.12 #import "Logging.h"
1.13 +#import "ExceptionUtils.h"
1.14 +#import <dns_sd.h>
1.15
1.16
1.17 NSString* const kBonjourServiceResolvedAddressesNotification = @"BonjourServiceResolvedAddresses";
1.18
1.19
1.20 @interface MYBonjourService ()
1.21 -@property (copy) NSSet* addresses;
1.22 @end
1.23
1.24 -@interface MYBonjourResolveOperation ()
1.25 -@property (assign) MYBonjourService *service;
1.26 -@property (retain) NSSet *addresses;
1.27 -@end
1.28 -
1.29 -
1.30
1.31 @implementation MYBonjourService
1.32
1.33
1.34 -- (id) initWithNetService: (NSNetService*)netService
1.35 +- (id) initWithName: (NSString*)serviceName
1.36 + type: (NSString*)type
1.37 + domain: (NSString*)domain
1.38 + interface: (uint32)interfaceIndex
1.39 {
1.40 self = [super init];
1.41 if (self != nil) {
1.42 - _netService = [netService retain];
1.43 - _netService.delegate = self;
1.44 + _name = [serviceName copy];
1.45 + _type = [type copy];
1.46 + _domain = [domain copy];
1.47 + _interfaceIndex = interfaceIndex;
1.48 }
1.49 return self;
1.50 }
1.51
1.52 -- (void) dealloc
1.53 -{
1.54 - Log(@"DEALLOC %@",self);
1.55 - _netService.delegate = nil;
1.56 - [_netService release];
1.57 - [_txtRecord release];
1.58 - [_addresses release];
1.59 +- (void) dealloc {
1.60 + [_name release];
1.61 + [_type release];
1.62 + [_domain release];
1.63 + [_hostname release];
1.64 + [_txtQuery stop];
1.65 + [_txtQuery release];
1.66 + [_addressLookup stop];
1.67 + [_addressLookup release];
1.68 [super dealloc];
1.69 }
1.70
1.71
1.72 -- (NSString*) description
1.73 -{
1.74 - return $sprintf(@"%@['%@'.%@%@]", self.class,self.name,_netService.type,_netService.domain);
1.75 +@synthesize name=_name, type=_type, domain=_domain, interfaceIndex=_interfaceIndex;
1.76 +
1.77 +
1.78 +- (NSString*) description {
1.79 + return $sprintf(@"%@['%@'.%@%@]", self.class,_name,_type,_domain);
1.80 }
1.81
1.82
1.83 -- (NSComparisonResult) compare: (id)obj
1.84 -{
1.85 - return [self.name caseInsensitiveCompare: [obj name]];
1.86 +- (NSComparisonResult) compare: (id)obj {
1.87 + return [_name caseInsensitiveCompare: [obj name]];
1.88 }
1.89
1.90 -
1.91 -- (NSNetService*) netService {return _netService;}
1.92 -- (BOOL) isEqual: (id)obj {return [obj isKindOfClass: [MYBonjourService class]] && [_netService isEqual: [obj netService]];}
1.93 -- (NSUInteger) hash {return _netService.hash;}
1.94 -- (NSString*) name {return _netService.name;}
1.95 -
1.96 -
1.97 -- (void) added
1.98 -{
1.99 - LogTo(Bonjour,@"Added %@",_netService);
1.100 +- (BOOL) isEqual: (id)obj {
1.101 + if ([obj isKindOfClass: [MYBonjourService class]]) {
1.102 + MYBonjourService *service = obj;
1.103 + return [_name caseInsensitiveCompare: [service name]] == 0
1.104 + && $equal(_type, service->_type)
1.105 + && $equal(_domain, service->_domain)
1.106 + && _interfaceIndex == service->_interfaceIndex;
1.107 + } else {
1.108 + return NO;
1.109 + }
1.110 }
1.111
1.112 -- (void) removed
1.113 -{
1.114 - LogTo(Bonjour,@"Removed %@",_netService);
1.115 - [_netService stopMonitoring];
1.116 - _netService.delegate = nil;
1.117 +- (NSUInteger) hash {
1.118 + return _name.hash ^ _type.hash ^ _domain.hash;
1.119 +}
1.120 +
1.121 +
1.122 +- (void) added {
1.123 + LogTo(Bonjour,@"Added %@",self);
1.124 +}
1.125 +
1.126 +- (void) removed {
1.127 + LogTo(Bonjour,@"Removed %@",self);
1.128 + [self stop];
1.129
1.130 - if( _resolveOp ) {
1.131 - [_resolveOp cancel];
1.132 - [_resolveOp release];
1.133 - _resolveOp = nil;
1.134 - }
1.135 + [_txtQuery stop];
1.136 + [_txtQuery release];
1.137 + _txtQuery = nil;
1.138 +
1.139 + [_addressLookup stop];
1.140 +}
1.141 +
1.142 +
1.143 +- (void) priv_finishResolve {
1.144 + // If I haven't finished my resolve yet, run it synchronously now so I can return a valid value:
1.145 + if (!_startedResolve )
1.146 + [self start];
1.147 + if (self.serviceRef)
1.148 + [self waitForReply];
1.149 +}
1.150 +
1.151 +- (NSString*) fullName {
1.152 + if (!_fullName) [self priv_finishResolve];
1.153 + return _fullName;
1.154 +}
1.155 +
1.156 +- (NSString*) hostname {
1.157 + if (!_hostname) [self priv_finishResolve];
1.158 + return _hostname;
1.159 +}
1.160 +
1.161 +- (UInt16) port {
1.162 + if (!_port) [self priv_finishResolve];
1.163 + return _port;
1.164 }
1.165
1.166
1.167 @@ -92,19 +128,18 @@
1.168 #pragma mark TXT RECORD:
1.169
1.170
1.171 -- (NSDictionary*) txtRecord
1.172 -{
1.173 - [_netService startMonitoring];
1.174 +- (NSDictionary*) txtRecord {
1.175 + // If I haven't started my resolve yet, start it now. (_txtRecord will be nil till it finishes.)
1.176 + if (!_startedResolve)
1.177 + [self start];
1.178 return _txtRecord;
1.179 }
1.180
1.181 -- (void) txtRecordChanged
1.182 -{
1.183 +- (void) txtRecordChanged {
1.184 // no-op (this is here for subclassers to override)
1.185 }
1.186
1.187 -- (NSString*) txtStringForKey: (NSString*)key
1.188 -{
1.189 +- (NSString*) txtStringForKey: (NSString*)key {
1.190 NSData *value = [self.txtRecord objectForKey: key];
1.191 if( ! value )
1.192 return nil;
1.193 @@ -118,95 +153,112 @@
1.194 return [str autorelease];
1.195 }
1.196
1.197 -
1.198 -- (void)netService:(NSNetService *)sender didUpdateTXTRecordData:(NSData *)data
1.199 -{
1.200 - NSDictionary *txtDict = [NSNetService dictionaryFromTXTRecordData: data];
1.201 - if( ! $equal(txtDict,_txtRecord) ) {
1.202 - LogTo(Bonjour,@"%@ got TXT record (%u bytes)",self,data.length);
1.203 +- (void) setTxtData: (NSData*)txtData {
1.204 + NSDictionary *txtRecord = txtData ?[NSNetService dictionaryFromTXTRecordData: txtData] :nil;
1.205 + if (!$equal(txtRecord,_txtRecord)) {
1.206 + LogTo(Bonjour,@"%@ TXT = %@", self,txtRecord);
1.207 [self willChangeValueForKey: @"txtRecord"];
1.208 - setObj(&_txtRecord,txtDict);
1.209 + setObj(&_txtRecord, txtRecord);
1.210 [self didChangeValueForKey: @"txtRecord"];
1.211 [self txtRecordChanged];
1.212 }
1.213 }
1.214
1.215
1.216 -#pragma mark -
1.217 -#pragma mark ADDRESS RESOLUTION:
1.218 -
1.219 -
1.220 -#define kAddressResolveTimeout 10.0
1.221 -#define kAddressExpirationInterval 60.0
1.222 -#define kAddressErrorRetryInterval 5.0
1.223 -
1.224 -
1.225 -- (NSSet*) addresses
1.226 -{
1.227 - if( _addresses && CFAbsoluteTimeGetCurrent() >= _addressesExpireAt ) {
1.228 - setObj(&_addresses,nil); // eww, toss 'em and get new ones
1.229 - [self resolve];
1.230 - }
1.231 - return _addresses;
1.232 +- (void) queryDidUpdate: (MYBonjourQuery*)query {
1.233 + if (query==_txtQuery)
1.234 + [self setTxtData: query.recordData];
1.235 }
1.236
1.237
1.238 -- (MYBonjourResolveOperation*) resolve
1.239 +#pragma mark -
1.240 +#pragma mark FULLNAME/HOSTNAME/PORT RESOLUTION:
1.241 +
1.242 +
1.243 +- (void) priv_resolvedFullName: (NSString*)fullName
1.244 + hostname: (NSString*)hostname
1.245 + port: (uint16_t)port
1.246 + txtRecord: (NSData*)txtData
1.247 {
1.248 - if( ! _resolveOp ) {
1.249 - LogTo(Bonjour,@"Resolving %@",self);
1.250 - _resolveOp = [[MYBonjourResolveOperation alloc] init];
1.251 - _resolveOp.service = self;
1.252 - [_resolveOp start];
1.253 - Assert(_netService);
1.254 - Assert(_netService.delegate=self);
1.255 - [_netService resolveWithTimeout: kAddressResolveTimeout];
1.256 - }
1.257 - return _resolveOp;
1.258 + LogTo(Bonjour, @"%@: fullname='%@', hostname=%@, port=%u, txt=%u bytes",
1.259 + self, fullName, hostname, port, txtData.length);
1.260 +
1.261 + // Don't call a setter method to set these properties: the getters are synchronous, so
1.262 + // I might already be blocked in a call to one of them, in which case creating a KV
1.263 + // notification could cause trouble...
1.264 + _fullName = fullName.copy;
1.265 + _hostname = hostname.copy;
1.266 + _port = port;
1.267 +
1.268 + // TXT getter is async, though, so I can use a setter to announce the data's availability:
1.269 + [self setTxtData: txtData];
1.270 +
1.271 + // Now that I know my full name, I can start a persistent query to track the TXT:
1.272 + _txtQuery = [[MYBonjourQuery alloc] initWithBonjourService: self
1.273 + recordType: kDNSServiceType_TXT];
1.274 + _txtQuery.continuous = YES;
1.275 + [_txtQuery start];
1.276 }
1.277
1.278 -- (void) setAddresses: (NSSet*)addresses
1.279 +
1.280 +static void resolveCallback(DNSServiceRef sdRef,
1.281 + DNSServiceFlags flags,
1.282 + uint32_t interfaceIndex,
1.283 + DNSServiceErrorType errorCode,
1.284 + const char *fullname,
1.285 + const char *hosttarget,
1.286 + uint16_t port,
1.287 + uint16_t txtLen,
1.288 + const unsigned char *txtRecord,
1.289 + void *context)
1.290 {
1.291 - setObj(&_addresses,addresses);
1.292 + MYBonjourService *service = context;
1.293 + @try{
1.294 + //LogTo(Bonjour, @"resolveCallback for %@ (err=%i)", service,errorCode);
1.295 + if (errorCode) {
1.296 + [service setError: errorCode];
1.297 + } else {
1.298 + NSData *txtData = nil;
1.299 + if (txtRecord)
1.300 + txtData = [NSData dataWithBytes: txtRecord length: txtLen];
1.301 + [service priv_resolvedFullName: [NSString stringWithUTF8String: fullname]
1.302 + hostname: [NSString stringWithUTF8String: hosttarget]
1.303 + port: ntohs(port)
1.304 + txtRecord: txtData];
1.305 + }
1.306 + }catchAndReport(@"MYBonjourResolver query callback");
1.307 }
1.308
1.309
1.310 -- (void) _finishedResolving: (NSSet*)addresses expireIn: (NSTimeInterval)expirationInterval
1.311 -{
1.312 - _addressesExpireAt = CFAbsoluteTimeGetCurrent() + expirationInterval;
1.313 - self.addresses = addresses;
1.314 - _resolveOp.addresses = addresses;
1.315 - [_resolveOp finish];
1.316 - [_resolveOp release];
1.317 - _resolveOp = nil;
1.318 +- (DNSServiceRef) createServiceRef {
1.319 + _startedResolve = YES;
1.320 + DNSServiceRef serviceRef = NULL;
1.321 + self.error = DNSServiceResolve(&serviceRef, 0,
1.322 + _interfaceIndex,
1.323 + _name.UTF8String, _type.UTF8String, _domain.UTF8String,
1.324 + &resolveCallback, self);
1.325 + return serviceRef;
1.326 }
1.327
1.328
1.329 -- (void)netServiceDidResolveAddress:(NSNetService *)sender
1.330 -{
1.331 - // Convert the raw sockaddrs into IPAddress objects:
1.332 - NSMutableSet *addresses = [NSMutableSet setWithCapacity: 2];
1.333 - for( NSData *rawAddr in _netService.addresses ) {
1.334 - IPAddress *addr = [[IPAddress alloc] initWithSockAddr: rawAddr.bytes];
1.335 - if( addr ) {
1.336 - [addresses addObject: addr];
1.337 - [addr release];
1.338 - }
1.339 +- (MYAddressLookup*) addressLookup {
1.340 + if (!_addressLookup) {
1.341 + // Create the lookup the first time this is called:
1.342 + _addressLookup = [[MYAddressLookup alloc] initWithHostname: self.hostname];
1.343 + _addressLookup.port = _port;
1.344 + _addressLookup.interfaceIndex = _interfaceIndex;
1.345 }
1.346 - LogTo(Bonjour,@"Resolved %@: %@",self,addresses);
1.347 - [self _finishedResolving: addresses expireIn: kAddressExpirationInterval];
1.348 + // (Re)start the lookup if it's expired:
1.349 + if (_addressLookup && _addressLookup.timeToLive <= 0.0)
1.350 + [_addressLookup start];
1.351 + return _addressLookup;
1.352 }
1.353
1.354 -- (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict
1.355 -{
1.356 - LogTo(Bonjour,@"Error resolving %@ -- %@",self,errorDict);
1.357 - [self _finishedResolving: [NSArray array] expireIn: kAddressErrorRetryInterval];
1.358 -}
1.359
1.360 -- (void)netServiceDidStop:(NSNetService *)sender
1.361 -{
1.362 - LogTo(Bonjour,@"Resolve stopped for %@",self);
1.363 - [self _finishedResolving: [NSArray array] expireIn: kAddressErrorRetryInterval];
1.364 +- (MYBonjourQuery*) queryForRecord: (UInt16)recordType {
1.365 + MYBonjourQuery *query = [[[MYBonjourQuery alloc] initWithBonjourService: self recordType: recordType]
1.366 + autorelease];
1.367 + return [query start] ?query :nil;
1.368 }
1.369
1.370
1.371 @@ -214,20 +266,6 @@
1.372
1.373
1.374
1.375 -
1.376 -@implementation MYBonjourResolveOperation
1.377 -
1.378 -@synthesize service=_service, addresses=_addresses;
1.379 -
1.380 -- (void) dealloc
1.381 -{
1.382 - [_addresses release];
1.383 - [super dealloc];
1.384 -}
1.385 -
1.386 -@end
1.387 -
1.388 -
1.389 /*
1.390 Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
1.391