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