PortMapper/MYDNSService.m
changeset 53 e9f209a24d53
parent 28 732576fa8a0d
child 50 63baa74c903f
     1.1 --- a/PortMapper/MYDNSService.m	Mon Apr 27 09:03:56 2009 -0700
     1.2 +++ b/PortMapper/MYDNSService.m	Tue Jun 23 12:46:40 2009 -0700
     1.3 @@ -20,7 +20,7 @@
     1.4                              CFDataRef address,
     1.5                              const void *data,
     1.6                              void *clientCallBackInfo);
     1.7 -
     1.8 +        
     1.9  
    1.10  @implementation MYDNSService
    1.11  
    1.12 @@ -52,14 +52,25 @@
    1.13  }
    1.14  
    1.15  
    1.16 -@synthesize continuous=_continuous, serviceRef=_serviceRef;
    1.17 +@synthesize continuous=_continuous, serviceRef=_serviceRef, usePrivateConnection=_usePrivateConnection;
    1.18  
    1.19  
    1.20 -- (DNSServiceRef) createServiceRef {
    1.21 +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
    1.22      AssertAbstractMethod();
    1.23  }
    1.24  
    1.25  
    1.26 +- (void) gotResponse: (DNSServiceErrorType)errorCode {
    1.27 +    _gotResponse = YES;
    1.28 +    if (!_continuous)
    1.29 +        [self cancel];
    1.30 +    if (errorCode && errorCode != _error) {
    1.31 +        Log(@"%@ got error %i", self,errorCode);
    1.32 +        self.error = errorCode;
    1.33 +    }
    1.34 +}
    1.35 +
    1.36 +
    1.37  - (BOOL) start
    1.38  {
    1.39      if (_serviceRef)
    1.40 @@ -67,55 +78,60 @@
    1.41  
    1.42      if (_error)
    1.43          self.error = 0;
    1.44 +    _gotResponse = NO;
    1.45  
    1.46 +    if (!_usePrivateConnection) {
    1.47 +        _connection = [[MYDNSConnection sharedConnection] retain];
    1.48 +        if (!_connection) {
    1.49 +            self.error = kDNSServiceErr_Unknown;
    1.50 +            return NO;
    1.51 +        }
    1.52 +        _serviceRef = _connection.connectionRef;
    1.53 +    }
    1.54 +    
    1.55      // Ask the subclass to create a DNSServiceRef:
    1.56 -    _serviceRef = [self createServiceRef];
    1.57 +    _error = [self createServiceRef: &_serviceRef];
    1.58 +    if (_error) {
    1.59 +        _serviceRef = NULL;
    1.60 +        setObj(&_connection,nil);
    1.61 +        if (!_error)
    1.62 +            self.error = kDNSServiceErr_Unknown;
    1.63 +        LogTo(DNS,@"Failed to open %@ -- err=%i",self,_error);
    1.64 +        return NO;
    1.65 +    }
    1.66      
    1.67 -    if (_serviceRef) {
    1.68 -        // Wrap a CFSocket around the service's socket:
    1.69 -        CFSocketContext ctxt = { 0, self, CFRetain, CFRelease, NULL };
    1.70 -        _socket = CFSocketCreateWithNative(NULL, 
    1.71 -                                           DNSServiceRefSockFD(_serviceRef), 
    1.72 -                                           kCFSocketReadCallBack, 
    1.73 -                                           &serviceCallback, &ctxt);
    1.74 -        if( _socket ) {
    1.75 -            CFSocketSetSocketFlags(_socket, CFSocketGetSocketFlags(_socket) & ~kCFSocketCloseOnInvalidate);
    1.76 -            // Attach the socket to the runloop so the serviceCallback will be invoked:
    1.77 -            _socketSource = CFSocketCreateRunLoopSource(NULL, _socket, 0);
    1.78 -            if( _socketSource ) {
    1.79 -                CFRunLoopAddSource(CFRunLoopGetCurrent(), _socketSource, kCFRunLoopCommonModes);
    1.80 -                LogTo(DNS,@"Opening %@ -- service=%p",self,_serviceRef);
    1.81 -                return YES; // success
    1.82 -            }
    1.83 -        }
    1.84 -    }
    1.85 -    if (!_error)
    1.86 -        self.error = kDNSServiceErr_Unknown;
    1.87 -    LogTo(DNS,@"Failed to open %@ -- err=%i",self,_error);
    1.88 -    [self cancel];
    1.89 -    return NO;
    1.90 +    if (!_connection)
    1.91 +        _connection = [[MYDNSConnection alloc] initWithServiceRef: _serviceRef];
    1.92 +    
    1.93 +    LogTo(DNS,@"Started %@",self);
    1.94 +    return YES; // Succeeded
    1.95 +}
    1.96 +
    1.97 +
    1.98 +- (BOOL) waitForReply {
    1.99 +    if( ! _serviceRef )
   1.100 +        if( ! [self start] )
   1.101 +            return NO;
   1.102 +    // Run the runloop until there's either an error or a result:
   1.103 +    _gotResponse = NO;
   1.104 +    LogTo(DNS,@"Waiting for reply to %@...", self);
   1.105 +    while( !_gotResponse )
   1.106 +        if( ! [_connection processResult] )
   1.107 +            break;
   1.108 +    LogTo(DNS,@"    ...got reply");
   1.109 +    return (self.error==0);
   1.110  }
   1.111  
   1.112  
   1.113  - (void) cancel
   1.114  {
   1.115 -    [self retain];            // Prevents _socket's dealloc from releasing & deallocing me!
   1.116 -    if( _socketSource ) {
   1.117 -        CFRunLoopSourceInvalidate(_socketSource);
   1.118 -        CFRelease(_socketSource);
   1.119 -        _socketSource = NULL;
   1.120 -    }
   1.121 -    if( _socket ) {
   1.122 -        CFSocketInvalidate(_socket);
   1.123 -        CFRelease(_socket);
   1.124 -        _socket = NULL;
   1.125 -    }
   1.126      if( _serviceRef ) {
   1.127          LogTo(DNS,@"Stopped %@",self);
   1.128          DNSServiceRefDeallocate(_serviceRef);
   1.129          _serviceRef = NULL;
   1.130 +        
   1.131 +        setObj(&_connection,nil);
   1.132      }
   1.133 -    [self release];
   1.134  }
   1.135  
   1.136  
   1.137 @@ -127,31 +143,159 @@
   1.138  }
   1.139  
   1.140  
   1.141 -- (BOOL) priv_processResult
   1.142 ++ (NSString*) fullNameOfService: (NSString*)serviceName
   1.143 +                         ofType: (NSString*)type
   1.144 +                       inDomain: (NSString*)domain
   1.145  {
   1.146 -    Assert(_serviceRef);
   1.147 -    DNSServiceErrorType err = DNSServiceProcessResult(_serviceRef);
   1.148 -    if (err) {
   1.149 -        // An error here means the socket has failed and should be closed.
   1.150 -        self.error = err;
   1.151 -        [self cancel];
   1.152 -        return NO;
   1.153 -    } else {
   1.154 -        if (!_continuous)
   1.155 -            [self cancel];
   1.156 -        return YES;
   1.157 +    //FIX: Do I need to un-escape the serviceName?
   1.158 +    Assert(type);
   1.159 +    Assert(domain);
   1.160 +    char fullName[kDNSServiceMaxDomainName];
   1.161 +    if (DNSServiceConstructFullName(fullName, serviceName.UTF8String, type.UTF8String, domain.UTF8String) == 0)
   1.162 +        return [NSString stringWithUTF8String: fullName];
   1.163 +    else
   1.164 +        return nil;
   1.165 +}
   1.166 +
   1.167 +
   1.168 +@end
   1.169 +
   1.170 +
   1.171 +#pragma mark -
   1.172 +#pragma mark SHARED CONNECTION:
   1.173 +
   1.174 +
   1.175 +@interface MYDNSConnection ()
   1.176 +- (BOOL) open;
   1.177 +@end
   1.178 +
   1.179 +
   1.180 +@implementation MYDNSConnection
   1.181 +
   1.182 +
   1.183 +MYDNSConnection *sSharedConnection;
   1.184 +
   1.185 +
   1.186 +- (id) init
   1.187 +{
   1.188 +    DNSServiceRef connectionRef = NULL;
   1.189 +    DNSServiceErrorType err = DNSServiceCreateConnection(&connectionRef);
   1.190 +    if (err || !connectionRef) {
   1.191 +        Warn(@"MYDNSConnection: DNSServiceCreateConnection failed, err=%i", err);
   1.192 +        [self release];
   1.193 +        return nil;
   1.194 +    }
   1.195 +    return [self initWithServiceRef: connectionRef];
   1.196 +}
   1.197 +
   1.198 +
   1.199 +- (id) initWithServiceRef: (DNSServiceRef)serviceRef
   1.200 +{
   1.201 +    Assert(serviceRef);
   1.202 +    self = [super init];
   1.203 +    if (self != nil) {
   1.204 +        _connectionRef = serviceRef;
   1.205 +        LogTo(DNS,@"INIT %@", self);
   1.206 +        if (![self open]) {
   1.207 +            [self release];
   1.208 +            return nil;
   1.209 +        }
   1.210 +    }
   1.211 +    return self;
   1.212 +}
   1.213 +
   1.214 +
   1.215 ++ (MYDNSConnection*) sharedConnection {
   1.216 +    @synchronized(self) {
   1.217 +        if (!sSharedConnection)
   1.218 +            sSharedConnection = [[[self alloc] init] autorelease];
   1.219 +    }
   1.220 +    return sSharedConnection;
   1.221 +}
   1.222 +
   1.223 +
   1.224 +- (void) dealloc
   1.225 +{
   1.226 +    LogTo(DNS,@"DEALLOC %@", self);
   1.227 +    [self close];
   1.228 +    [super dealloc];
   1.229 +}
   1.230 +
   1.231 +- (void) finalize {
   1.232 +    [self close];
   1.233 +    [super finalize];
   1.234 +}
   1.235 +
   1.236 +
   1.237 +@synthesize connectionRef=_connectionRef;
   1.238 +
   1.239 +- (NSString*) description {
   1.240 +    return $sprintf(@"%@[conn=%p]", self.class,_connectionRef);
   1.241 +}
   1.242 +
   1.243 +- (BOOL) open {
   1.244 +    if (_runLoopSource)
   1.245 +        return YES;        // Already opened
   1.246 +    
   1.247 +    // Wrap a CFSocket around the service's socket:
   1.248 +    CFSocketContext ctxt = { 0, self, CFRetain, CFRelease, NULL };
   1.249 +    _socket = CFSocketCreateWithNative(NULL, 
   1.250 +                                                       DNSServiceRefSockFD(_connectionRef), 
   1.251 +                                                       kCFSocketReadCallBack, 
   1.252 +                                                       &serviceCallback, &ctxt);
   1.253 +    if( _socket ) {
   1.254 +        CFSocketSetSocketFlags(_socket, 
   1.255 +                               CFSocketGetSocketFlags(_socket) & ~kCFSocketCloseOnInvalidate);
   1.256 +        // Attach the socket to the runloop so the serviceCallback will be invoked:
   1.257 +        _runLoopSource = CFSocketCreateRunLoopSource(NULL, _socket, 0);
   1.258 +        if( _runLoopSource ) {
   1.259 +            CFRunLoopAddSource(CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes);
   1.260 +            // Success!
   1.261 +            LogTo(DNS,@"Successfully opened %@", self);
   1.262 +            return YES;
   1.263 +        }
   1.264 +    }
   1.265 +    
   1.266 +    // Failure:
   1.267 +    Warn(@"Failed to connect %@ to runloop", self);
   1.268 +    [self close];
   1.269 +    return NO;
   1.270 +}
   1.271 +
   1.272 +
   1.273 +- (void) close {
   1.274 +    @synchronized(self) {
   1.275 +        if( _runLoopSource ) {
   1.276 +            CFRunLoopSourceInvalidate(_runLoopSource);
   1.277 +            CFRelease(_runLoopSource);
   1.278 +            _runLoopSource = NULL;
   1.279 +        }
   1.280 +        if( _socket ) {
   1.281 +            CFSocketInvalidate(_socket);
   1.282 +            CFRelease(_socket);
   1.283 +            _socket = NULL;
   1.284 +        }
   1.285 +        if( _connectionRef ) {
   1.286 +            LogTo(DNS,@"Closed %@",self);
   1.287 +            DNSServiceRefDeallocate(_connectionRef);
   1.288 +            _connectionRef = NULL;
   1.289 +        }
   1.290 +        
   1.291 +        if (self==sSharedConnection)
   1.292 +            sSharedConnection = nil;
   1.293      }
   1.294  }
   1.295  
   1.296 -- (BOOL) waitForReply
   1.297 -{
   1.298 -    if (!_serviceRef)
   1.299 -        return NO;
   1.300 -    LogTo(DNS,@"Waiting for %@ ...", self);
   1.301 -    BOOL ok = [self priv_processResult];
   1.302 -    LogTo(DNS,@"    ...done waiting");
   1.303 -    return ok;
   1.304 -}    
   1.305 +
   1.306 +- (BOOL) processResult {
   1.307 +    NSAutoreleasePool *pool = [NSAutoreleasePool new];
   1.308 +    LogTo(DNS,@"---serviceCallback----");
   1.309 +    DNSServiceErrorType err = DNSServiceProcessResult(_connectionRef);
   1.310 +    if (err)
   1.311 +        Warn(@"%@: DNSServiceProcessResult failed, err=%i !!!", self,err);
   1.312 +    [pool drain];
   1.313 +    return !err;
   1.314 +}
   1.315  
   1.316  
   1.317  /** CFSocket callback, informing us that _socket has data available, which means
   1.318 @@ -161,11 +305,8 @@
   1.319                              CFSocketCallBackType type,
   1.320                              CFDataRef address, const void *data, void *clientCallBackInfo)
   1.321  {
   1.322 -    NSAutoreleasePool *pool = [NSAutoreleasePool new];
   1.323 -    @try{
   1.324 -        [(MYDNSService*)clientCallBackInfo priv_processResult];
   1.325 -    }catchAndReport(@"PortMapper serviceCallback");
   1.326 -    [pool drain];
   1.327 +    MYDNSConnection *connection = clientCallBackInfo;
   1.328 +    [connection processResult];
   1.329  }
   1.330  
   1.331