PortMapper/MYDNSService.m
author morrowa@betelgeuse.local
Tue Jun 23 11:44:30 2009 -0700 (2009-06-23)
changeset 51 de59ce19f42e
parent 28 732576fa8a0d
child 50 63baa74c903f
permissions -rw-r--r--
BROKEN COMMIT. Majority of code to handle closing has been added. Listeners do not close correctly.
     1 //
     2 //  MYDNSService.m
     3 //  MYNetwork
     4 //
     5 //  Created by Jens Alfke on 4/23/09.
     6 //  Copyright 2009 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "MYDNSService.h"
    10 #import "CollectionUtils.h"
    11 #import "Logging.h"
    12 #import "Test.h"
    13 #import "ExceptionUtils.h"
    14 
    15 #import <dns_sd.h>
    16 
    17 
    18 static void serviceCallback(CFSocketRef s, 
    19                             CFSocketCallBackType type,
    20                             CFDataRef address,
    21                             const void *data,
    22                             void *clientCallBackInfo);
    23         
    24 
    25 @implementation MYDNSService
    26 
    27 
    28 - (void) dealloc
    29 {
    30     Log(@"DEALLOC %@ %p", self.class,self);
    31     if( _serviceRef )
    32         [self cancel];
    33     [super dealloc];
    34 }
    35 
    36 - (void) finalize
    37 {
    38     if( _serviceRef )
    39         [self cancel];
    40     [super finalize];
    41 }
    42 
    43 
    44 - (DNSServiceErrorType) error {
    45     return _error;
    46 }
    47 
    48 - (void) setError: (DNSServiceErrorType)error {
    49     if (error)
    50         Warn(@"%@ error := %i", self,error);
    51     _error = error;
    52 }
    53 
    54 
    55 @synthesize continuous=_continuous, serviceRef=_serviceRef, usePrivateConnection=_usePrivateConnection;
    56 
    57 
    58 - (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
    59     AssertAbstractMethod();
    60 }
    61 
    62 
    63 - (void) gotResponse: (DNSServiceErrorType)errorCode {
    64     _gotResponse = YES;
    65     if (!_continuous)
    66         [self cancel];
    67     if (errorCode && errorCode != _error) {
    68         Log(@"%@ got error %i", self,errorCode);
    69         self.error = errorCode;
    70     }
    71 }
    72 
    73 
    74 - (BOOL) start
    75 {
    76     if (_serviceRef)
    77         return YES;     // already started
    78 
    79     if (_error)
    80         self.error = 0;
    81     _gotResponse = NO;
    82 
    83     if (!_usePrivateConnection) {
    84         _connection = [[MYDNSConnection sharedConnection] retain];
    85         if (!_connection) {
    86             self.error = kDNSServiceErr_Unknown;
    87             return NO;
    88         }
    89         _serviceRef = _connection.connectionRef;
    90     }
    91     
    92     // Ask the subclass to create a DNSServiceRef:
    93     _error = [self createServiceRef: &_serviceRef];
    94     if (_error) {
    95         _serviceRef = NULL;
    96         setObj(&_connection,nil);
    97         if (!_error)
    98             self.error = kDNSServiceErr_Unknown;
    99         LogTo(DNS,@"Failed to open %@ -- err=%i",self,_error);
   100         return NO;
   101     }
   102     
   103     if (!_connection)
   104         _connection = [[MYDNSConnection alloc] initWithServiceRef: _serviceRef];
   105     
   106     LogTo(DNS,@"Started %@",self);
   107     return YES; // Succeeded
   108 }
   109 
   110 
   111 - (BOOL) waitForReply {
   112     if( ! _serviceRef )
   113         if( ! [self start] )
   114             return NO;
   115     // Run the runloop until there's either an error or a result:
   116     _gotResponse = NO;
   117     LogTo(DNS,@"Waiting for reply to %@...", self);
   118     while( !_gotResponse )
   119         if( ! [_connection processResult] )
   120             break;
   121     LogTo(DNS,@"    ...got reply");
   122     return (self.error==0);
   123 }
   124 
   125 
   126 - (void) cancel
   127 {
   128     if( _serviceRef ) {
   129         LogTo(DNS,@"Stopped %@",self);
   130         DNSServiceRefDeallocate(_serviceRef);
   131         _serviceRef = NULL;
   132         
   133         setObj(&_connection,nil);
   134     }
   135 }
   136 
   137 
   138 - (void) stop
   139 {
   140     [self cancel];
   141     if (_error)
   142         self.error = 0;
   143 }
   144 
   145 
   146 + (NSString*) fullNameOfService: (NSString*)serviceName
   147                          ofType: (NSString*)type
   148                        inDomain: (NSString*)domain
   149 {
   150     //FIX: Do I need to un-escape the serviceName?
   151     Assert(type);
   152     Assert(domain);
   153     char fullName[kDNSServiceMaxDomainName];
   154     if (DNSServiceConstructFullName(fullName, serviceName.UTF8String, type.UTF8String, domain.UTF8String) == 0)
   155         return [NSString stringWithUTF8String: fullName];
   156     else
   157         return nil;
   158 }
   159 
   160 
   161 @end
   162 
   163 
   164 #pragma mark -
   165 #pragma mark SHARED CONNECTION:
   166 
   167 
   168 @interface MYDNSConnection ()
   169 - (BOOL) open;
   170 @end
   171 
   172 
   173 @implementation MYDNSConnection
   174 
   175 
   176 MYDNSConnection *sSharedConnection;
   177 
   178 
   179 - (id) init
   180 {
   181     DNSServiceRef connectionRef = NULL;
   182     DNSServiceErrorType err = DNSServiceCreateConnection(&connectionRef);
   183     if (err || !connectionRef) {
   184         Warn(@"MYDNSConnection: DNSServiceCreateConnection failed, err=%i", err);
   185         [self release];
   186         return nil;
   187     }
   188     return [self initWithServiceRef: connectionRef];
   189 }
   190 
   191 
   192 - (id) initWithServiceRef: (DNSServiceRef)serviceRef
   193 {
   194     Assert(serviceRef);
   195     self = [super init];
   196     if (self != nil) {
   197         _connectionRef = serviceRef;
   198         LogTo(DNS,@"INIT %@", self);
   199         if (![self open]) {
   200             [self release];
   201             return nil;
   202         }
   203     }
   204     return self;
   205 }
   206 
   207 
   208 + (MYDNSConnection*) sharedConnection {
   209     @synchronized(self) {
   210         if (!sSharedConnection)
   211             sSharedConnection = [[[self alloc] init] autorelease];
   212     }
   213     return sSharedConnection;
   214 }
   215 
   216 
   217 - (void) dealloc
   218 {
   219     LogTo(DNS,@"DEALLOC %@", self);
   220     [self close];
   221     [super dealloc];
   222 }
   223 
   224 - (void) finalize {
   225     [self close];
   226     [super finalize];
   227 }
   228 
   229 
   230 @synthesize connectionRef=_connectionRef;
   231 
   232 - (NSString*) description {
   233     return $sprintf(@"%@[conn=%p]", self.class,_connectionRef);
   234 }
   235 
   236 - (BOOL) open {
   237     if (_runLoopSource)
   238         return YES;        // Already opened
   239     
   240     // Wrap a CFSocket around the service's socket:
   241     CFSocketContext ctxt = { 0, self, CFRetain, CFRelease, NULL };
   242     _socket = CFSocketCreateWithNative(NULL, 
   243                                                        DNSServiceRefSockFD(_connectionRef), 
   244                                                        kCFSocketReadCallBack, 
   245                                                        &serviceCallback, &ctxt);
   246     if( _socket ) {
   247         CFSocketSetSocketFlags(_socket, 
   248                                CFSocketGetSocketFlags(_socket) & ~kCFSocketCloseOnInvalidate);
   249         // Attach the socket to the runloop so the serviceCallback will be invoked:
   250         _runLoopSource = CFSocketCreateRunLoopSource(NULL, _socket, 0);
   251         if( _runLoopSource ) {
   252             CFRunLoopAddSource(CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes);
   253             // Success!
   254             LogTo(DNS,@"Successfully opened %@", self);
   255             return YES;
   256         }
   257     }
   258     
   259     // Failure:
   260     Warn(@"Failed to connect %@ to runloop", self);
   261     [self close];
   262     return NO;
   263 }
   264 
   265 
   266 - (void) close {
   267     @synchronized(self) {
   268         if( _runLoopSource ) {
   269             CFRunLoopSourceInvalidate(_runLoopSource);
   270             CFRelease(_runLoopSource);
   271             _runLoopSource = NULL;
   272         }
   273         if( _socket ) {
   274             CFSocketInvalidate(_socket);
   275             CFRelease(_socket);
   276             _socket = NULL;
   277         }
   278         if( _connectionRef ) {
   279             LogTo(DNS,@"Closed %@",self);
   280             DNSServiceRefDeallocate(_connectionRef);
   281             _connectionRef = NULL;
   282         }
   283         
   284         if (self==sSharedConnection)
   285             sSharedConnection = nil;
   286     }
   287 }
   288 
   289 
   290 - (BOOL) processResult {
   291     NSAutoreleasePool *pool = [NSAutoreleasePool new];
   292     LogTo(DNS,@"---serviceCallback----");
   293     DNSServiceErrorType err = DNSServiceProcessResult(_connectionRef);
   294     if (err)
   295         Warn(@"%@: DNSServiceProcessResult failed, err=%i !!!", self,err);
   296     [pool drain];
   297     return !err;
   298 }
   299 
   300 
   301 /** CFSocket callback, informing us that _socket has data available, which means
   302     that the DNS service has an incoming result to be processed. This will end up invoking
   303     the service's specific callback. */
   304 static void serviceCallback(CFSocketRef s, 
   305                             CFSocketCallBackType type,
   306                             CFDataRef address, const void *data, void *clientCallBackInfo)
   307 {
   308     MYDNSConnection *connection = clientCallBackInfo;
   309     [connection processResult];
   310 }
   311 
   312 
   313 @end
   314 
   315 
   316 /*
   317  Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   318  
   319  Redistribution and use in source and binary forms, with or without modification, are permitted
   320  provided that the following conditions are met:
   321  
   322  * Redistributions of source code must retain the above copyright notice, this list of conditions
   323  and the following disclaimer.
   324  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   325  and the following disclaimer in the documentation and/or other materials provided with the
   326  distribution.
   327  
   328  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   329  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   330  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   331  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   332  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   333   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   334  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   335  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   336  */