Rewrote the Bonjour classes, using the low-level <dns_sd.h> API. They're now subclasses of MYDNSService.
authorJens Alfke <jens@mooseyard.com>
Mon Apr 27 09:03:56 2009 -0700 (2009-04-27)
changeset 28732576fa8a0d
parent 27 92581f26073e
child 29 59689fbdcf77
Rewrote the Bonjour classes, using the low-level <dns_sd.h> API. They're now subclasses of MYDNSService.
Bonjour/MYAddressLookup.h
Bonjour/MYAddressLookup.m
Bonjour/MYBonjourBrowser.h
Bonjour/MYBonjourBrowser.m
Bonjour/MYBonjourQuery.h
Bonjour/MYBonjourQuery.m
Bonjour/MYBonjourService.h
Bonjour/MYBonjourService.m
IPAddress.h
IPAddress.m
MYNetwork.xcodeproj/project.pbxproj
PortMapper/MYDNSService.h
PortMapper/MYDNSService.m
PortMapper/MYPortMapper.m
PortMapper/PortMapperTest.m
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/Bonjour/MYAddressLookup.h	Mon Apr 27 09:03:56 2009 -0700
     1.3 @@ -0,0 +1,42 @@
     1.4 +//
     1.5 +//  MYAddressLookup.h
     1.6 +//  MYNetwork
     1.7 +//
     1.8 +//  Created by Jens Alfke on 4/24/09.
     1.9 +//  Copyright 2009 Jens Alfke. All rights reserved.
    1.10 +//
    1.11 +
    1.12 +#import "MYDNSService.h"
    1.13 +
    1.14 +
    1.15 +/** An asynchronous DNS address lookup. Supports both Bonjour services and traditional hostnames. */
    1.16 +@interface MYAddressLookup : MYDNSService
    1.17 +{
    1.18 +    NSString *_hostname;
    1.19 +    UInt16 _interfaceIndex;
    1.20 +    NSMutableSet *_addresses;
    1.21 +    UInt16 _port;
    1.22 +    CFAbsoluteTime _expires;
    1.23 +}
    1.24 +
    1.25 +/** Initializes the lookup with a DNS hostname. */
    1.26 +- (id) initWithHostname: (NSString*)hostname;
    1.27 +
    1.28 +/** The port number; this will be copied into the resulting IPAddress objects.
    1.29 +    Defaults to zero, but you can set it before calling -start. */
    1.30 +@property UInt16 port;
    1.31 +
    1.32 +/** The index of the network interface. You usually don't need to set this. */
    1.33 +@property UInt16 interfaceIndex;
    1.34 +
    1.35 +/** The resulting address(es) of the host, as HostAddress objects. */
    1.36 +@property (readonly) NSSet *addresses;
    1.37 +
    1.38 +/** How much longer the addresses will remain valid.
    1.39 +    If the value is zero, the addresses are no longer valid, and you should instead
    1.40 +    call -start again and wait for the 'addresses' property to update.
    1.41 +    If you set the service to continuous mode, addresses will never expire since the
    1.42 +    query will continue to update them. */
    1.43 +@property (readonly) NSTimeInterval timeToLive;
    1.44 +
    1.45 +@end
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/Bonjour/MYAddressLookup.m	Mon Apr 27 09:03:56 2009 -0700
     2.3 @@ -0,0 +1,122 @@
     2.4 +//
     2.5 +//  MYAddressLookup.m
     2.6 +//  MYNetwork
     2.7 +//
     2.8 +//  Created by Jens Alfke on 4/24/09.
     2.9 +//  Copyright 2009 Jens Alfke. All rights reserved.
    2.10 +//
    2.11 +
    2.12 +#import "MYAddressLookup.h"
    2.13 +#import "IPAddress.h"
    2.14 +#import "ExceptionUtils.h"
    2.15 +#import "Test.h"
    2.16 +#import "Logging.h"
    2.17 +#import <dns_sd.h>
    2.18 +
    2.19 +
    2.20 +@implementation MYAddressLookup
    2.21 +
    2.22 +- (id) initWithHostname: (NSString*)hostname
    2.23 +{
    2.24 +    self = [super init];
    2.25 +    if (self != nil) {
    2.26 +        if (!hostname) {
    2.27 +            [self release];
    2.28 +            return nil;
    2.29 +        }
    2.30 +        _hostname = [hostname copy];
    2.31 +        _addresses = [[NSMutableArray alloc] init];
    2.32 +    }
    2.33 +    return self;
    2.34 +}
    2.35 +
    2.36 +- (void) dealloc
    2.37 +{
    2.38 +    [_hostname release];
    2.39 +    [_addresses release];
    2.40 +    [super dealloc];
    2.41 +}
    2.42 +
    2.43 +
    2.44 +- (NSString*) description
    2.45 +{
    2.46 +    return $sprintf(@"%@[%@]", self.class,_hostname);
    2.47 +}
    2.48 +
    2.49 +
    2.50 +@synthesize port=_port, interfaceIndex=_interfaceIndex, addresses=_addresses;
    2.51 +
    2.52 +
    2.53 +- (NSTimeInterval) timeToLive {
    2.54 +    return MAX(0.0, _expires - CFAbsoluteTimeGetCurrent());
    2.55 +}
    2.56 +
    2.57 +
    2.58 +- (void) priv_resolvedAddress: (const struct sockaddr*)sockaddr
    2.59 +                          ttl: (uint32_t)ttl
    2.60 +                        flags: (DNSServiceFlags)flags
    2.61 +{
    2.62 +    HostAddress *address = [[HostAddress alloc] initWithHostname: _hostname 
    2.63 +                                                        sockaddr: sockaddr
    2.64 +                                                            port: _port];
    2.65 +    if (address) {
    2.66 +        if (flags & kDNSServiceFlagsAdd)
    2.67 +            [_addresses addObject: address];
    2.68 +        else
    2.69 +            [_addresses removeObject: address];
    2.70 +        [address release];
    2.71 +    }
    2.72 +    
    2.73 +    _expires = CFAbsoluteTimeGetCurrent() + ttl;
    2.74 +
    2.75 +    if (!(flags & kDNSServiceFlagsMoreComing))
    2.76 +        LogTo(DNS,@"Got addresses of %@: %@ [TTL = %u]", self, _addresses, ttl);
    2.77 +}
    2.78 +
    2.79 +
    2.80 +static void lookupCallback(DNSServiceRef                    sdRef,
    2.81 +                           DNSServiceFlags                  flags,
    2.82 +                           uint32_t                         interfaceIndex,
    2.83 +                           DNSServiceErrorType              errorCode,
    2.84 +                           const char                       *hostname,
    2.85 +                           const struct sockaddr            *address,
    2.86 +                           uint32_t                         ttl,
    2.87 +                           void                             *context)
    2.88 +{
    2.89 +    MYAddressLookup *lookup = context;
    2.90 +    @try{
    2.91 +        //LogTo(Bonjour, @"lookupCallback for %s (err=%i)", hostname,errorCode);
    2.92 +        if (errorCode)
    2.93 +            [lookup setError: errorCode];
    2.94 +        else
    2.95 +            [lookup priv_resolvedAddress: address ttl: ttl flags: flags];
    2.96 +    }catchAndReport(@"MYDNSLookup query callback");
    2.97 +}
    2.98 +
    2.99 +
   2.100 +- (DNSServiceRef) createServiceRef {
   2.101 +    [_addresses removeAllObjects];
   2.102 +    DNSServiceRef serviceRef = NULL;
   2.103 +    self.error = DNSServiceGetAddrInfo(&serviceRef, 0,
   2.104 +                                       _interfaceIndex, 0,
   2.105 +                                       _hostname.UTF8String,
   2.106 +                                       &lookupCallback, self);
   2.107 +    return serviceRef;
   2.108 +}
   2.109 +
   2.110 +
   2.111 +@end
   2.112 +
   2.113 +
   2.114 +
   2.115 +TestCase(MYDNSLookup) {
   2.116 +    EnableLogTo(Bonjour,YES);
   2.117 +    EnableLogTo(DNS,YES);
   2.118 +    [NSRunLoop currentRunLoop]; // create runloop
   2.119 +
   2.120 +    MYAddressLookup *lookup = [[MYAddressLookup alloc] initWithHostname: @"www.apple.com" port: 80];
   2.121 +    [lookup start];
   2.122 +    
   2.123 +    [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 10]];
   2.124 +    [lookup release];
   2.125 +}    
     3.1 --- a/Bonjour/MYBonjourBrowser.h	Fri Apr 24 10:10:32 2009 -0700
     3.2 +++ b/Bonjour/MYBonjourBrowser.h	Mon Apr 27 09:03:56 2009 -0700
     3.3 @@ -6,17 +6,15 @@
     3.4  //  Copyright 2008 Jens Alfke. All rights reserved.
     3.5  //
     3.6  
     3.7 -#import <Foundation/Foundation.h>
     3.8 +#import "MYDNSService.h"
     3.9  
    3.10  
    3.11  /** Searches for Bonjour services of a specific type. */
    3.12 -@interface MYBonjourBrowser : NSObject
    3.13 +@interface MYBonjourBrowser : MYDNSService
    3.14  {
    3.15      @private
    3.16      NSString *_serviceType;
    3.17 -    NSNetServiceBrowser *_browser;
    3.18      BOOL _browsing;
    3.19 -    NSError *_error;
    3.20      Class _serviceClass;
    3.21      NSMutableSet *_services, *_addServices, *_rmvServices;
    3.22  }
    3.23 @@ -26,19 +24,9 @@
    3.24      @param serviceType  The name of the service type to look for, e.g. "_http._tcp". */
    3.25  - (id) initWithServiceType: (NSString*)serviceType;
    3.26  
    3.27 -/** Starts browsing. This is asynchronous, so nothing will happen immediately. */
    3.28 -- (void) start;
    3.29 -
    3.30 -/** Stops browsing. */
    3.31 -- (void) stop;
    3.32 -
    3.33  /** Is the browser currently browsing? */
    3.34  @property (readonly) BOOL browsing;
    3.35  
    3.36 -/** The current error status, if any.
    3.37 -    This is KV-observable. */
    3.38 -@property (readonly,retain) NSError* error;
    3.39 -
    3.40  /** The set of currently found services. These are instances of the serviceClass,
    3.41      which is BonjourService by default.
    3.42      This is KV-observable. */
     4.1 --- a/Bonjour/MYBonjourBrowser.m	Fri Apr 24 10:10:32 2009 -0700
     4.2 +++ b/Bonjour/MYBonjourBrowser.m	Mon Apr 27 09:03:56 2009 -0700
     4.3 @@ -8,13 +8,24 @@
     4.4  
     4.5  #import "MYBonjourBrowser.h"
     4.6  #import "MYBonjourService.h"
     4.7 +#import "ExceptionUtils.h"
     4.8  #import "Test.h"
     4.9  #import "Logging.h"
    4.10 +#import <dns_sd.h>
    4.11  
    4.12  
    4.13 +static void browseCallback (DNSServiceRef                       sdRef,
    4.14 +                            DNSServiceFlags                     flags,
    4.15 +                            uint32_t                            interfaceIndex,
    4.16 +                            DNSServiceErrorType                 errorCode,
    4.17 +                            const char                          *serviceName,
    4.18 +                            const char                          *regtype,
    4.19 +                            const char                          *replyDomain,
    4.20 +                            void                                *context);
    4.21 +
    4.22  @interface MYBonjourBrowser ()
    4.23  @property BOOL browsing;
    4.24 -@property (retain) NSError* error;
    4.25 +- (void) _updateServiceList;
    4.26  @end
    4.27  
    4.28  
    4.29 @@ -26,9 +37,8 @@
    4.30      Assert(serviceType);
    4.31      self = [super init];
    4.32      if (self != nil) {
    4.33 +        self.continuous = YES;
    4.34          _serviceType = [serviceType copy];
    4.35 -        _browser = [[NSNetServiceBrowser alloc] init];
    4.36 -        _browser.delegate = self;
    4.37          _services = [[NSMutableSet alloc] init];
    4.38          _addServices = [[NSMutableSet alloc] init];
    4.39          _rmvServices = [[NSMutableSet alloc] init];
    4.40 @@ -41,11 +51,7 @@
    4.41  - (void) dealloc
    4.42  {
    4.43      LogTo(Bonjour,@"DEALLOC BonjourBrowser");
    4.44 -    [_browser stop];
    4.45 -    _browser.delegate = nil;
    4.46 -    [_browser release];
    4.47      [_serviceType release];
    4.48 -    [_error release];
    4.49      [_services release];
    4.50      [_addServices release];
    4.51      [_rmvServices release];
    4.52 @@ -53,40 +59,64 @@
    4.53  }
    4.54  
    4.55  
    4.56 -@synthesize browsing=_browsing, error=_error, services=_services, serviceClass=_serviceClass;
    4.57 +@synthesize browsing=_browsing, services=_services, serviceClass=_serviceClass;
    4.58  
    4.59  
    4.60 -- (void) start
    4.61 +- (NSString*) description
    4.62  {
    4.63 -    [_browser searchForServicesOfType: _serviceType inDomain: @"local."];
    4.64 +    return $sprintf(@"%@[%@]", self.class,_serviceType);
    4.65  }
    4.66  
    4.67 -- (void) stop
    4.68 -{
    4.69 -    [_browser stop];
    4.70 +
    4.71 +- (DNSServiceRef) createServiceRef {
    4.72 +    DNSServiceRef serviceRef = NULL;
    4.73 +    self.error = DNSServiceBrowse(&serviceRef, 0, 0,
    4.74 +                                  _serviceType.UTF8String, NULL,
    4.75 +                                  &browseCallback, self);
    4.76 +    return serviceRef;
    4.77  }
    4.78  
    4.79  
    4.80 -- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)netServiceBrowser
    4.81 -{
    4.82 -    LogTo(Bonjour,@"%@ started browsing",self);
    4.83 -    self.browsing = YES;
    4.84 +- (void) priv_gotError: (DNSServiceErrorType)errorCode {
    4.85 +    LogTo(Bonjour,@"%@ got error %i", self,errorCode);
    4.86 +    self.error = errorCode;
    4.87  }
    4.88  
    4.89 -- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)netServiceBrowser
    4.90 +- (void) priv_gotServiceName: (NSString*)serviceName
    4.91 +                        type: (NSString*)regtype
    4.92 +                      domain: (NSString*)domain
    4.93 +                   interface: (uint32_t)interfaceIndex
    4.94 +                       flags: (DNSServiceFlags)flags
    4.95  {
    4.96 -    LogTo(Bonjour,@"%@ stopped browsing",self);
    4.97 -    self.browsing = NO;
    4.98 -}
    4.99 -
   4.100 -- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser 
   4.101 -             didNotSearch:(NSDictionary *)errorDict
   4.102 -{
   4.103 -    NSString *domain = [errorDict objectForKey: NSNetServicesErrorDomain];
   4.104 -    int err = [[errorDict objectForKey: NSNetServicesErrorCode] intValue];
   4.105 -    self.error = [NSError errorWithDomain: domain code: err userInfo: nil];
   4.106 -    LogTo(Bonjour,@"%@ got error: ",self,self.error);
   4.107 -    self.browsing = NO;
   4.108 +    // Create (or reuse existing) MYBonjourService object:
   4.109 +    MYBonjourService *service = [[_serviceClass alloc] initWithName: serviceName
   4.110 +                                                               type: regtype
   4.111 +                                                             domain: domain
   4.112 +                                                          interface: interfaceIndex];
   4.113 +    MYBonjourService *existingService = [_services member: service];
   4.114 +    if( existingService ) {
   4.115 +        [service release];
   4.116 +        service = [existingService retain];
   4.117 +    }
   4.118 +    
   4.119 +    // Add it to the add/remove sets:
   4.120 +    NSMutableSet *addTo, *removeFrom;
   4.121 +    if (flags & kDNSServiceFlagsAdd) {
   4.122 +        addTo = _addServices;
   4.123 +        removeFrom = _rmvServices;
   4.124 +    } else {
   4.125 +        addTo = _rmvServices;
   4.126 +        removeFrom = _addServices;
   4.127 +    }
   4.128 +    if( [removeFrom containsObject: service] )
   4.129 +        [removeFrom removeObject: service];
   4.130 +    else
   4.131 +        [addTo addObject: service];
   4.132 +    [service release];
   4.133 +    
   4.134 +    // After a round of updates is done, do the update:
   4.135 +    if( ! (flags & kDNSServiceFlagsMoreComing) )
   4.136 +        [self _updateServiceList];
   4.137  }
   4.138  
   4.139  
   4.140 @@ -117,42 +147,26 @@
   4.141  }
   4.142  
   4.143  
   4.144 -- (void) _handleService: (NSNetService*)netService 
   4.145 -                  addTo: (NSMutableSet*)addTo
   4.146 -             removeFrom: (NSMutableSet*)removeFrom
   4.147 -             moreComing: (BOOL)moreComing
   4.148 +static void browseCallback (DNSServiceRef                       sdRef,
   4.149 +                            DNSServiceFlags                     flags,
   4.150 +                            uint32_t                            interfaceIndex,
   4.151 +                            DNSServiceErrorType                 errorCode,
   4.152 +                            const char                          *serviceName,
   4.153 +                            const char                          *regtype,
   4.154 +                            const char                          *replyDomain,
   4.155 +                            void                                *context)
   4.156  {
   4.157 -    // Wrap the NSNetService in a BonjourService, using an existing instance if possible:
   4.158 -    MYBonjourService *service = [[_serviceClass alloc] initWithNetService: netService];
   4.159 -    MYBonjourService *existingService = [_services member: service];
   4.160 -    if( existingService ) {
   4.161 -        [service release];
   4.162 -        service = [existingService retain];
   4.163 -    }
   4.164 -    
   4.165 -    if( [removeFrom containsObject: service] )
   4.166 -        [removeFrom removeObject: service];
   4.167 -    else
   4.168 -        [addTo addObject: service];
   4.169 -    [service release];
   4.170 -    if( ! moreComing )
   4.171 -        [self _updateServiceList];
   4.172 -}
   4.173 -
   4.174 -- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser 
   4.175 -           didFindService:(NSNetService *)netService
   4.176 -               moreComing:(BOOL)moreComing 
   4.177 -{
   4.178 -    //LogTo(Bonjour,@"Add service %@",netService);
   4.179 -    [self _handleService: netService addTo: _addServices removeFrom: _rmvServices moreComing: moreComing];
   4.180 -}
   4.181 -
   4.182 -- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser 
   4.183 -         didRemoveService:(NSNetService *)netService 
   4.184 -               moreComing:(BOOL)moreComing 
   4.185 -{
   4.186 -    //LogTo(Bonjour,@"Remove service %@",netService);
   4.187 -    [self _handleService: netService addTo: _rmvServices removeFrom: _addServices moreComing: moreComing];
   4.188 +    @try{
   4.189 +        //LogTo(Bonjour,@"browseCallback (error=%i, name='%s')", errorCode,serviceName);
   4.190 +        if (errorCode)
   4.191 +            [(MYBonjourBrowser*)context priv_gotError: errorCode];
   4.192 +        else
   4.193 +            [(MYBonjourBrowser*)context priv_gotServiceName: [NSString stringWithUTF8String: serviceName]
   4.194 +                                                       type: [NSString stringWithUTF8String: regtype]
   4.195 +                                                     domain: [NSString stringWithUTF8String: replyDomain]
   4.196 +                                                  interface: interfaceIndex
   4.197 +                                                      flags: flags];
   4.198 +    }catchAndReport(@"Bonjour");
   4.199  }
   4.200  
   4.201  
   4.202 @@ -163,6 +177,11 @@
   4.203  #pragma mark -
   4.204  #pragma mark TESTING:
   4.205  
   4.206 +#if DEBUG
   4.207 +
   4.208 +#import "MYBonjourQuery.h"
   4.209 +#import "MYAddressLookup.h"
   4.210 +
   4.211  @interface BonjourTester : NSObject
   4.212  {
   4.213      MYBonjourBrowser *_browser;
   4.214 @@ -175,7 +194,7 @@
   4.215  {
   4.216      self = [super init];
   4.217      if (self != nil) {
   4.218 -        _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_http._tcp"];
   4.219 +        _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_presence._tcp"];
   4.220          [_browser addObserver: self forKeyPath: @"services" options: NSKeyValueObservingOptionNew context: NULL];
   4.221          [_browser addObserver: self forKeyPath: @"browsing" options: NSKeyValueObservingOptionNew context: NULL];
   4.222          [_browser start];
   4.223 @@ -199,7 +218,11 @@
   4.224          if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) {
   4.225              NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey];
   4.226              for( MYBonjourService *service in newServices ) {
   4.227 -                LogTo(Bonjour,@"    --> %@ : TXT=%@", service,service.txtRecord);
   4.228 +                NSString *hostname = service.hostname;  // block till it's resolved
   4.229 +                Log(@"##### %@ : at %@:%hu, TXT=%@", 
   4.230 +                      service, hostname, service.port, service.txtRecord);
   4.231 +                service.addressLookup.continuous = YES;
   4.232 +                [service queryForRecord: kDNSServiceType_NULL];
   4.233              }
   4.234          }
   4.235      }
   4.236 @@ -208,12 +231,15 @@
   4.237  @end
   4.238  
   4.239  TestCase(Bonjour) {
   4.240 +    EnableLogTo(Bonjour,YES);
   4.241 +    EnableLogTo(DNS,YES);
   4.242      [NSRunLoop currentRunLoop]; // create runloop
   4.243      BonjourTester *tester = [[BonjourTester alloc] init];
   4.244 -    [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 15]];
   4.245 +    [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1500]];
   4.246      [tester release];
   4.247  }
   4.248  
   4.249 +#endif
   4.250  
   4.251  
   4.252  /*
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/Bonjour/MYBonjourQuery.h	Mon Apr 27 09:03:56 2009 -0700
     5.3 @@ -0,0 +1,35 @@
     5.4 +//
     5.5 +//  MYBonjourQuery.h
     5.6 +//  MYNetwork
     5.7 +//
     5.8 +//  Created by Jens Alfke on 4/24/09.
     5.9 +//  Copyright 2009 Jens Alfke. All rights reserved.
    5.10 +//
    5.11 +
    5.12 +#import "MYDNSService.h"
    5.13 +@class MYBonjourService;
    5.14 +
    5.15 +
    5.16 +/** A query for a particular DNS record (TXT, NULL, etc.) of a Bonjour service.
    5.17 +    This class is used internally by MYBonjourService to track the TXT record;
    5.18 +    you won't need to use it directly, unless you're interested in the contents of some other
    5.19 +    record (such as the NULL record that iChat's _presence._tcp service uses for buddy icons.) */
    5.20 +@interface MYBonjourQuery : MYDNSService 
    5.21 +{
    5.22 +    @private
    5.23 +    MYBonjourService *_bonjourService;
    5.24 +    uint16_t _recordType;
    5.25 +    NSData *_recordData;
    5.26 +}
    5.27 +
    5.28 +/** Initializes a query for a particular service and record type.
    5.29 +    @param service  The Bonjour service to query
    5.30 +    @param recordType  The DNS record type, e.g. kDNSServiceType_TXT; see the enum in <dns_sd.h>. */
    5.31 +- (id) initWithBonjourService: (MYBonjourService*)service 
    5.32 +                   recordType: (uint16_t)recordType;
    5.33 +
    5.34 +/** The data of the DNS record, once it's been found.
    5.35 +    This property is KV-observable. */
    5.36 +@property (readonly,copy) NSData *recordData;
    5.37 +
    5.38 +@end
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/Bonjour/MYBonjourQuery.m	Mon Apr 27 09:03:56 2009 -0700
     6.3 @@ -0,0 +1,135 @@
     6.4 +//
     6.5 +//  MYBonjourQuery.m
     6.6 +//  MYNetwork
     6.7 +//
     6.8 +//  Created by Jens Alfke on 4/24/09.
     6.9 +//  Copyright 2009 Jens Alfke. All rights reserved.
    6.10 +//
    6.11 +
    6.12 +#import "MYBonjourQuery.h"
    6.13 +#import "MYBonjourService.h"
    6.14 +#import "Test.h"
    6.15 +#import "Logging.h"
    6.16 +#import "ExceptionUtils.h"
    6.17 +#import <dns_sd.h>
    6.18 +
    6.19 +
    6.20 +static NSString* kRecordTypeNames[] = {
    6.21 +    @"0",
    6.22 +    @"A", //         = 1,      /* Host address. */
    6.23 +    @"NS", //        = 2,      /* Authoritative server. */
    6.24 +    @"MD", //        = 3,      /* Mail destination. */
    6.25 +    @"MF", //        = 4,      /* Mail forwarder. */
    6.26 +    @"CNAME", //     = 5,      /* Canonical name. */
    6.27 +    @"SOA", //       = 6,      /* Start of authority zone. */
    6.28 +    @"MB", //        = 7,      /* Mailbox domain name. */
    6.29 +    @"MG", //        = 8,      /* Mail group member. */
    6.30 +    @"MR", //        = 9,      /* Mail rename name. */
    6.31 +    @"NULL", //      = 10,     /* Null resource record. */
    6.32 +    @"WKS", //       = 11,     /* Well known service. */
    6.33 +    @"PTR", //       = 12,     /* Domain name pointer. */
    6.34 +    @"HINFO", //     = 13,     /* Host information. */
    6.35 +    @"MINFO", //     = 14,     /* Mailbox information. */
    6.36 +    @"MX", //        = 15,     /* Mail routing information. */
    6.37 +    @"TXT" //       = 16,     /* One or more text strings (NOT "zero or more..."). */
    6.38 +    // this isn't a complete list; it just includes the most common ones.
    6.39 +    // For the full list, see the "kDNSServiceType_..." constants in <dns_sd.h>.
    6.40 +};
    6.41 +
    6.42 +@interface MYBonjourQuery ()
    6.43 +@property (copy) NSData *recordData;
    6.44 +@end
    6.45 +
    6.46 +
    6.47 +@implementation MYBonjourQuery
    6.48 +
    6.49 +
    6.50 +- (id) initWithBonjourService: (MYBonjourService*)service recordType: (uint16_t)recordType;
    6.51 +{
    6.52 +    self = [super init];
    6.53 +    if (self) {
    6.54 +        _bonjourService = service;
    6.55 +        _recordType = recordType;
    6.56 +    }
    6.57 +    return self;
    6.58 +}
    6.59 +
    6.60 +- (void) dealloc
    6.61 +{
    6.62 +    [_recordData release];
    6.63 +    [super dealloc];
    6.64 +}
    6.65 +
    6.66 +
    6.67 +- (NSString*) description
    6.68 +{
    6.69 +    NSString *typeName;
    6.70 +    if (_recordType <= 16)
    6.71 +        typeName = kRecordTypeNames[_recordType];
    6.72 +    else
    6.73 +        typeName = $sprintf(@"%u", _recordType);
    6.74 +    return $sprintf(@"%@[%@ /%@]", self.class, _bonjourService.name, typeName);
    6.75 +}
    6.76 +
    6.77 +
    6.78 +@synthesize recordData=_recordData;
    6.79 +
    6.80 +
    6.81 +- (void) priv_gotRecordBytes: (const void *)rdata
    6.82 +                      length: (uint16_t)rdlen
    6.83 +                        type: (uint16_t)rrtype
    6.84 +                         ttl: (uint32_t)ttl
    6.85 +                       flags: (DNSServiceFlags)flags
    6.86 +{
    6.87 +    NSData *data = [NSData dataWithBytes: rdata length: rdlen];
    6.88 +    if (!$equal(data,_recordData)) {
    6.89 +        if (data.length <= 16)
    6.90 +            LogTo(Bonjour,@"%@ = %@", self, data);
    6.91 +        else
    6.92 +            LogTo(Bonjour,@"%@ = %@...", self, [data subdataWithRange: NSMakeRange(0,16)]);
    6.93 +        self.recordData = data;
    6.94 +    }
    6.95 +    [_bonjourService queryDidUpdate: self];
    6.96 +}
    6.97 +
    6.98 +
    6.99 +static void queryCallback( DNSServiceRef                       DNSServiceRef,
   6.100 +                           DNSServiceFlags                     flags,
   6.101 +                           uint32_t                            interfaceIndex,
   6.102 +                           DNSServiceErrorType                 errorCode,
   6.103 +                           const char                          *fullname,
   6.104 +                           uint16_t                            rrtype,
   6.105 +                           uint16_t                            rrclass,
   6.106 +                           uint16_t                            rdlen,
   6.107 +                           const void                          *rdata,
   6.108 +                           uint32_t                            ttl,
   6.109 +                           void                                *context)
   6.110 +{
   6.111 +    @try{
   6.112 +        //LogTo(Bonjour, @"queryCallback for %@ (err=%i)", context,errorCode);
   6.113 +        if (errorCode)
   6.114 +            [(MYBonjourQuery*)context setError: errorCode];
   6.115 +        else
   6.116 +            [(MYBonjourQuery*)context priv_gotRecordBytes: rdata
   6.117 +                                                      length: rdlen
   6.118 +                                                        type: rrtype
   6.119 +                                                         ttl: ttl
   6.120 +                                                       flags: flags];
   6.121 +    }catchAndReport(@"MYBonjourResolver query callback");
   6.122 +}
   6.123 +
   6.124 +
   6.125 +- (DNSServiceRef) createServiceRef {
   6.126 +    DNSServiceRef serviceRef = NULL;
   6.127 +    const char *fullName = _bonjourService.fullName.UTF8String;
   6.128 +    if (fullName)
   6.129 +        self.error = DNSServiceQueryRecord(&serviceRef, 0, 
   6.130 +                                           _bonjourService.interfaceIndex, 
   6.131 +                                           fullName,
   6.132 +                                           _recordType, kDNSServiceClass_IN, 
   6.133 +                                           &queryCallback, self);
   6.134 +    return serviceRef;
   6.135 +}
   6.136 +
   6.137 +
   6.138 +@end
     7.1 --- a/Bonjour/MYBonjourService.h	Fri Apr 24 10:10:32 2009 -0700
     7.2 +++ b/Bonjour/MYBonjourService.h	Mon Apr 27 09:03:56 2009 -0700
     7.3 @@ -6,54 +6,72 @@
     7.4  //  Copyright 2008 Jens Alfke. All rights reserved.
     7.5  //
     7.6  
     7.7 -#import <Foundation/Foundation.h>
     7.8 +#import "MYDNSService.h"
     7.9  #import "ConcurrentOperation.h"
    7.10 -@class MYBonjourResolveOperation;
    7.11 +@class MYBonjourQuery, MYAddressLookup;
    7.12  
    7.13  
    7.14  /** Represents a Bonjour service discovered by a BonjourBrowser. */
    7.15 -@interface MYBonjourService : NSObject 
    7.16 +@interface MYBonjourService : MYDNSService 
    7.17  {
    7.18      @private
    7.19 -    NSNetService *_netService;
    7.20 +    NSString *_name, *_fullName, *_type, *_domain, *_hostname;
    7.21 +    uint32_t _interfaceIndex;
    7.22 +    BOOL _startedResolve;
    7.23 +    UInt16 _port;
    7.24      NSDictionary *_txtRecord;
    7.25 -    NSSet *_addresses;
    7.26 -    CFAbsoluteTime _addressesExpireAt;
    7.27 -    MYBonjourResolveOperation *_resolveOp;
    7.28 +    MYBonjourQuery *_txtQuery;
    7.29 +    MYAddressLookup *_addressLookup;
    7.30  }
    7.31  
    7.32  /** The service's name. */
    7.33  @property (readonly) NSString *name;
    7.34  
    7.35 +/** The service's type. */
    7.36 +@property (readonly) NSString *type;
    7.37 +
    7.38 +/** The service's domain. */
    7.39 +@property (readonly) NSString *domain;
    7.40 +
    7.41 +@property (readonly, copy) NSString *hostname;
    7.42 +
    7.43 +@property (readonly) UInt16 port;
    7.44 +
    7.45 +@property (readonly) uint32_t interfaceIndex;
    7.46 +
    7.47 +@property (readonly,copy) NSString* fullName;
    7.48 +
    7.49  /** The service's metadata dictionary, from its DNS TXT record */
    7.50  @property (readonly,copy) NSDictionary *txtRecord;
    7.51  
    7.52  /** A convenience to access a single property from the TXT record. */
    7.53  - (NSString*) txtStringForKey: (NSString*)key;
    7.54  
    7.55 -/** Returns a set of IPAddress objects; may be the empty set if address resolution failed,
    7.56 -    or nil if addresses have not been resolved yet (or expired).
    7.57 -    In the latter case, call -resolve and wait for the returned Operation to finish. */
    7.58 -@property (readonly,copy) NSSet* addresses;
    7.59 +/** Returns a MYDNSLookup object that resolves the IP address(es) of this service.
    7.60 +    Subsequent calls to this method will always return the same object. */
    7.61 +- (MYAddressLookup*) addressLookup;
    7.62  
    7.63 -/** Starts looking up the IP address(es) of this service.
    7.64 -    @return  The NSOperation representing the lookup; you can observe this to see when it
    7.65 -        completes, or you can observe the service's 'addresses' property. */
    7.66 -- (MYBonjourResolveOperation*) resolve;
    7.67 -
    7.68 -/** The underlying NSNetSerice object. */
    7.69 -@property (readonly) NSNetService *netService;
    7.70 +/** Starts a new MYBonjourQuery for the specified DNS record type of this service.
    7.71 +    @param recordType  The DNS record type, e.g. kDNSServiceType_TXT; see the enum in <dns_sd.h>. */
    7.72 +- (MYBonjourQuery*) queryForRecord: (UInt16)recordType;
    7.73  
    7.74  
    7.75  // Protected methods, for subclass use only:
    7.76  
    7.77 -- (id) initWithNetService: (NSNetService*)netService;
    7.78 +// (for subclasses to override, but not call):
    7.79 +- (id) initWithName: (NSString*)serviceName
    7.80 +               type: (NSString*)type
    7.81 +             domain: (NSString*)domain
    7.82 +          interface: (uint32_t)interfaceIndex;
    7.83  
    7.84 -// (for subclasses to override, but not call):
    7.85  - (void) added;
    7.86  - (void) removed;
    7.87  - (void) txtRecordChanged;
    7.88  
    7.89 +// Internal:
    7.90 +
    7.91 +- (void) queryDidUpdate: (MYBonjourQuery*)query;
    7.92 +
    7.93  @end
    7.94  
    7.95  
     8.1 --- a/Bonjour/MYBonjourService.m	Fri Apr 24 10:10:32 2009 -0700
     8.2 +++ b/Bonjour/MYBonjourService.m	Mon Apr 27 09:03:56 2009 -0700
     8.3 @@ -7,84 +7,120 @@
     8.4  //
     8.5  
     8.6  #import "MYBonjourService.h"
     8.7 +#import "MYBonjourQuery.h"
     8.8 +#import "MYAddressLookup.h"
     8.9  #import "IPAddress.h"
    8.10  #import "ConcurrentOperation.h"
    8.11  #import "Test.h"
    8.12  #import "Logging.h"
    8.13 +#import "ExceptionUtils.h"
    8.14 +#import <dns_sd.h>
    8.15  
    8.16  
    8.17  NSString* const kBonjourServiceResolvedAddressesNotification = @"BonjourServiceResolvedAddresses";
    8.18  
    8.19  
    8.20  @interface MYBonjourService ()
    8.21 -@property (copy) NSSet* addresses;
    8.22  @end
    8.23  
    8.24 -@interface MYBonjourResolveOperation ()
    8.25 -@property (assign) MYBonjourService *service;
    8.26 -@property (retain) NSSet *addresses;
    8.27 -@end
    8.28 -
    8.29 -
    8.30  
    8.31  @implementation MYBonjourService
    8.32  
    8.33  
    8.34 -- (id) initWithNetService: (NSNetService*)netService
    8.35 +- (id) initWithName: (NSString*)serviceName
    8.36 +               type: (NSString*)type
    8.37 +             domain: (NSString*)domain
    8.38 +          interface: (uint32)interfaceIndex
    8.39  {
    8.40      self = [super init];
    8.41      if (self != nil) {
    8.42 -        _netService = [netService retain];
    8.43 -        _netService.delegate = self;
    8.44 +        _name = [serviceName copy];
    8.45 +        _type = [type copy];
    8.46 +        _domain = [domain copy];
    8.47 +        _interfaceIndex = interfaceIndex;
    8.48      }
    8.49      return self;
    8.50  }
    8.51  
    8.52 -- (void) dealloc
    8.53 -{
    8.54 -    Log(@"DEALLOC %@",self);
    8.55 -    _netService.delegate = nil;
    8.56 -    [_netService release];
    8.57 -    [_txtRecord release];
    8.58 -    [_addresses release];
    8.59 +- (void) dealloc {
    8.60 +    [_name release];
    8.61 +    [_type release];
    8.62 +    [_domain release];
    8.63 +    [_hostname release];
    8.64 +    [_txtQuery stop];
    8.65 +    [_txtQuery release];
    8.66 +    [_addressLookup stop];
    8.67 +    [_addressLookup release];
    8.68      [super dealloc];
    8.69  }
    8.70  
    8.71  
    8.72 -- (NSString*) description
    8.73 -{
    8.74 -    return $sprintf(@"%@['%@'.%@%@]", self.class,self.name,_netService.type,_netService.domain);
    8.75 +@synthesize name=_name, type=_type, domain=_domain, interfaceIndex=_interfaceIndex;
    8.76 +
    8.77 +
    8.78 +- (NSString*) description {
    8.79 +    return $sprintf(@"%@['%@'.%@%@]", self.class,_name,_type,_domain);
    8.80  }
    8.81  
    8.82  
    8.83 -- (NSComparisonResult) compare: (id)obj
    8.84 -{
    8.85 -    return [self.name caseInsensitiveCompare: [obj name]];
    8.86 +- (NSComparisonResult) compare: (id)obj {
    8.87 +    return [_name caseInsensitiveCompare: [obj name]];
    8.88  }
    8.89  
    8.90 -
    8.91 -- (NSNetService*) netService        {return _netService;}
    8.92 -- (BOOL) isEqual: (id)obj           {return [obj isKindOfClass: [MYBonjourService class]] && [_netService isEqual: [obj netService]];}
    8.93 -- (NSUInteger) hash                 {return _netService.hash;}
    8.94 -- (NSString*) name                  {return _netService.name;}
    8.95 -
    8.96 -
    8.97 -- (void) added
    8.98 -{
    8.99 -    LogTo(Bonjour,@"Added %@",_netService);
   8.100 +- (BOOL) isEqual: (id)obj {
   8.101 +    if ([obj isKindOfClass: [MYBonjourService class]]) {
   8.102 +        MYBonjourService *service = obj;
   8.103 +        return [_name caseInsensitiveCompare: [service name]] == 0
   8.104 +            && $equal(_type, service->_type)
   8.105 +            && $equal(_domain, service->_domain)
   8.106 +            && _interfaceIndex == service->_interfaceIndex;
   8.107 +    } else {
   8.108 +        return NO;
   8.109 +    }
   8.110  }
   8.111  
   8.112 -- (void) removed
   8.113 -{
   8.114 -    LogTo(Bonjour,@"Removed %@",_netService);
   8.115 -    [_netService stopMonitoring];
   8.116 -    _netService.delegate = nil;
   8.117 +- (NSUInteger) hash {
   8.118 +    return _name.hash ^ _type.hash ^ _domain.hash;
   8.119 +}
   8.120 +
   8.121 +
   8.122 +- (void) added {
   8.123 +    LogTo(Bonjour,@"Added %@",self);
   8.124 +}
   8.125 +
   8.126 +- (void) removed {
   8.127 +    LogTo(Bonjour,@"Removed %@",self);
   8.128 +    [self stop];
   8.129      
   8.130 -    if( _resolveOp ) {
   8.131 -        [_resolveOp cancel];
   8.132 -        [_resolveOp release];
   8.133 -        _resolveOp = nil;
   8.134 -    }
   8.135 +    [_txtQuery stop];
   8.136 +    [_txtQuery release];
   8.137 +    _txtQuery = nil;
   8.138 +    
   8.139 +    [_addressLookup stop];
   8.140 +}
   8.141 +
   8.142 +
   8.143 +- (void) priv_finishResolve {
   8.144 +    // If I haven't finished my resolve yet, run it synchronously now so I can return a valid value:
   8.145 +    if (!_startedResolve )
   8.146 +        [self start];
   8.147 +    if (self.serviceRef)
   8.148 +        [self waitForReply];
   8.149 +}    
   8.150 +
   8.151 +- (NSString*) fullName {
   8.152 +    if (!_fullName) [self priv_finishResolve];
   8.153 +    return _fullName;
   8.154 +}
   8.155 +
   8.156 +- (NSString*) hostname {
   8.157 +    if (!_hostname) [self priv_finishResolve];
   8.158 +    return _hostname;
   8.159 +}
   8.160 +
   8.161 +- (UInt16) port {
   8.162 +    if (!_port) [self priv_finishResolve];
   8.163 +    return _port;
   8.164  }
   8.165  
   8.166  
   8.167 @@ -92,19 +128,18 @@
   8.168  #pragma mark TXT RECORD:
   8.169  
   8.170  
   8.171 -- (NSDictionary*) txtRecord
   8.172 -{
   8.173 -    [_netService startMonitoring];
   8.174 +- (NSDictionary*) txtRecord {
   8.175 +    // If I haven't started my resolve yet, start it now. (_txtRecord will be nil till it finishes.)
   8.176 +    if (!_startedResolve)
   8.177 +        [self start];
   8.178      return _txtRecord;
   8.179  }
   8.180  
   8.181 -- (void) txtRecordChanged
   8.182 -{
   8.183 +- (void) txtRecordChanged {
   8.184      // no-op (this is here for subclassers to override)
   8.185  }
   8.186  
   8.187 -- (NSString*) txtStringForKey: (NSString*)key
   8.188 -{
   8.189 +- (NSString*) txtStringForKey: (NSString*)key {
   8.190      NSData *value = [self.txtRecord objectForKey: key];
   8.191      if( ! value )
   8.192          return nil;
   8.193 @@ -118,95 +153,112 @@
   8.194      return [str autorelease];
   8.195  }
   8.196  
   8.197 -
   8.198 -- (void)netService:(NSNetService *)sender didUpdateTXTRecordData:(NSData *)data
   8.199 -{
   8.200 -    NSDictionary *txtDict = [NSNetService dictionaryFromTXTRecordData: data];
   8.201 -    if( ! $equal(txtDict,_txtRecord) ) {
   8.202 -        LogTo(Bonjour,@"%@ got TXT record (%u bytes)",self,data.length);
   8.203 +- (void) setTxtData: (NSData*)txtData {
   8.204 +    NSDictionary *txtRecord = txtData ?[NSNetService dictionaryFromTXTRecordData: txtData] :nil;
   8.205 +    if (!$equal(txtRecord,_txtRecord)) {
   8.206 +        LogTo(Bonjour,@"%@ TXT = %@", self,txtRecord);
   8.207          [self willChangeValueForKey: @"txtRecord"];
   8.208 -        setObj(&_txtRecord,txtDict);
   8.209 +        setObj(&_txtRecord, txtRecord);
   8.210          [self didChangeValueForKey: @"txtRecord"];
   8.211          [self txtRecordChanged];
   8.212      }
   8.213  }
   8.214  
   8.215  
   8.216 -#pragma mark -
   8.217 -#pragma mark ADDRESS RESOLUTION:
   8.218 -
   8.219 -
   8.220 -#define kAddressResolveTimeout      10.0
   8.221 -#define kAddressExpirationInterval  60.0
   8.222 -#define kAddressErrorRetryInterval   5.0
   8.223 -
   8.224 -
   8.225 -- (NSSet*) addresses
   8.226 -{
   8.227 -    if( _addresses && CFAbsoluteTimeGetCurrent() >= _addressesExpireAt ) {
   8.228 -        setObj(&_addresses,nil);            // eww, toss 'em and get new ones
   8.229 -        [self resolve];
   8.230 -    }
   8.231 -    return _addresses;
   8.232 +- (void) queryDidUpdate: (MYBonjourQuery*)query {
   8.233 +    if (query==_txtQuery)
   8.234 +        [self setTxtData: query.recordData];
   8.235  }
   8.236  
   8.237  
   8.238 -- (MYBonjourResolveOperation*) resolve
   8.239 +#pragma mark -
   8.240 +#pragma mark FULLNAME/HOSTNAME/PORT RESOLUTION:
   8.241 +
   8.242 +
   8.243 +- (void) priv_resolvedFullName: (NSString*)fullName
   8.244 +                      hostname: (NSString*)hostname
   8.245 +                          port: (uint16_t)port
   8.246 +                     txtRecord: (NSData*)txtData
   8.247  {
   8.248 -    if( ! _resolveOp ) {
   8.249 -        LogTo(Bonjour,@"Resolving %@",self);
   8.250 -        _resolveOp = [[MYBonjourResolveOperation alloc] init];
   8.251 -        _resolveOp.service = self;
   8.252 -        [_resolveOp start];
   8.253 -        Assert(_netService);
   8.254 -        Assert(_netService.delegate=self);
   8.255 -        [_netService resolveWithTimeout: kAddressResolveTimeout];
   8.256 -    }
   8.257 -    return _resolveOp;
   8.258 +    LogTo(Bonjour, @"%@: fullname='%@', hostname=%@, port=%u, txt=%u bytes", 
   8.259 +          self, fullName, hostname, port, txtData.length);
   8.260 +
   8.261 +    // Don't call a setter method to set these properties: the getters are synchronous, so
   8.262 +    // I might already be blocked in a call to one of them, in which case creating a KV
   8.263 +    // notification could cause trouble...
   8.264 +    _fullName = fullName.copy;
   8.265 +    _hostname = hostname.copy;
   8.266 +    _port = port;
   8.267 +    
   8.268 +    // TXT getter is async, though, so I can use a setter to announce the data's availability:
   8.269 +    [self setTxtData: txtData];
   8.270 +    
   8.271 +    // Now that I know my full name, I can start a persistent query to track the TXT:
   8.272 +    _txtQuery = [[MYBonjourQuery alloc] initWithBonjourService: self 
   8.273 +                                                    recordType: kDNSServiceType_TXT];
   8.274 +    _txtQuery.continuous = YES;
   8.275 +    [_txtQuery start];
   8.276  }
   8.277  
   8.278 -- (void) setAddresses: (NSSet*)addresses
   8.279 +
   8.280 +static void resolveCallback(DNSServiceRef                       sdRef,
   8.281 +                            DNSServiceFlags                     flags,
   8.282 +                            uint32_t                            interfaceIndex,
   8.283 +                            DNSServiceErrorType                 errorCode,
   8.284 +                            const char                          *fullname,
   8.285 +                            const char                          *hosttarget,
   8.286 +                            uint16_t                            port,
   8.287 +                            uint16_t                            txtLen,
   8.288 +                            const unsigned char                 *txtRecord,
   8.289 +                            void                                *context)
   8.290  {
   8.291 -    setObj(&_addresses,addresses);
   8.292 +    MYBonjourService *service = context;
   8.293 +    @try{
   8.294 +        //LogTo(Bonjour, @"resolveCallback for %@ (err=%i)", service,errorCode);
   8.295 +        if (errorCode) {
   8.296 +            [service setError: errorCode];
   8.297 +        } else {
   8.298 +            NSData *txtData = nil;
   8.299 +            if (txtRecord)
   8.300 +                txtData = [NSData dataWithBytes: txtRecord length: txtLen];
   8.301 +            [service priv_resolvedFullName: [NSString stringWithUTF8String: fullname]
   8.302 +                                  hostname: [NSString stringWithUTF8String: hosttarget]
   8.303 +                                      port: ntohs(port)
   8.304 +                                 txtRecord: txtData];
   8.305 +        }
   8.306 +    }catchAndReport(@"MYBonjourResolver query callback");
   8.307  }
   8.308  
   8.309  
   8.310 -- (void) _finishedResolving: (NSSet*)addresses expireIn: (NSTimeInterval)expirationInterval
   8.311 -{
   8.312 -    _addressesExpireAt = CFAbsoluteTimeGetCurrent() + expirationInterval;
   8.313 -    self.addresses = addresses;
   8.314 -    _resolveOp.addresses = addresses;
   8.315 -    [_resolveOp finish];
   8.316 -    [_resolveOp release];
   8.317 -    _resolveOp = nil;
   8.318 +- (DNSServiceRef) createServiceRef {
   8.319 +    _startedResolve = YES;
   8.320 +    DNSServiceRef serviceRef = NULL;
   8.321 +    self.error = DNSServiceResolve(&serviceRef, 0,
   8.322 +                                   _interfaceIndex, 
   8.323 +                                   _name.UTF8String, _type.UTF8String, _domain.UTF8String,
   8.324 +                                   &resolveCallback, self);
   8.325 +    return serviceRef;
   8.326  }
   8.327  
   8.328  
   8.329 -- (void)netServiceDidResolveAddress:(NSNetService *)sender
   8.330 -{
   8.331 -    // Convert the raw sockaddrs into IPAddress objects:
   8.332 -    NSMutableSet *addresses = [NSMutableSet setWithCapacity: 2];
   8.333 -    for( NSData *rawAddr in _netService.addresses ) {
   8.334 -        IPAddress *addr = [[IPAddress alloc] initWithSockAddr: rawAddr.bytes];
   8.335 -        if( addr ) {
   8.336 -            [addresses addObject: addr];
   8.337 -            [addr release];
   8.338 -        }
   8.339 +- (MYAddressLookup*) addressLookup {
   8.340 +    if (!_addressLookup) {
   8.341 +        // Create the lookup the first time this is called:
   8.342 +        _addressLookup = [[MYAddressLookup alloc] initWithHostname: self.hostname];
   8.343 +        _addressLookup.port = _port;
   8.344 +        _addressLookup.interfaceIndex = _interfaceIndex;
   8.345      }
   8.346 -    LogTo(Bonjour,@"Resolved %@: %@",self,addresses);
   8.347 -    [self _finishedResolving: addresses expireIn: kAddressExpirationInterval];
   8.348 +    // (Re)start the lookup if it's expired:
   8.349 +    if (_addressLookup && _addressLookup.timeToLive <= 0.0)
   8.350 +        [_addressLookup start];
   8.351 +    return _addressLookup;
   8.352  }
   8.353  
   8.354 -- (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict
   8.355 -{
   8.356 -    LogTo(Bonjour,@"Error resolving %@ -- %@",self,errorDict);
   8.357 -    [self _finishedResolving: [NSArray array] expireIn: kAddressErrorRetryInterval];
   8.358 -}
   8.359  
   8.360 -- (void)netServiceDidStop:(NSNetService *)sender
   8.361 -{
   8.362 -    LogTo(Bonjour,@"Resolve stopped for %@",self);
   8.363 -    [self _finishedResolving: [NSArray array] expireIn: kAddressErrorRetryInterval];
   8.364 +- (MYBonjourQuery*) queryForRecord: (UInt16)recordType {
   8.365 +    MYBonjourQuery *query = [[[MYBonjourQuery alloc] initWithBonjourService: self recordType: recordType]
   8.366 +                                 autorelease];
   8.367 +    return [query start] ?query :nil;
   8.368  }
   8.369  
   8.370  
   8.371 @@ -214,20 +266,6 @@
   8.372  
   8.373  
   8.374  
   8.375 -
   8.376 -@implementation MYBonjourResolveOperation
   8.377 -
   8.378 -@synthesize service=_service, addresses=_addresses;
   8.379 -
   8.380 -- (void) dealloc
   8.381 -{
   8.382 -    [_addresses release];
   8.383 -    [super dealloc];
   8.384 -}
   8.385 -
   8.386 -@end
   8.387 -
   8.388 -
   8.389  /*
   8.390   Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   8.391   
     9.1 --- a/IPAddress.h	Fri Apr 24 10:10:32 2009 -0700
     9.2 +++ b/IPAddress.h	Mon Apr 27 09:03:56 2009 -0700
     9.3 @@ -80,6 +80,10 @@
     9.4  
     9.5  - (id) initWithHostname: (NSString*)hostname port: (UInt16)port;
     9.6  
     9.7 +- (id) initWithHostname: (NSString*)hostname
     9.8 +               sockaddr: (const struct sockaddr*)sockaddr
     9.9 +                   port: (UInt16)port;
    9.10 +
    9.11  @end
    9.12  
    9.13  
    10.1 --- a/IPAddress.m	Fri Apr 24 10:10:32 2009 -0700
    10.2 +++ b/IPAddress.m	Mon Apr 27 09:03:56 2009 -0700
    10.3 @@ -233,6 +233,22 @@
    10.4      return self;
    10.5  }
    10.6  
    10.7 +- (id) initWithHostname: (NSString*)hostname
    10.8 +               sockaddr: (const struct sockaddr*)sockaddr
    10.9 +                   port: (UInt16)port;
   10.10 +{
   10.11 +    if( [hostname length]==0 ) {
   10.12 +        [self release];
   10.13 +        return nil;
   10.14 +    }
   10.15 +    self = [super initWithSockAddr: sockaddr];
   10.16 +    if( self ) {
   10.17 +        _hostname = [hostname copy];
   10.18 +        _port = port;
   10.19 +    }
   10.20 +    return self;
   10.21 +}    
   10.22 +
   10.23  
   10.24  - (void)encodeWithCoder:(NSCoder *)coder
   10.25  {
   10.26 @@ -256,6 +272,18 @@
   10.27  }
   10.28  
   10.29  
   10.30 +- (NSString*) description
   10.31 +{
   10.32 +    NSMutableString *desc = [_hostname mutableCopy];
   10.33 +    NSString *addr = self.ipv4name;
   10.34 +    if (addr)
   10.35 +        [desc appendFormat: @"(%@)", addr];
   10.36 +    if( _port )
   10.37 +        [desc appendFormat: @":%hu",_port];
   10.38 +    return desc;
   10.39 +}
   10.40 +
   10.41 +
   10.42  - (NSUInteger) hash
   10.43  {
   10.44      return [_hostname hash] ^ _port;
    11.1 --- a/MYNetwork.xcodeproj/project.pbxproj	Fri Apr 24 10:10:32 2009 -0700
    11.2 +++ b/MYNetwork.xcodeproj/project.pbxproj	Mon Apr 27 09:03:56 2009 -0700
    11.3 @@ -33,6 +33,12 @@
    11.4  		2780F20C0FA194BD00C0FB83 /* MYDNSService.h in Headers */ = {isa = PBXBuildFile; fileRef = 2780F20A0FA194BD00C0FB83 /* MYDNSService.h */; };
    11.5  		2780F20D0FA194BD00C0FB83 /* MYDNSService.m in Sources */ = {isa = PBXBuildFile; fileRef = 2780F20B0FA194BD00C0FB83 /* MYDNSService.m */; };
    11.6  		2780F20E0FA194BD00C0FB83 /* MYDNSService.m in Sources */ = {isa = PBXBuildFile; fileRef = 2780F20B0FA194BD00C0FB83 /* MYDNSService.m */; };
    11.7 +		2780F4380FA28F4400C0FB83 /* MYBonjourQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 2780F4360FA28F4400C0FB83 /* MYBonjourQuery.h */; };
    11.8 +		2780F4390FA28F4400C0FB83 /* MYBonjourQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 2780F4370FA28F4400C0FB83 /* MYBonjourQuery.m */; };
    11.9 +		2780F43A0FA28F4400C0FB83 /* MYBonjourQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 2780F4370FA28F4400C0FB83 /* MYBonjourQuery.m */; };
   11.10 +		2780F4A10FA2C59000C0FB83 /* MYAddressLookup.h in Headers */ = {isa = PBXBuildFile; fileRef = 2780F49F0FA2C59000C0FB83 /* MYAddressLookup.h */; };
   11.11 +		2780F4A20FA2C59000C0FB83 /* MYAddressLookup.m in Sources */ = {isa = PBXBuildFile; fileRef = 2780F4A00FA2C59000C0FB83 /* MYAddressLookup.m */; };
   11.12 +		2780F4A30FA2C59000C0FB83 /* MYAddressLookup.m in Sources */ = {isa = PBXBuildFile; fileRef = 2780F4A00FA2C59000C0FB83 /* MYAddressLookup.m */; };
   11.13  		278C1A3D0F9F687800954AE1 /* PortMapperTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 278C1A340F9F687800954AE1 /* PortMapperTest.m */; };
   11.14  		278C1A3E0F9F687800954AE1 /* MYPortMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 278C1A360F9F687800954AE1 /* MYPortMapper.m */; };
   11.15  		278C1BA60F9F92EA00954AE1 /* MYBonjourBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = 278C1B9F0F9F92EA00954AE1 /* MYBonjourBrowser.m */; };
   11.16 @@ -172,8 +178,12 @@
   11.17  		277904280DE91C7900C6D295 /* BLIP Echo Client-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "BLIP Echo Client-Info.plist"; sourceTree = "<group>"; };
   11.18  		2779048A0DE9204300C6D295 /* BLIPEchoClient.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BLIPEchoClient.xib; sourceTree = "<group>"; };
   11.19  		2779052D0DE9E5BC00C6D295 /* BLIPEchoServer */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = BLIPEchoServer; sourceTree = BUILT_PRODUCTS_DIR; };
   11.20 -		2780F20A0FA194BD00C0FB83 /* MYDNSService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYDNSService.h; sourceTree = "<group>"; };
   11.21 -		2780F20B0FA194BD00C0FB83 /* MYDNSService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYDNSService.m; sourceTree = "<group>"; };
   11.22 +		2780F20A0FA194BD00C0FB83 /* MYDNSService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYDNSService.h; path = PortMapper/MYDNSService.h; sourceTree = "<group>"; };
   11.23 +		2780F20B0FA194BD00C0FB83 /* MYDNSService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYDNSService.m; path = PortMapper/MYDNSService.m; sourceTree = "<group>"; };
   11.24 +		2780F4360FA28F4400C0FB83 /* MYBonjourQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYBonjourQuery.h; sourceTree = "<group>"; };
   11.25 +		2780F4370FA28F4400C0FB83 /* MYBonjourQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYBonjourQuery.m; sourceTree = "<group>"; };
   11.26 +		2780F49F0FA2C59000C0FB83 /* MYAddressLookup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYAddressLookup.h; sourceTree = "<group>"; };
   11.27 +		2780F4A00FA2C59000C0FB83 /* MYAddressLookup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYAddressLookup.m; sourceTree = "<group>"; };
   11.28  		278C1A340F9F687800954AE1 /* PortMapperTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PortMapperTest.m; sourceTree = "<group>"; };
   11.29  		278C1A350F9F687800954AE1 /* MYPortMapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYPortMapper.h; sourceTree = "<group>"; };
   11.30  		278C1A360F9F687800954AE1 /* MYPortMapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYPortMapper.m; sourceTree = "<group>"; };
   11.31 @@ -283,12 +293,11 @@
   11.32  			isa = PBXGroup;
   11.33  			children = (
   11.34  				279DDCCB0F9E381500D75D91 /* MYNetwork.h */,
   11.35 -				270461010DE49030003D9D3F /* IPAddress.h */,
   11.36 -				270461020DE49030003D9D3F /* IPAddress.m */,
   11.37 +				2780F5710FA2E38100C0FB83 /* Addressing */,
   11.38 +				278C1A320F9F687800954AE1 /* PortMapper */,
   11.39 +				278C1B9D0F9F92D600954AE1 /* Bonjour */,
   11.40  				270461070DE49030003D9D3F /* TCP */,
   11.41 -				278C1A320F9F687800954AE1 /* PortMapper */,
   11.42  				270460F10DE49030003D9D3F /* BLIP */,
   11.43 -				278C1B9D0F9F92D600954AE1 /* Bonjour */,
   11.44  			);
   11.45  			name = MYNetwork;
   11.46  			sourceTree = "<group>";
   11.47 @@ -356,8 +365,7 @@
   11.48  				27E0DBEC0DF3450F00E7F648 /* GoogleToolboxSubset */,
   11.49  			);
   11.50  			name = MYUtilities;
   11.51 -			path = ../MYUtilities;
   11.52 -			sourceTree = "<group>";
   11.53 +			sourceTree = MYUtilities;
   11.54  		};
   11.55  		277903E70DE8F05F00C6D295 /* Demo */ = {
   11.56  			isa = PBXGroup;
   11.57 @@ -374,11 +382,20 @@
   11.58  			path = BLIP/Demo;
   11.59  			sourceTree = "<group>";
   11.60  		};
   11.61 +		2780F5710FA2E38100C0FB83 /* Addressing */ = {
   11.62 +			isa = PBXGroup;
   11.63 +			children = (
   11.64 +				270461010DE49030003D9D3F /* IPAddress.h */,
   11.65 +				270461020DE49030003D9D3F /* IPAddress.m */,
   11.66 +				2780F20A0FA194BD00C0FB83 /* MYDNSService.h */,
   11.67 +				2780F20B0FA194BD00C0FB83 /* MYDNSService.m */,
   11.68 +			);
   11.69 +			name = Addressing;
   11.70 +			sourceTree = "<group>";
   11.71 +		};
   11.72  		278C1A320F9F687800954AE1 /* PortMapper */ = {
   11.73  			isa = PBXGroup;
   11.74  			children = (
   11.75 -				2780F20A0FA194BD00C0FB83 /* MYDNSService.h */,
   11.76 -				2780F20B0FA194BD00C0FB83 /* MYDNSService.m */,
   11.77  				278C1A350F9F687800954AE1 /* MYPortMapper.h */,
   11.78  				278C1A360F9F687800954AE1 /* MYPortMapper.m */,
   11.79  				278C1A340F9F687800954AE1 /* PortMapperTest.m */,
   11.80 @@ -393,6 +410,10 @@
   11.81  				278C1B9F0F9F92EA00954AE1 /* MYBonjourBrowser.m */,
   11.82  				278C1BA00F9F92EA00954AE1 /* MYBonjourService.h */,
   11.83  				278C1BA10F9F92EA00954AE1 /* MYBonjourService.m */,
   11.84 +				2780F4360FA28F4400C0FB83 /* MYBonjourQuery.h */,
   11.85 +				2780F4370FA28F4400C0FB83 /* MYBonjourQuery.m */,
   11.86 +				2780F49F0FA2C59000C0FB83 /* MYAddressLookup.h */,
   11.87 +				2780F4A00FA2C59000C0FB83 /* MYAddressLookup.m */,
   11.88  			);
   11.89  			path = Bonjour;
   11.90  			sourceTree = "<group>";
   11.91 @@ -415,6 +436,8 @@
   11.92  			buildActionMask = 2147483647;
   11.93  			files = (
   11.94  				2780F20C0FA194BD00C0FB83 /* MYDNSService.h in Headers */,
   11.95 +				2780F4380FA28F4400C0FB83 /* MYBonjourQuery.h in Headers */,
   11.96 +				2780F4A10FA2C59000C0FB83 /* MYAddressLookup.h in Headers */,
   11.97  			);
   11.98  			runOnlyForDeploymentPostprocessing = 0;
   11.99  		};
  11.100 @@ -570,6 +593,8 @@
  11.101  				279E8FB70F9FDD2600608D8D /* MYBonjourService.m in Sources */,
  11.102  				279E8FB80F9FDD2600608D8D /* ConcurrentOperation.m in Sources */,
  11.103  				2780F20D0FA194BD00C0FB83 /* MYDNSService.m in Sources */,
  11.104 +				2780F4390FA28F4400C0FB83 /* MYBonjourQuery.m in Sources */,
  11.105 +				2780F4A20FA2C59000C0FB83 /* MYAddressLookup.m in Sources */,
  11.106  			);
  11.107  			runOnlyForDeploymentPostprocessing = 0;
  11.108  		};
  11.109 @@ -604,6 +629,8 @@
  11.110  				278C1BA70F9F92EA00954AE1 /* MYBonjourService.m in Sources */,
  11.111  				278C1BB90F9F975700954AE1 /* ConcurrentOperation.m in Sources */,
  11.112  				2780F20E0FA194BD00C0FB83 /* MYDNSService.m in Sources */,
  11.113 +				2780F43A0FA28F4400C0FB83 /* MYBonjourQuery.m in Sources */,
  11.114 +				2780F4A30FA2C59000C0FB83 /* MYAddressLookup.m in Sources */,
  11.115  			);
  11.116  			runOnlyForDeploymentPostprocessing = 0;
  11.117  		};
    12.1 --- a/PortMapper/MYDNSService.h	Fri Apr 24 10:10:32 2009 -0700
    12.2 +++ b/PortMapper/MYDNSService.h	Mon Apr 27 09:03:56 2009 -0700
    12.3 @@ -18,23 +18,27 @@
    12.4      CFSocketRef _socket;
    12.5      CFRunLoopSourceRef _socketSource;
    12.6      SInt32 _error;
    12.7 +    BOOL _continuous;
    12.8  }
    12.9  
   12.10 +/** If NO (the default), the service will stop after it gets a result.
   12.11 +    If YES, it will continue to run until stopped. */
   12.12 +@property BOOL continuous;
   12.13 +
   12.14  /** Starts the service.
   12.15      Returns immediately; you can find out when the service actually starts (or fails to)
   12.16      by observing the isOpen and error properties.
   12.17      It's very unlikely that this call itself will fail (return NO). If it does, it
   12.18      probably means that the mDNSResponder process isn't working. */
   12.19 -- (BOOL) open;
   12.20 +- (BOOL) start;
   12.21  
   12.22 -- (void) close;
   12.23 +/** Stops the service. */
   12.24 +- (void) stop;
   12.25  
   12.26  
   12.27 -@property (readonly) struct _DNSServiceRef_t* serviceRef;
   12.28 -
   12.29  /** The error status, a DNSServiceErrorType enum; nonzero if something went wrong. 
   12.30      This property is KV observable. */
   12.31 -@property SInt32 error;
   12.32 +@property int32_t error;
   12.33  
   12.34  // PROTECTED:
   12.35  
   12.36 @@ -43,6 +47,15 @@
   12.37      If an error occurs, the method should set self.error and return NULL.*/
   12.38  - (struct _DNSServiceRef_t*) createServiceRef;
   12.39  
   12.40 -- (void) stopService;
   12.41 +@property (readonly) struct _DNSServiceRef_t* serviceRef;
   12.42 +
   12.43 +/** Same as -stop, but does not clear the error property.
   12.44 +    (The stop method actually calls this first.) */
   12.45 +- (void) cancel;
   12.46 +
   12.47 +/** Block until a message is received from the daemon.
   12.48 +    This will cause the service's callback (defined by the subclass) to be invoked.
   12.49 +    @return  YES if a message is received, NO on error (or if the service isn't started) */
   12.50 +- (BOOL) waitForReply;
   12.51  
   12.52  @end
    13.1 --- a/PortMapper/MYDNSService.m	Fri Apr 24 10:10:32 2009 -0700
    13.2 +++ b/PortMapper/MYDNSService.m	Mon Apr 27 09:03:56 2009 -0700
    13.3 @@ -27,20 +27,32 @@
    13.4  
    13.5  - (void) dealloc
    13.6  {
    13.7 +    Log(@"DEALLOC %@ %p", self.class,self);
    13.8      if( _serviceRef )
    13.9 -        [self stopService];
   13.10 +        [self cancel];
   13.11      [super dealloc];
   13.12  }
   13.13  
   13.14  - (void) finalize
   13.15  {
   13.16      if( _serviceRef )
   13.17 -        [self stopService];
   13.18 +        [self cancel];
   13.19      [super finalize];
   13.20  }
   13.21  
   13.22  
   13.23 -@synthesize serviceRef=_serviceRef, error=_error;
   13.24 +- (DNSServiceErrorType) error {
   13.25 +    return _error;
   13.26 +}
   13.27 +
   13.28 +- (void) setError: (DNSServiceErrorType)error {
   13.29 +    if (error)
   13.30 +        Warn(@"%@ error := %i", self,error);
   13.31 +    _error = error;
   13.32 +}
   13.33 +
   13.34 +
   13.35 +@synthesize continuous=_continuous, serviceRef=_serviceRef;
   13.36  
   13.37  
   13.38  - (DNSServiceRef) createServiceRef {
   13.39 @@ -48,11 +60,17 @@
   13.40  }
   13.41  
   13.42  
   13.43 -- (BOOL) open
   13.44 +- (BOOL) start
   13.45  {
   13.46      if (_serviceRef)
   13.47 -        return YES;
   13.48 +        return YES;     // already started
   13.49 +
   13.50 +    if (_error)
   13.51 +        self.error = 0;
   13.52 +
   13.53 +    // Ask the subclass to create a DNSServiceRef:
   13.54      _serviceRef = [self createServiceRef];
   13.55 +    
   13.56      if (_serviceRef) {
   13.57          // Wrap a CFSocket around the service's socket:
   13.58          CFSocketContext ctxt = { 0, self, CFRetain, CFRelease, NULL };
   13.59 @@ -66,7 +84,7 @@
   13.60              _socketSource = CFSocketCreateRunLoopSource(NULL, _socket, 0);
   13.61              if( _socketSource ) {
   13.62                  CFRunLoopAddSource(CFRunLoopGetCurrent(), _socketSource, kCFRunLoopCommonModes);
   13.63 -                LogTo(DNS,@"Opening %@",self);
   13.64 +                LogTo(DNS,@"Opening %@ -- service=%p",self,_serviceRef);
   13.65                  return YES; // success
   13.66              }
   13.67          }
   13.68 @@ -74,13 +92,14 @@
   13.69      if (!_error)
   13.70          self.error = kDNSServiceErr_Unknown;
   13.71      LogTo(DNS,@"Failed to open %@ -- err=%i",self,_error);
   13.72 -    [self stopService];
   13.73 +    [self cancel];
   13.74      return NO;
   13.75  }
   13.76  
   13.77  
   13.78 -- (void) stopService
   13.79 +- (void) cancel
   13.80  {
   13.81 +    [self retain];            // Prevents _socket's dealloc from releasing & deallocing me!
   13.82      if( _socketSource ) {
   13.83          CFRunLoopSourceInvalidate(_socketSource);
   13.84          CFRelease(_socketSource);
   13.85 @@ -96,17 +115,45 @@
   13.86          DNSServiceRefDeallocate(_serviceRef);
   13.87          _serviceRef = NULL;
   13.88      }
   13.89 +    [self release];
   13.90  }
   13.91  
   13.92  
   13.93 -- (void) close
   13.94 +- (void) stop
   13.95  {
   13.96 -    [self stopService];
   13.97 +    [self cancel];
   13.98      if (_error)
   13.99          self.error = 0;
  13.100  }
  13.101  
  13.102  
  13.103 +- (BOOL) priv_processResult
  13.104 +{
  13.105 +    Assert(_serviceRef);
  13.106 +    DNSServiceErrorType err = DNSServiceProcessResult(_serviceRef);
  13.107 +    if (err) {
  13.108 +        // An error here means the socket has failed and should be closed.
  13.109 +        self.error = err;
  13.110 +        [self cancel];
  13.111 +        return NO;
  13.112 +    } else {
  13.113 +        if (!_continuous)
  13.114 +            [self cancel];
  13.115 +        return YES;
  13.116 +    }
  13.117 +}
  13.118 +
  13.119 +- (BOOL) waitForReply
  13.120 +{
  13.121 +    if (!_serviceRef)
  13.122 +        return NO;
  13.123 +    LogTo(DNS,@"Waiting for %@ ...", self);
  13.124 +    BOOL ok = [self priv_processResult];
  13.125 +    LogTo(DNS,@"    ...done waiting");
  13.126 +    return ok;
  13.127 +}    
  13.128 +
  13.129 +
  13.130  /** CFSocket callback, informing us that _socket has data available, which means
  13.131      that the DNS service has an incoming result to be processed. This will end up invoking
  13.132      the service's specific callback. */
  13.133 @@ -114,14 +161,11 @@
  13.134                              CFSocketCallBackType type,
  13.135                              CFDataRef address, const void *data, void *clientCallBackInfo)
  13.136  {
  13.137 -    MYDNSService *serviceObj = (MYDNSService*)clientCallBackInfo;
  13.138 -    DNSServiceRef service = serviceObj.serviceRef;
  13.139 -    DNSServiceErrorType err = DNSServiceProcessResult(service);
  13.140 -    if( err ) {
  13.141 -        // An error here means the socket has failed and should be closed.
  13.142 -        serviceObj.error = err;
  13.143 -        [serviceObj stopService];
  13.144 -    }
  13.145 +    NSAutoreleasePool *pool = [NSAutoreleasePool new];
  13.146 +    @try{
  13.147 +        [(MYDNSService*)clientCallBackInfo priv_processResult];
  13.148 +    }catchAndReport(@"PortMapper serviceCallback");
  13.149 +    [pool drain];
  13.150  }
  13.151  
  13.152  
    14.1 --- a/PortMapper/MYPortMapper.m	Fri Apr 24 10:10:32 2009 -0700
    14.2 +++ b/PortMapper/MYPortMapper.m	Mon Apr 27 09:03:56 2009 -0700
    14.3 @@ -33,6 +33,7 @@
    14.4      if (self != nil) {
    14.5          _localPort = localPort;
    14.6          _mapTCP = YES;
    14.7 +        self.continuous = YES;
    14.8          [self priv_updateLocalAddress];
    14.9      }
   14.10      return self;
   14.11 @@ -87,11 +88,9 @@
   14.12                publicAddress: (UInt32)rawPublicAddress
   14.13                   publicPort: (UInt16)publicPort
   14.14  {
   14.15 -    LogTo(PortMapper,@"Callback got err %i, addr %08X:%hu",
   14.16 -          errorCode, rawPublicAddress, publicPort);
   14.17      if( errorCode==kDNSServiceErr_NoError ) {
   14.18          if( rawPublicAddress==0 || (publicPort==0 && (_mapTCP || _mapUDP)) ) {
   14.19 -            LogTo(PortMapper,@"(Callback reported no mapping available)");
   14.20 +            LogTo(PortMapper,@"%@: No port-map available", self);
   14.21              errorCode = kDNSServiceErr_NATPortMappingUnsupported;
   14.22          }
   14.23      }
   14.24 @@ -104,8 +103,8 @@
   14.25          self.publicAddress = publicAddress;
   14.26      
   14.27      if( ! errorCode ) {
   14.28 -        LogTo(PortMapper,@"Callback got %08X:%hu -> %@ (mapped=%i)",
   14.29 -              rawPublicAddress,publicPort, self.publicAddress, self.isMapped);
   14.30 +        LogTo(PortMapper,@"%@: Public addr is %@ (mapped=%i)",
   14.31 +              self, self.publicAddress, self.isMapped);
   14.32      }
   14.33      [[NSNotificationCenter defaultCenter] postNotificationName: MYPortMapperChangedNotification
   14.34                                                          object: self];
   14.35 @@ -128,13 +127,11 @@
   14.36                        void                             *context
   14.37                        )
   14.38  {
   14.39 -    NSAutoreleasePool *pool = [NSAutoreleasePool new];
   14.40      @try{
   14.41          [(MYPortMapper*)context priv_portMapStatus: errorCode 
   14.42                                       publicAddress: publicAddress
   14.43                                          publicPort: ntohs(publicPort)];  // port #s in network byte order!
   14.44      }catchAndReport(@"PortMapper");
   14.45 -    [pool drain];
   14.46  }
   14.47  
   14.48  
   14.49 @@ -157,24 +154,12 @@
   14.50  }
   14.51  
   14.52  
   14.53 -- (void) stopService
   14.54 -{
   14.55 -    [super stopService];
   14.56 -    if (_publicAddress)
   14.57 -        self.publicAddress = nil;
   14.58 -}
   14.59 -
   14.60 -
   14.61  - (BOOL) waitTillOpened
   14.62  {
   14.63      if( ! self.serviceRef )
   14.64 -        if( ! [self open] )
   14.65 +        if( ! [self start] )
   14.66              return NO;
   14.67 -    // Run the runloop until there's either an error or a result:
   14.68 -    while( self.error==0 && _publicAddress==nil )
   14.69 -        if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
   14.70 -                                       beforeDate: [NSDate distantFuture]] )
   14.71 -            break;
   14.72 +    [self waitForReply];
   14.73      return (self.error==0);
   14.74  }
   14.75  
   14.76 @@ -183,9 +168,10 @@
   14.77  {
   14.78      IPAddress *addr = nil;
   14.79      MYPortMapper *mapper = [[self alloc] initWithNullMapping];
   14.80 +    mapper.continuous = NO;
   14.81      if( [mapper waitTillOpened] )
   14.82          addr = [mapper.publicAddress retain];
   14.83 -    [mapper close];
   14.84 +    [mapper stop];
   14.85      [mapper release];
   14.86      return [addr autorelease];
   14.87  }
    15.1 --- a/PortMapper/PortMapperTest.m	Fri Apr 24 10:10:32 2009 -0700
    15.2 +++ b/PortMapper/PortMapperTest.m	Mon Apr 27 09:03:56 2009 -0700
    15.3 @@ -42,7 +42,7 @@
    15.4          _mapper.desiredPublicPort = 22222;
    15.5          
    15.6          // Now open the mapping (asynchronously):
    15.7 -        if( [_mapper open] ) {
    15.8 +        if( [_mapper start] ) {
    15.9              Log(@"Opening port mapping...");
   15.10              // Now listen for notifications to find out when the mapping opens, fails, or changes:
   15.11              [[NSNotificationCenter defaultCenter] addObserver: self 
   15.12 @@ -78,7 +78,7 @@
   15.13  
   15.14  - (void) dealloc
   15.15  {
   15.16 -    [_mapper close];
   15.17 +    [_mapper stop];
   15.18      [_mapper release];
   15.19      [super dealloc];
   15.20  }
   15.21 @@ -90,7 +90,9 @@
   15.22  
   15.23  TestCase(MYPortMapper) {
   15.24      
   15.25 +    EnableLogTo(DNS,YES);
   15.26      EnableLogTo(PortMapper,YES);
   15.27 +    
   15.28      // Here's how to simply obtain your local and public address(es):
   15.29      IPAddress *addr = [IPAddress localAddress];
   15.30      Log(@"** Local address is %@%@ ...getting public addr...",