jens@26: // jens@26: // MYBonjourService.m jens@26: // MYNetwork jens@26: // jens@26: // Created by Jens Alfke on 1/22/08. jens@26: // Copyright 2008 Jens Alfke. All rights reserved. jens@26: // jens@26: jens@26: #import "MYBonjourService.h" jens@28: #import "MYBonjourQuery.h" jens@28: #import "MYAddressLookup.h" jens@26: #import "IPAddress.h" jens@26: #import "ConcurrentOperation.h" jens@26: #import "Test.h" jens@26: #import "Logging.h" jens@28: #import "ExceptionUtils.h" jens@28: #import jens@26: jens@26: jens@26: NSString* const kBonjourServiceResolvedAddressesNotification = @"BonjourServiceResolvedAddresses"; jens@26: jens@26: jens@26: @interface MYBonjourService () jens@26: @end jens@26: jens@26: jens@26: @implementation MYBonjourService jens@26: jens@26: jens@28: - (id) initWithName: (NSString*)serviceName jens@28: type: (NSString*)type jens@28: domain: (NSString*)domain jens@28: interface: (uint32)interfaceIndex jens@26: { jens@26: self = [super init]; jens@26: if (self != nil) { jens@28: _name = [serviceName copy]; jens@28: _type = [type copy]; jens@28: _domain = [domain copy]; jens@28: _interfaceIndex = interfaceIndex; jens@26: } jens@26: return self; jens@26: } jens@26: jens@28: - (void) dealloc { jens@28: [_name release]; jens@28: [_type release]; jens@28: [_domain release]; jens@28: [_hostname release]; jens@28: [_txtQuery stop]; jens@28: [_txtQuery release]; jens@28: [_addressLookup stop]; jens@28: [_addressLookup release]; jens@26: [super dealloc]; jens@26: } jens@26: jens@26: jens@28: @synthesize name=_name, type=_type, domain=_domain, interfaceIndex=_interfaceIndex; jens@28: jens@28: jens@28: - (NSString*) description { jens@28: return $sprintf(@"%@['%@'.%@%@]", self.class,_name,_type,_domain); jens@26: } jens@26: jens@26: jens@28: - (NSComparisonResult) compare: (id)obj { jens@28: return [_name caseInsensitiveCompare: [obj name]]; jens@26: } jens@26: jens@28: - (BOOL) isEqual: (id)obj { jens@28: if ([obj isKindOfClass: [MYBonjourService class]]) { jens@28: MYBonjourService *service = obj; jens@28: return [_name caseInsensitiveCompare: [service name]] == 0 jens@28: && $equal(_type, service->_type) jens@28: && $equal(_domain, service->_domain) jens@28: && _interfaceIndex == service->_interfaceIndex; jens@28: } else { jens@28: return NO; jens@28: } jens@26: } jens@26: jens@28: - (NSUInteger) hash { jens@28: return _name.hash ^ _type.hash ^ _domain.hash; jens@28: } jens@28: jens@28: jens@28: - (void) added { jens@28: LogTo(Bonjour,@"Added %@",self); jens@28: } jens@28: jens@28: - (void) removed { jens@28: LogTo(Bonjour,@"Removed %@",self); jens@28: [self stop]; jens@26: jens@28: [_txtQuery stop]; jens@28: [_txtQuery release]; jens@28: _txtQuery = nil; jens@28: jens@28: [_addressLookup stop]; jens@28: } jens@28: jens@28: jens@28: - (void) priv_finishResolve { jens@28: // If I haven't finished my resolve yet, run it synchronously now so I can return a valid value: jens@28: if (!_startedResolve ) jens@28: [self start]; jens@28: if (self.serviceRef) jens@28: [self waitForReply]; jens@28: } jens@28: jens@28: - (NSString*) fullName { jens@28: if (!_fullName) [self priv_finishResolve]; jens@28: return _fullName; jens@28: } jens@28: jens@28: - (NSString*) hostname { jens@28: if (!_hostname) [self priv_finishResolve]; jens@28: return _hostname; jens@28: } jens@28: jens@28: - (UInt16) port { jens@28: if (!_port) [self priv_finishResolve]; jens@28: return _port; jens@26: } jens@26: jens@26: jens@26: #pragma mark - jens@26: #pragma mark TXT RECORD: jens@26: jens@26: jens@28: - (NSDictionary*) txtRecord { jens@28: // If I haven't started my resolve yet, start it now. (_txtRecord will be nil till it finishes.) jens@28: if (!_startedResolve) jens@28: [self start]; jens@26: return _txtRecord; jens@26: } jens@26: jens@28: - (void) txtRecordChanged { jens@26: // no-op (this is here for subclassers to override) jens@26: } jens@26: jens@28: - (NSString*) txtStringForKey: (NSString*)key { jens@26: NSData *value = [self.txtRecord objectForKey: key]; jens@26: if( ! value ) jens@26: return nil; jens@26: if( ! [value isKindOfClass: [NSData class]] ) { jens@26: Warn(@"TXT dictionary has unexpected value type: %@",value.class); jens@26: return nil; jens@26: } jens@26: NSString *str = [[NSString alloc] initWithData: value encoding: NSUTF8StringEncoding]; jens@26: if( ! str ) jens@26: str = [[NSString alloc] initWithData: value encoding: NSWindowsCP1252StringEncoding]; jens@26: return [str autorelease]; jens@26: } jens@26: jens@28: - (void) setTxtData: (NSData*)txtData { jens@28: NSDictionary *txtRecord = txtData ?[NSNetService dictionaryFromTXTRecordData: txtData] :nil; jens@28: if (!$equal(txtRecord,_txtRecord)) { jens@28: LogTo(Bonjour,@"%@ TXT = %@", self,txtRecord); jens@26: [self willChangeValueForKey: @"txtRecord"]; jens@28: setObj(&_txtRecord, txtRecord); jens@26: [self didChangeValueForKey: @"txtRecord"]; jens@26: [self txtRecordChanged]; jens@26: } jens@26: } jens@26: jens@26: jens@28: - (void) queryDidUpdate: (MYBonjourQuery*)query { jens@28: if (query==_txtQuery) jens@28: [self setTxtData: query.recordData]; jens@26: } jens@26: jens@26: jens@28: #pragma mark - jens@28: #pragma mark FULLNAME/HOSTNAME/PORT RESOLUTION: jens@28: jens@28: jens@28: - (void) priv_resolvedFullName: (NSString*)fullName jens@28: hostname: (NSString*)hostname jens@28: port: (uint16_t)port jens@28: txtRecord: (NSData*)txtData jens@26: { jens@28: LogTo(Bonjour, @"%@: fullname='%@', hostname=%@, port=%u, txt=%u bytes", jens@28: self, fullName, hostname, port, txtData.length); jens@28: jens@28: // Don't call a setter method to set these properties: the getters are synchronous, so jens@28: // I might already be blocked in a call to one of them, in which case creating a KV jens@28: // notification could cause trouble... jens@28: _fullName = fullName.copy; jens@28: _hostname = hostname.copy; jens@28: _port = port; jens@28: jens@28: // TXT getter is async, though, so I can use a setter to announce the data's availability: jens@28: [self setTxtData: txtData]; jens@28: jens@28: // Now that I know my full name, I can start a persistent query to track the TXT: jens@28: _txtQuery = [[MYBonjourQuery alloc] initWithBonjourService: self jens@28: recordType: kDNSServiceType_TXT]; jens@28: _txtQuery.continuous = YES; jens@28: [_txtQuery start]; jens@26: } jens@26: jens@28: jens@28: static void resolveCallback(DNSServiceRef sdRef, jens@28: DNSServiceFlags flags, jens@28: uint32_t interfaceIndex, jens@28: DNSServiceErrorType errorCode, jens@28: const char *fullname, jens@28: const char *hosttarget, jens@28: uint16_t port, jens@28: uint16_t txtLen, jens@28: const unsigned char *txtRecord, jens@28: void *context) jens@26: { jens@28: MYBonjourService *service = context; jens@28: @try{ jens@28: //LogTo(Bonjour, @"resolveCallback for %@ (err=%i)", service,errorCode); jens@28: if (errorCode) { jens@28: [service setError: errorCode]; jens@28: } else { jens@28: NSData *txtData = nil; jens@28: if (txtRecord) jens@28: txtData = [NSData dataWithBytes: txtRecord length: txtLen]; jens@28: [service priv_resolvedFullName: [NSString stringWithUTF8String: fullname] jens@28: hostname: [NSString stringWithUTF8String: hosttarget] jens@28: port: ntohs(port) jens@28: txtRecord: txtData]; jens@28: } jens@28: }catchAndReport(@"MYBonjourResolver query callback"); jens@26: } jens@26: jens@26: jens@28: - (DNSServiceRef) createServiceRef { jens@28: _startedResolve = YES; jens@28: DNSServiceRef serviceRef = NULL; jens@28: self.error = DNSServiceResolve(&serviceRef, 0, jens@28: _interfaceIndex, jens@28: _name.UTF8String, _type.UTF8String, _domain.UTF8String, jens@28: &resolveCallback, self); jens@28: return serviceRef; jens@26: } jens@26: jens@26: jens@28: - (MYAddressLookup*) addressLookup { jens@28: if (!_addressLookup) { jens@28: // Create the lookup the first time this is called: jens@28: _addressLookup = [[MYAddressLookup alloc] initWithHostname: self.hostname]; jens@28: _addressLookup.port = _port; jens@28: _addressLookup.interfaceIndex = _interfaceIndex; jens@26: } jens@28: // (Re)start the lookup if it's expired: jens@28: if (_addressLookup && _addressLookup.timeToLive <= 0.0) jens@28: [_addressLookup start]; jens@28: return _addressLookup; jens@26: } jens@26: jens@26: jens@28: - (MYBonjourQuery*) queryForRecord: (UInt16)recordType { jens@28: MYBonjourQuery *query = [[[MYBonjourQuery alloc] initWithBonjourService: self recordType: recordType] jens@28: autorelease]; jens@28: return [query start] ?query :nil; jens@26: } jens@26: jens@26: jens@26: @end jens@26: jens@26: jens@26: jens@26: /* jens@26: Copyright (c) 2008-2009, Jens Alfke . All rights reserved. jens@26: jens@26: Redistribution and use in source and binary forms, with or without modification, are permitted jens@26: provided that the following conditions are met: jens@26: jens@26: * Redistributions of source code must retain the above copyright notice, this list of conditions jens@26: and the following disclaimer. jens@26: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions jens@26: and the following disclaimer in the documentation and/or other materials provided with the jens@26: distribution. jens@26: jens@26: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR jens@26: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND jens@26: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- jens@26: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES jens@26: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR jens@26: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN jens@26: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF jens@26: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jens@26: */