jens@27: // jens@27: // MYDNSService.m jens@27: // MYNetwork jens@27: // jens@27: // Created by Jens Alfke on 4/23/09. jens@27: // Copyright 2009 Jens Alfke. All rights reserved. jens@27: // jens@27: jens@27: #import "MYDNSService.h" jens@27: #import "CollectionUtils.h" jens@27: #import "Logging.h" jens@27: #import "Test.h" jens@27: #import "ExceptionUtils.h" jens@27: jens@27: #import jens@27: jens@27: jens@27: static void serviceCallback(CFSocketRef s, jens@27: CFSocketCallBackType type, jens@27: CFDataRef address, jens@27: const void *data, jens@27: void *clientCallBackInfo); jens@31: jens@27: jens@27: @implementation MYDNSService jens@27: jens@27: jens@27: - (void) dealloc jens@27: { jens@28: Log(@"DEALLOC %@ %p", self.class,self); jens@27: if( _serviceRef ) jens@28: [self cancel]; jens@27: [super dealloc]; jens@27: } jens@27: jens@27: - (void) finalize jens@27: { jens@27: if( _serviceRef ) jens@28: [self cancel]; jens@27: [super finalize]; jens@27: } jens@27: jens@27: jens@28: - (DNSServiceErrorType) error { jens@28: return _error; jens@28: } jens@28: jens@28: - (void) setError: (DNSServiceErrorType)error { jens@28: if (error) jens@28: Warn(@"%@ error := %i", self,error); jens@28: _error = error; jens@28: } jens@28: jens@28: jens@31: @synthesize continuous=_continuous, serviceRef=_serviceRef, usePrivateConnection=_usePrivateConnection; jens@27: jens@27: jens@31: - (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr { jens@27: AssertAbstractMethod(); jens@27: } jens@27: jens@27: jens@31: - (void) gotResponse: (DNSServiceErrorType)errorCode { jens@31: _gotResponse = YES; jens@31: if (!_continuous) jens@31: [self cancel]; jens@31: if (errorCode && errorCode != _error) { jens@31: Log(@"%@ got error %i", self,errorCode); jens@31: self.error = errorCode; jens@31: } jens@31: } jens@31: jens@31: jens@28: - (BOOL) start jens@27: { jens@27: if (_serviceRef) jens@28: return YES; // already started jens@28: jens@28: if (_error) jens@28: self.error = 0; jens@31: _gotResponse = NO; jens@28: jens@31: if (!_usePrivateConnection) { jens@31: _connection = [[MYDNSConnection sharedConnection] retain]; jens@31: if (!_connection) { jens@31: self.error = kDNSServiceErr_Unknown; jens@31: return NO; jens@31: } jens@31: _serviceRef = _connection.connectionRef; jens@31: } jens@31: jens@28: // Ask the subclass to create a DNSServiceRef: jens@31: _error = [self createServiceRef: &_serviceRef]; jens@31: if (_error) { jens@31: _serviceRef = NULL; jens@31: setObj(&_connection,nil); jens@31: if (!_error) jens@31: self.error = kDNSServiceErr_Unknown; jens@31: LogTo(DNS,@"Failed to open %@ -- err=%i",self,_error); jens@31: return NO; jens@31: } jens@28: jens@31: if (!_connection) jens@31: _connection = [[MYDNSConnection alloc] initWithServiceRef: _serviceRef]; jens@31: jens@31: LogTo(DNS,@"Started %@",self); jens@31: return YES; // Succeeded jens@31: } jens@31: jens@31: jens@31: - (BOOL) waitForReply { jens@31: if( ! _serviceRef ) jens@31: if( ! [self start] ) jens@31: return NO; jens@31: // Run the runloop until there's either an error or a result: jens@31: _gotResponse = NO; jens@31: LogTo(DNS,@"Waiting for reply to %@...", self); jens@31: while( !_gotResponse ) jens@31: if( ! [_connection processResult] ) jens@31: break; jens@31: LogTo(DNS,@" ...got reply"); jens@31: return (self.error==0); jens@27: } jens@27: jens@27: jens@28: - (void) cancel jens@27: { jens@27: if( _serviceRef ) { jens@27: LogTo(DNS,@"Stopped %@",self); jens@27: DNSServiceRefDeallocate(_serviceRef); jens@27: _serviceRef = NULL; jens@31: jens@31: setObj(&_connection,nil); jens@27: } jens@27: } jens@27: jens@27: jens@28: - (void) stop jens@27: { jens@28: [self cancel]; jens@27: if (_error) jens@27: self.error = 0; jens@27: } jens@27: jens@27: jens@50: - (BOOL) isRunning { jens@50: return _serviceRef != NULL; jens@50: } jens@50: jens@50: jens@31: + (NSString*) fullNameOfService: (NSString*)serviceName jens@31: ofType: (NSString*)type jens@31: inDomain: (NSString*)domain jens@28: { jens@31: //FIX: Do I need to un-escape the serviceName? jens@31: Assert(type); jens@31: Assert(domain); jens@31: char fullName[kDNSServiceMaxDomainName]; jens@31: if (DNSServiceConstructFullName(fullName, serviceName.UTF8String, type.UTF8String, domain.UTF8String) == 0) jens@31: return [NSString stringWithUTF8String: fullName]; jens@31: else jens@31: return nil; jens@31: } jens@31: jens@31: jens@31: @end jens@31: jens@31: jens@31: #pragma mark - jens@31: #pragma mark SHARED CONNECTION: jens@31: jens@31: jens@31: @interface MYDNSConnection () jens@31: - (BOOL) open; jens@31: @end jens@31: jens@31: jens@31: @implementation MYDNSConnection jens@31: jens@31: jens@31: MYDNSConnection *sSharedConnection; jens@31: jens@31: jens@31: - (id) init jens@31: { jens@31: DNSServiceRef connectionRef = NULL; jens@31: DNSServiceErrorType err = DNSServiceCreateConnection(&connectionRef); jens@31: if (err || !connectionRef) { jens@31: Warn(@"MYDNSConnection: DNSServiceCreateConnection failed, err=%i", err); jens@31: [self release]; jens@31: return nil; jens@31: } jens@31: return [self initWithServiceRef: connectionRef]; jens@31: } jens@31: jens@31: jens@31: - (id) initWithServiceRef: (DNSServiceRef)serviceRef jens@31: { jens@31: Assert(serviceRef); jens@31: self = [super init]; jens@31: if (self != nil) { jens@31: _connectionRef = serviceRef; jens@31: LogTo(DNS,@"INIT %@", self); jens@31: if (![self open]) { jens@31: [self release]; jens@31: return nil; jens@31: } jens@31: } jens@31: return self; jens@31: } jens@31: jens@31: jens@31: + (MYDNSConnection*) sharedConnection { jens@31: @synchronized(self) { jens@31: if (!sSharedConnection) jens@31: sSharedConnection = [[[self alloc] init] autorelease]; jens@31: } jens@31: return sSharedConnection; jens@31: } jens@31: jens@31: jens@31: - (void) dealloc jens@31: { jens@31: LogTo(DNS,@"DEALLOC %@", self); jens@31: [self close]; jens@31: [super dealloc]; jens@31: } jens@31: jens@31: - (void) finalize { jens@31: [self close]; jens@31: [super finalize]; jens@31: } jens@31: jens@31: jens@31: @synthesize connectionRef=_connectionRef; jens@31: jens@31: - (NSString*) description { jens@31: return $sprintf(@"%@[conn=%p]", self.class,_connectionRef); jens@31: } jens@31: jens@31: - (BOOL) open { jens@31: if (_runLoopSource) jens@31: return YES; // Already opened jens@31: jens@31: // Wrap a CFSocket around the service's socket: jens@31: CFSocketContext ctxt = { 0, self, CFRetain, CFRelease, NULL }; jens@31: _socket = CFSocketCreateWithNative(NULL, jens@31: DNSServiceRefSockFD(_connectionRef), jens@31: kCFSocketReadCallBack, jens@31: &serviceCallback, &ctxt); jens@31: if( _socket ) { jens@31: CFSocketSetSocketFlags(_socket, jens@31: CFSocketGetSocketFlags(_socket) & ~kCFSocketCloseOnInvalidate); jens@31: // Attach the socket to the runloop so the serviceCallback will be invoked: jens@31: _runLoopSource = CFSocketCreateRunLoopSource(NULL, _socket, 0); jens@31: if( _runLoopSource ) { jens@31: CFRunLoopAddSource(CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes); jens@31: // Success! jens@31: LogTo(DNS,@"Successfully opened %@", self); jens@31: return YES; jens@31: } jens@31: } jens@31: jens@31: // Failure: jens@31: Warn(@"Failed to connect %@ to runloop", self); jens@31: [self close]; jens@31: return NO; jens@31: } jens@31: jens@31: jens@31: - (void) close { jens@31: @synchronized(self) { jens@31: if( _runLoopSource ) { jens@31: CFRunLoopSourceInvalidate(_runLoopSource); jens@31: CFRelease(_runLoopSource); jens@31: _runLoopSource = NULL; jens@31: } jens@31: if( _socket ) { jens@31: CFSocketInvalidate(_socket); jens@31: CFRelease(_socket); jens@31: _socket = NULL; jens@31: } jens@31: if( _connectionRef ) { jens@31: LogTo(DNS,@"Closed %@",self); jens@31: DNSServiceRefDeallocate(_connectionRef); jens@31: _connectionRef = NULL; jens@31: } jens@31: jens@31: if (self==sSharedConnection) jens@31: sSharedConnection = nil; jens@28: } jens@28: } jens@28: jens@31: jens@31: - (BOOL) processResult { jens@31: NSAutoreleasePool *pool = [NSAutoreleasePool new]; jens@31: LogTo(DNS,@"---serviceCallback----"); jens@31: DNSServiceErrorType err = DNSServiceProcessResult(_connectionRef); jens@50: if (err) { jens@31: Warn(@"%@: DNSServiceProcessResult failed, err=%i !!!", self,err); jens@50: //FIX: Are errors here fatal, meaning I should close the connection? jens@50: // I've run into infinite loops constantly getting kDNSServiceErr_ServiceNotRunning jens@50: // or kDNSServiceErr_BadReference ... jens@50: } jens@31: [pool drain]; jens@31: return !err; jens@31: } jens@28: jens@28: jens@27: /** CFSocket callback, informing us that _socket has data available, which means jens@27: that the DNS service has an incoming result to be processed. This will end up invoking jens@27: the service's specific callback. */ jens@27: static void serviceCallback(CFSocketRef s, jens@27: CFSocketCallBackType type, jens@27: CFDataRef address, const void *data, void *clientCallBackInfo) jens@27: { jens@31: MYDNSConnection *connection = clientCallBackInfo; jens@31: [connection processResult]; jens@27: } jens@27: jens@27: jens@27: @end jens@27: jens@27: jens@27: /* jens@27: Copyright (c) 2008-2009, Jens Alfke . All rights reserved. jens@27: jens@27: Redistribution and use in source and binary forms, with or without modification, are permitted jens@27: provided that the following conditions are met: jens@27: jens@27: * Redistributions of source code must retain the above copyright notice, this list of conditions jens@27: and the following disclaimer. jens@27: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions jens@27: and the following disclaimer in the documentation and/or other materials provided with the jens@27: distribution. jens@27: jens@27: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR jens@27: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND jens@27: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- jens@27: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES jens@27: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR jens@27: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN jens@27: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF jens@27: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jens@27: */