More work on Bonjour classes. They now support registering services.
authorJens Alfke <jens@mooseyard.com>
Wed Apr 29 13:29:31 2009 -0700 (2009-04-29)
changeset 311d6924779df7
parent 29 59689fbdcf77
parent 30 096cf03b65d4
child 32 b3254a2f6d6c
More work on Bonjour classes. They now support registering services.
Bonjour/MYAddressLookup.h
Bonjour/MYAddressLookup.m
Bonjour/MYBonjourBrowser.h
Bonjour/MYBonjourBrowser.m
Bonjour/MYBonjourQuery.m
Bonjour/MYBonjourRegistration.h
Bonjour/MYBonjourRegistration.m
Bonjour/MYBonjourService.h
Bonjour/MYBonjourService.m
MYNetwork-iPhone.xcodeproj/project.pbxproj
MYNetwork.xcodeproj/project.pbxproj
PortMapper/MYDNSService.h
PortMapper/MYDNSService.m
PortMapper/MYPortMapper.m
     1.1 --- a/Bonjour/MYAddressLookup.h	Tue Apr 28 10:36:28 2009 -0700
     1.2 +++ b/Bonjour/MYAddressLookup.h	Wed Apr 29 13:29:31 2009 -0700
     1.3 @@ -19,14 +19,17 @@
     1.4      CFAbsoluteTime _expires;
     1.5  }
     1.6  
     1.7 -/** Initializes the lookup with a DNS hostname. */
     1.8 +/** Initializes the lookup with a DNS hostname.
     1.9 +    (If you've got a Bonjour service already, as a MYBonjourService object, it's more convenient
    1.10 +    to access its addressLookup property instead of creating your own instance.) */
    1.11  - (id) initWithHostname: (NSString*)hostname;
    1.12  
    1.13  /** The port number; this will be copied into the resulting IPAddress objects.
    1.14      Defaults to zero, but you can set it before calling -start. */
    1.15  @property UInt16 port;
    1.16  
    1.17 -/** The index of the network interface. You usually don't need to set this. */
    1.18 +/** The index of the network interface to use, or zero (the default) for any interface.
    1.19 +    You usually don't need to set this. */
    1.20  @property UInt16 interfaceIndex;
    1.21  
    1.22  /** The resulting address(es) of the host, as HostAddress objects. */
     2.1 --- a/Bonjour/MYAddressLookup.m	Tue Apr 28 10:36:28 2009 -0700
     2.2 +++ b/Bonjour/MYAddressLookup.m	Wed Apr 29 13:29:31 2009 -0700
     2.3 @@ -60,17 +60,17 @@
     2.4                                                          sockaddr: sockaddr
     2.5                                                              port: _port];
     2.6      if (address) {
     2.7 -        if (flags & kDNSServiceFlagsAdd)
     2.8 +        if (flags & kDNSServiceFlagsAdd) {
     2.9 +            LogTo(DNS,@"%@ got %@ [TTL = %u]", self, address, ttl);
    2.10              [_addresses addObject: address];
    2.11 -        else
    2.12 +        } else {
    2.13 +            LogTo(DNS,@"%@ lost %@ [TTL = %u]", self, address, ttl);
    2.14              [_addresses removeObject: address];
    2.15 +        }
    2.16          [address release];
    2.17      }
    2.18      
    2.19      _expires = CFAbsoluteTimeGetCurrent() + ttl;
    2.20 -
    2.21 -    if (!(flags & kDNSServiceFlagsMoreComing))
    2.22 -        LogTo(DNS,@"Got addresses of %@: %@ [TTL = %u]", self, _addresses, ttl);
    2.23  }
    2.24  
    2.25  
    2.26 @@ -91,17 +91,17 @@
    2.27          else
    2.28              [lookup priv_resolvedAddress: address ttl: ttl flags: flags];
    2.29      }catchAndReport(@"MYDNSLookup query callback");
    2.30 +    [lookup gotResponse: errorCode];
    2.31  }
    2.32  
    2.33  
    2.34 -- (DNSServiceRef) createServiceRef {
    2.35 +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
    2.36      [_addresses removeAllObjects];
    2.37 -    DNSServiceRef serviceRef = NULL;
    2.38 -    self.error = DNSServiceGetAddrInfo(&serviceRef, 0,
    2.39 -                                       _interfaceIndex, 0,
    2.40 -                                       _hostname.UTF8String,
    2.41 -                                       &lookupCallback, self);
    2.42 -    return serviceRef;
    2.43 +    return DNSServiceGetAddrInfo(sdRefPtr,
    2.44 +                                 kDNSServiceFlagsShareConnection,
    2.45 +                                 _interfaceIndex, 0,
    2.46 +                                 _hostname.UTF8String,
    2.47 +                                 &lookupCallback, self);
    2.48  }
    2.49  
    2.50  
    2.51 @@ -120,3 +120,26 @@
    2.52      [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 10]];
    2.53      [lookup release];
    2.54  }    
    2.55 +
    2.56 +
    2.57 +/*
    2.58 + Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
    2.59 + 
    2.60 + Redistribution and use in source and binary forms, with or without modification, are permitted
    2.61 + provided that the following conditions are met:
    2.62 + 
    2.63 + * Redistributions of source code must retain the above copyright notice, this list of conditions
    2.64 + and the following disclaimer.
    2.65 + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
    2.66 + and the following disclaimer in the documentation and/or other materials provided with the
    2.67 + distribution.
    2.68 + 
    2.69 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
    2.70 + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
    2.71 + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
    2.72 + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    2.73 + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
    2.74 +  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
    2.75 + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
    2.76 + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    2.77 + */
     3.1 --- a/Bonjour/MYBonjourBrowser.h	Tue Apr 28 10:36:28 2009 -0700
     3.2 +++ b/Bonjour/MYBonjourBrowser.h	Wed Apr 29 13:29:31 2009 -0700
     3.3 @@ -7,6 +7,7 @@
     3.4  //
     3.5  
     3.6  #import "MYDNSService.h"
     3.7 +@class MYBonjourRegistration;
     3.8  
     3.9  
    3.10  /** Searches for Bonjour services of a specific type. */
    3.11 @@ -17,6 +18,8 @@
    3.12      BOOL _browsing;
    3.13      Class _serviceClass;
    3.14      NSMutableSet *_services, *_addServices, *_rmvServices;
    3.15 +    BOOL _pendingUpdate;
    3.16 +    MYBonjourRegistration *_myRegistration;
    3.17  }
    3.18  
    3.19  /** Initializes a new BonjourBrowser.
    3.20 @@ -37,4 +40,10 @@
    3.21      to a subclass of that. */
    3.22  @property Class serviceClass;
    3.23  
    3.24 +/** My own registration for this service type.
    3.25 +    This object is created on demand and won't be started up until you tell it to.
    3.26 +    Before starting it, you'll need to set its port, and optionally its name.
    3.27 +    Your own registration will _not_ be visible in the set of services.*/
    3.28 +@property (readonly) MYBonjourRegistration *myRegistration;
    3.29 +
    3.30  @end
     4.1 --- a/Bonjour/MYBonjourBrowser.m	Tue Apr 28 10:36:28 2009 -0700
     4.2 +++ b/Bonjour/MYBonjourBrowser.m	Wed Apr 29 13:29:31 2009 -0700
     4.3 @@ -8,6 +8,7 @@
     4.4  
     4.5  #import "MYBonjourBrowser.h"
     4.6  #import "MYBonjourService.h"
     4.7 +#import "MYBonjourRegistration.h"
     4.8  #import "ExceptionUtils.h"
     4.9  #import "Test.h"
    4.10  #import "Logging.h"
    4.11 @@ -51,6 +52,8 @@
    4.12  - (void) dealloc
    4.13  {
    4.14      LogTo(Bonjour,@"DEALLOC BonjourBrowser");
    4.15 +    [_myRegistration cancel];
    4.16 +    [_myRegistration release];
    4.17      [_serviceType release];
    4.18      [_services release];
    4.19      [_addServices release];
    4.20 @@ -68,12 +71,12 @@
    4.21  }
    4.22  
    4.23  
    4.24 -- (DNSServiceRef) createServiceRef {
    4.25 -    DNSServiceRef serviceRef = NULL;
    4.26 -    self.error = DNSServiceBrowse(&serviceRef, 0, 0,
    4.27 -                                  _serviceType.UTF8String, NULL,
    4.28 -                                  &browseCallback, self);
    4.29 -    return serviceRef;
    4.30 +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
    4.31 +    return DNSServiceBrowse(sdRefPtr,
    4.32 +                            kDNSServiceFlagsShareConnection, 
    4.33 +                            0,
    4.34 +                            _serviceType.UTF8String, NULL,
    4.35 +                            &browseCallback, self);
    4.36  }
    4.37  
    4.38  
    4.39 @@ -93,8 +96,15 @@
    4.40                                                                 type: regtype
    4.41                                                               domain: domain
    4.42                                                            interface: interfaceIndex];
    4.43 +    if ([_myRegistration isSameAsService: service]) {
    4.44 +        // This is an echo of my own registration, so ignore it
    4.45 +        LogTo(Bonjour,@"%@ ignoring echo %@", self,service);
    4.46 +        [service release];
    4.47 +        return;
    4.48 +    }
    4.49      MYBonjourService *existingService = [_services member: service];
    4.50      if( existingService ) {
    4.51 +        // Use existing service object instead of creating a new one
    4.52          [service release];
    4.53          service = [existingService retain];
    4.54      }
    4.55 @@ -114,14 +124,17 @@
    4.56          [addTo addObject: service];
    4.57      [service release];
    4.58      
    4.59 -    // After a round of updates is done, do the update:
    4.60 -    if( ! (flags & kDNSServiceFlagsMoreComing) )
    4.61 -        [self _updateServiceList];
    4.62 +    // Schedule a (single) call to _updateServiceList:
    4.63 +    if (!_pendingUpdate) {
    4.64 +        [self performSelector: @selector(_updateServiceList) withObject: nil afterDelay: 0];
    4.65 +        _pendingUpdate = YES;
    4.66 +    }
    4.67  }
    4.68  
    4.69  
    4.70  - (void) _updateServiceList
    4.71  {
    4.72 +    _pendingUpdate = NO;
    4.73      if( _rmvServices.count ) {
    4.74          [self willChangeValueForKey: @"services" 
    4.75                      withSetMutation: NSKeyValueMinusSetMutation
    4.76 @@ -156,17 +169,30 @@
    4.77                              const char                          *replyDomain,
    4.78                              void                                *context)
    4.79  {
    4.80 +    MYBonjourBrowser *browser = context;
    4.81      @try{
    4.82          //LogTo(Bonjour,@"browseCallback (error=%i, name='%s')", errorCode,serviceName);
    4.83 -        if (errorCode)
    4.84 -            [(MYBonjourBrowser*)context priv_gotError: errorCode];
    4.85 -        else
    4.86 -            [(MYBonjourBrowser*)context priv_gotServiceName: [NSString stringWithUTF8String: serviceName]
    4.87 -                                                       type: [NSString stringWithUTF8String: regtype]
    4.88 -                                                     domain: [NSString stringWithUTF8String: replyDomain]
    4.89 -                                                  interface: interfaceIndex
    4.90 -                                                      flags: flags];
    4.91 +        if (!errorCode)
    4.92 +            [browser priv_gotServiceName: [NSString stringWithUTF8String: serviceName]
    4.93 +                                    type: [NSString stringWithUTF8String: regtype]
    4.94 +                                  domain: [NSString stringWithUTF8String: replyDomain]
    4.95 +                               interface: interfaceIndex
    4.96 +                                   flags: flags];
    4.97      }catchAndReport(@"Bonjour");
    4.98 +    [browser gotResponse: errorCode];
    4.99 +}
   4.100 +
   4.101 +
   4.102 +- (void) cancel {
   4.103 +    [_myRegistration stop];
   4.104 +    [super cancel];
   4.105 +}
   4.106 +
   4.107 +
   4.108 +- (MYBonjourRegistration *) myRegistration {
   4.109 +    if (!_myRegistration)
   4.110 +        _myRegistration = [[MYBonjourRegistration alloc] initWithServiceType: _serviceType port: 0];
   4.111 +    return _myRegistration;
   4.112  }
   4.113  
   4.114  
   4.115 @@ -198,6 +224,10 @@
   4.116          [_browser addObserver: self forKeyPath: @"services" options: NSKeyValueObservingOptionNew context: NULL];
   4.117          [_browser addObserver: self forKeyPath: @"browsing" options: NSKeyValueObservingOptionNew context: NULL];
   4.118          [_browser start];
   4.119 +        
   4.120 +        MYBonjourRegistration *myReg = _browser.myRegistration;
   4.121 +        myReg.port = 12346;
   4.122 +        Assert([myReg start]);
   4.123      }
   4.124      return self;
   4.125  }
     5.1 --- a/Bonjour/MYBonjourQuery.m	Tue Apr 28 10:36:28 2009 -0700
     5.2 +++ b/Bonjour/MYBonjourQuery.m	Wed Apr 29 13:29:31 2009 -0700
     5.3 @@ -105,31 +105,55 @@
     5.4                             uint32_t                            ttl,
     5.5                             void                                *context)
     5.6  {
     5.7 +    MYBonjourQuery *query = context;
     5.8      @try{
     5.9          //LogTo(Bonjour, @"queryCallback for %@ (err=%i)", context,errorCode);
    5.10 -        if (errorCode)
    5.11 -            [(MYBonjourQuery*)context setError: errorCode];
    5.12 -        else
    5.13 -            [(MYBonjourQuery*)context priv_gotRecordBytes: rdata
    5.14 -                                                      length: rdlen
    5.15 -                                                        type: rrtype
    5.16 -                                                         ttl: ttl
    5.17 -                                                       flags: flags];
    5.18 +        if (!errorCode)
    5.19 +            [query priv_gotRecordBytes: rdata
    5.20 +                                length: rdlen
    5.21 +                                  type: rrtype
    5.22 +                                   ttl: ttl
    5.23 +                                 flags: flags];
    5.24      }catchAndReport(@"MYBonjourResolver query callback");
    5.25 +    [query gotResponse: errorCode];
    5.26  }
    5.27  
    5.28  
    5.29 -- (DNSServiceRef) createServiceRef {
    5.30 -    DNSServiceRef serviceRef = NULL;
    5.31 +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
    5.32      const char *fullName = _bonjourService.fullName.UTF8String;
    5.33      if (fullName)
    5.34 -        self.error = DNSServiceQueryRecord(&serviceRef, 0, 
    5.35 -                                           _bonjourService.interfaceIndex, 
    5.36 -                                           fullName,
    5.37 -                                           _recordType, kDNSServiceClass_IN, 
    5.38 -                                           &queryCallback, self);
    5.39 -    return serviceRef;
    5.40 +        return DNSServiceQueryRecord(sdRefPtr,
    5.41 +                                     kDNSServiceFlagsShareConnection, 
    5.42 +                                     _bonjourService.interfaceIndex, 
    5.43 +                                     fullName,
    5.44 +                                     _recordType, kDNSServiceClass_IN, 
    5.45 +                                     &queryCallback, self);
    5.46 +    else
    5.47 +        return kDNSServiceErr_NoSuchName;
    5.48  }
    5.49  
    5.50  
    5.51  @end
    5.52 +
    5.53 +
    5.54 +/*
    5.55 + Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
    5.56 + 
    5.57 + Redistribution and use in source and binary forms, with or without modification, are permitted
    5.58 + provided that the following conditions are met:
    5.59 + 
    5.60 + * Redistributions of source code must retain the above copyright notice, this list of conditions
    5.61 + and the following disclaimer.
    5.62 + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
    5.63 + and the following disclaimer in the documentation and/or other materials provided with the
    5.64 + distribution.
    5.65 + 
    5.66 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
    5.67 + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
    5.68 + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
    5.69 + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    5.70 + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
    5.71 +  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
    5.72 + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
    5.73 + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    5.74 + */
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/Bonjour/MYBonjourRegistration.h	Wed Apr 29 13:29:31 2009 -0700
     6.3 @@ -0,0 +1,81 @@
     6.4 +//
     6.5 +//  MYBonjourRegistration.h
     6.6 +//  MYNetwork
     6.7 +//
     6.8 +//  Created by Jens Alfke on 4/27/09.
     6.9 +//  Copyright 2009 Jens Alfke. All rights reserved.
    6.10 +//
    6.11 +
    6.12 +#import "MYDNSService.h"
    6.13 +@class MYBonjourService;
    6.14 +
    6.15 +
    6.16 +/** Registers a local network service with Bonjour, so it can be browsed by other computers. */
    6.17 +@interface MYBonjourRegistration : MYDNSService
    6.18 +{
    6.19 +    NSString *_name, *_type, *_domain;
    6.20 +    UInt16 _port;
    6.21 +    BOOL _autoRename;
    6.22 +    BOOL _registered;
    6.23 +    NSMutableDictionary *_txtRecord;
    6.24 +}
    6.25 +
    6.26 +/** Initializes a new registration.
    6.27 +    If you're also browsing for the same service type, you should instead get an instance of this via
    6.28 +    the MYBonjourBrowser's 'myRegistration' property -- that way the browser knows about the
    6.29 +    registration and won't echo it back to you.
    6.30 +    Either way, don't forget to call -start! */
    6.31 +- (id) initWithServiceType: (NSString*)serviceType port: (UInt16)port;
    6.32 +
    6.33 +/** The name to register this service under.
    6.34 +    This is often left nil, in which case the user's chosen "Computer Name" (from the Sharing system
    6.35 +    pref pane) will be used.
    6.36 +    This can only be set before calling -start! */
    6.37 +@property (copy) NSString *name;
    6.38 +
    6.39 +/** The registration's service type. */
    6.40 +@property (readonly) NSString *type;
    6.41 +
    6.42 +/** The registration's IP port number.
    6.43 +    You'll need to set this if you got this object from MYBonjourBrowser's 'myRegistration' property,
    6.44 +    as that object doesn't have a pre-set port number yet.
    6.45 +    This can only be set before calling -start!  */
    6.46 +@property UInt16 port;
    6.47 +
    6.48 +/** The registration's full name -- the name, type and domain concatenated together. */
    6.49 +@property (readonly) NSString *fullName;
    6.50 +
    6.51 +/** Is the registration currently active? */
    6.52 +@property (readonly) BOOL registered;
    6.53 +
    6.54 +/** The service's metadata dictionary, stored in its DNS TXT record */
    6.55 +@property (copy) NSDictionary *txtRecord;
    6.56 +
    6.57 +/** Convenience to store a string value in a single TXT record key. */
    6.58 +- (void) setString: (NSString*)value forTxtKey: (NSString*)key;
    6.59 +
    6.60 +
    6.61 +/** @name Expert
    6.62 + *  Advanced methods you likely won't need
    6.63 + */
    6.64 +//@{
    6.65 +
    6.66 +/** The registration's domain name.
    6.67 +    This is almost always left nil, in which case the default registration domain is used
    6.68 +    (usually ".local".)
    6.69 +    This can only be set before calling -start!  */
    6.70 +@property (copy) NSString *domain;
    6.71 +
    6.72 +/** Determines what to do if there's a name conflict with an already-registered service on the
    6.73 +    network.
    6.74 +    If set to YES (the default), a number will be appended to the name to make it unique.
    6.75 +    If set to NO, the registration will fail, and you can choose a different name and try again.
    6.76 +    This can only be set before calling -start!  */
    6.77 +@property BOOL autoRename;
    6.78 +
    6.79 +/** Is this browsed service an echo of this local registration? (Compares fullNames.) */
    6.80 +- (BOOL) isSameAsService: (MYBonjourService*)service;
    6.81 +
    6.82 +//@}
    6.83 +
    6.84 +@end
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/Bonjour/MYBonjourRegistration.m	Wed Apr 29 13:29:31 2009 -0700
     7.3 @@ -0,0 +1,289 @@
     7.4 +//
     7.5 +//  MYBonjourRegistration.m
     7.6 +//  MYNetwork
     7.7 +//
     7.8 +//  Created by Jens Alfke on 4/27/09.
     7.9 +//  Copyright 2009 Jens Alfke. All rights reserved.
    7.10 +//
    7.11 +
    7.12 +#import "MYBonjourRegistration.h"
    7.13 +#import "MYBonjourService.h"
    7.14 +#import "ExceptionUtils.h"
    7.15 +#import "Test.h"
    7.16 +#import "Logging.h"
    7.17 +#import <dns_sd.h>
    7.18 +
    7.19 +
    7.20 +#define kTXTTTL 60          // TTL in seconds for TXT records I register
    7.21 +
    7.22 +
    7.23 +@interface MYBonjourRegistration ()
    7.24 +@property BOOL registered;
    7.25 +@end
    7.26 +
    7.27 +
    7.28 +@implementation MYBonjourRegistration
    7.29 +
    7.30 +
    7.31 +static NSMutableDictionary *sAllRegistrations;
    7.32 +
    7.33 +
    7.34 ++ (void) priv_addRegistration: (MYBonjourRegistration*)reg {
    7.35 +    if (!sAllRegistrations)
    7.36 +        sAllRegistrations = [[NSMutableDictionary alloc] init];
    7.37 +    [sAllRegistrations setObject: reg forKey: reg.fullName];
    7.38 +}
    7.39 +
    7.40 ++ (void) priv_removeRegistration: (MYBonjourRegistration*)reg {
    7.41 +    [sAllRegistrations removeObjectForKey: reg.fullName];
    7.42 +}
    7.43 +
    7.44 ++ (MYBonjourRegistration*) registrationWithFullName: (NSString*)fullName {
    7.45 +    return [sAllRegistrations objectForKey: fullName];
    7.46 +}
    7.47 +
    7.48 +
    7.49 +- (id) initWithServiceType: (NSString*)serviceType port: (UInt16)port
    7.50 +{
    7.51 +    self = [super init];
    7.52 +    if (self != nil) {
    7.53 +        self.continuous = YES;
    7.54 +        self.usePrivateConnection = YES;    // DNSServiceUpdateRecord doesn't work with shared conn :(
    7.55 +        _type = [serviceType copy];
    7.56 +        _port = port;
    7.57 +        _autoRename = YES;
    7.58 +    }
    7.59 +    return self;
    7.60 +}
    7.61 +
    7.62 +- (void) dealloc {
    7.63 +    [_name release];
    7.64 +    [_type release];
    7.65 +    [_domain release];
    7.66 +    [super dealloc];
    7.67 +}
    7.68 +
    7.69 +
    7.70 +@synthesize name=_name, type=_type, domain=_domain, port=_port, autoRename=_autoRename;
    7.71 +@synthesize registered=_registered;
    7.72 +
    7.73 +
    7.74 +- (NSString*) fullName {
    7.75 +    return [[self class] fullNameOfService: _name ofType: _type inDomain: _domain];
    7.76 +}
    7.77 +
    7.78 +
    7.79 +- (BOOL) isSameAsService: (MYBonjourService*)service {
    7.80 +    return _name && _domain && [self.fullName isEqualToString: service.fullName];
    7.81 +}
    7.82 +
    7.83 +
    7.84 +- (NSString*) description
    7.85 +{
    7.86 +    return $sprintf(@"%@['%@'.%@%@]", self.class,_name,_type,_domain);
    7.87 +}
    7.88 +
    7.89 +
    7.90 +- (void) priv_registeredAsName: (NSString*)name 
    7.91 +                          type: (NSString*)regtype
    7.92 +                        domain: (NSString*)domain
    7.93 +{
    7.94 +    if (!$equal(name,_name))
    7.95 +        self.name = name;
    7.96 +    if (!$equal(domain,_domain))
    7.97 +        self.domain = domain;
    7.98 +    LogTo(Bonjour,@"Registered %@", self);
    7.99 +    self.registered = YES;
   7.100 +    [[self class] priv_addRegistration: self];
   7.101 +}
   7.102 +
   7.103 +
   7.104 +static void regCallback(DNSServiceRef                       sdRef,
   7.105 +                        DNSServiceFlags                     flags,
   7.106 +                        DNSServiceErrorType                 errorCode,
   7.107 +                        const char                          *name,
   7.108 +                        const char                          *regtype,
   7.109 +                        const char                          *domain,
   7.110 +                        void                                *context)
   7.111 +{
   7.112 +    MYBonjourRegistration *reg = context;
   7.113 +    @try{
   7.114 +        if (!errorCode)
   7.115 +            [reg priv_registeredAsName: [NSString stringWithUTF8String: name]
   7.116 +                                  type: [NSString stringWithUTF8String: regtype]
   7.117 +                                domain: [NSString stringWithUTF8String: domain]];
   7.118 +    }catchAndReport(@"MYBonjourRegistration callback");
   7.119 +    [reg gotResponse: errorCode];
   7.120 +}
   7.121 +
   7.122 +
   7.123 +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
   7.124 +    DNSServiceFlags flags = 0;
   7.125 +    if (!_autoRename)
   7.126 +        flags |= kDNSServiceFlagsNoAutoRename;
   7.127 +    NSData *txtData = nil;
   7.128 +    if (_txtRecord)
   7.129 +        txtData = [NSNetService dataFromTXTRecordDictionary: _txtRecord];
   7.130 +    return DNSServiceRegister(sdRefPtr,
   7.131 +                              flags,
   7.132 +                              0,
   7.133 +                              _name.UTF8String,         // _name is likely to be nil
   7.134 +                              _type.UTF8String,
   7.135 +                              _domain.UTF8String,       // _domain is most likely nil
   7.136 +                              NULL,
   7.137 +                              htons(_port),
   7.138 +                              txtData.length,
   7.139 +                              txtData.bytes,
   7.140 +                              &regCallback,
   7.141 +                              self);
   7.142 +}
   7.143 +
   7.144 +
   7.145 +- (void) cancel {
   7.146 +    [super cancel];
   7.147 +    if (_registered) {
   7.148 +        [[self class] priv_removeRegistration: self];
   7.149 +        self.registered = NO;
   7.150 +    }
   7.151 +}
   7.152 +
   7.153 +
   7.154 +- (void) updateTxtRecord {
   7.155 +    [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
   7.156 +    if (self.serviceRef) {
   7.157 +        NSData *data = [NSNetService dataFromTXTRecordDictionary: _txtRecord];
   7.158 +        Assert(data!=nil, @"Can't convert dictionary to TXT record");
   7.159 +        DNSServiceErrorType err = DNSServiceUpdateRecord(self.serviceRef,
   7.160 +                                                         NULL,
   7.161 +                                                         0,
   7.162 +                                                         data.length,
   7.163 +                                                         data.bytes,
   7.164 +                                                         kTXTTTL);
   7.165 +        if (err)
   7.166 +            Warn(@"%@ failed to update TXT (err=%i)", self,err);
   7.167 +        else
   7.168 +            LogTo(Bonjour,@"%@ updated TXT to %@", self,data);
   7.169 +    }
   7.170 +}
   7.171 +
   7.172 +
   7.173 +- (NSDictionary*) txtRecord {
   7.174 +    return _txtRecord;
   7.175 +}
   7.176 +
   7.177 +- (void) setTxtRecord: (NSDictionary*)txtDict {
   7.178 +    if (!$equal(_txtRecord,txtDict)) {
   7.179 +        if (txtDict)
   7.180 +            [_txtRecord setDictionary: txtDict];
   7.181 +        else
   7.182 +            setObj(&_txtRecord,nil);
   7.183 +        [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
   7.184 +        [self performSelector: @selector(updateTxtRecord) withObject: nil afterDelay: 0.1];
   7.185 +    }
   7.186 +}
   7.187 +
   7.188 +- (void) setString: (NSString*)value forTxtKey: (NSString*)key
   7.189 +{
   7.190 +    NSData *data = [value dataUsingEncoding: NSUTF8StringEncoding];
   7.191 +    if (!$equal(data, [_txtRecord objectForKey: key])) {
   7.192 +        if (data) {
   7.193 +            if (!_txtRecord) _txtRecord = [[NSMutableDictionary alloc] init];
   7.194 +            [_txtRecord setObject: data forKey: key];
   7.195 +        } else
   7.196 +            [_txtRecord removeObjectForKey: key];
   7.197 +        [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
   7.198 +        [self performSelector: @selector(updateTxtRecord) withObject: nil afterDelay: 0.1];
   7.199 +    }
   7.200 +}
   7.201 +
   7.202 +@end
   7.203 +
   7.204 +
   7.205 +
   7.206 +
   7.207 +#pragma mark -
   7.208 +#pragma mark TESTING:
   7.209 +
   7.210 +#if DEBUG
   7.211 +
   7.212 +#import "MYBonjourQuery.h"
   7.213 +#import "MYAddressLookup.h"
   7.214 +
   7.215 +@interface BonjourRegTester : NSObject
   7.216 +{
   7.217 +    MYBonjourRegistration *_reg;
   7.218 +    BOOL _updating;
   7.219 +}
   7.220 +@end
   7.221 +
   7.222 +@implementation BonjourRegTester
   7.223 +
   7.224 +- (void) updateTXT {
   7.225 +    NSDictionary *txt = $dict({@"time", $sprintf(@"%.3lf", CFAbsoluteTimeGetCurrent())});
   7.226 +    _reg.txtRecord = txt;
   7.227 +    [self performSelector: @selector(updateTXT) withObject: nil afterDelay: 3.0];
   7.228 +}
   7.229 +
   7.230 +- (id) init
   7.231 +{
   7.232 +    self = [super init];
   7.233 +    if (self != nil) {
   7.234 +        _reg = [[MYBonjourRegistration alloc] initWithServiceType: @"_foo._tcp" port: 12345];
   7.235 +        [_reg addObserver: self forKeyPath: @"registered" options: NSKeyValueObservingOptionNew context: NULL];
   7.236 +        [_reg addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: NULL];
   7.237 +        
   7.238 +        [self updateTXT];
   7.239 +        [_reg start];
   7.240 +    }
   7.241 +    return self;
   7.242 +}
   7.243 +
   7.244 +- (void) dealloc
   7.245 +{
   7.246 +    [_reg stop];
   7.247 +    [_reg removeObserver: self forKeyPath: @"registered"];
   7.248 +    [_reg removeObserver: self forKeyPath: @"name"];
   7.249 +    [_reg release];
   7.250 +    [super dealloc];
   7.251 +}
   7.252 +
   7.253 +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
   7.254 +{
   7.255 +    LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change);
   7.256 +}
   7.257 +
   7.258 +@end
   7.259 +
   7.260 +TestCase(BonjourReg) {
   7.261 +    EnableLogTo(Bonjour,YES);
   7.262 +    EnableLogTo(DNS,YES);
   7.263 +    [NSRunLoop currentRunLoop]; // create runloop
   7.264 +    BonjourRegTester *tester = [[BonjourRegTester alloc] init];
   7.265 +    [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 15]];
   7.266 +    [tester release];
   7.267 +}
   7.268 +
   7.269 +#endif
   7.270 +
   7.271 +
   7.272 +/*
   7.273 + Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   7.274 + 
   7.275 + Redistribution and use in source and binary forms, with or without modification, are permitted
   7.276 + provided that the following conditions are met:
   7.277 + 
   7.278 + * Redistributions of source code must retain the above copyright notice, this list of conditions
   7.279 + and the following disclaimer.
   7.280 + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   7.281 + and the following disclaimer in the documentation and/or other materials provided with the
   7.282 + distribution.
   7.283 + 
   7.284 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   7.285 + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   7.286 + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   7.287 + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   7.288 + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   7.289 +  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   7.290 + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   7.291 + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   7.292 + */
     8.1 --- a/Bonjour/MYBonjourService.h	Tue Apr 28 10:36:28 2009 -0700
     8.2 +++ b/Bonjour/MYBonjourService.h	Wed Apr 29 13:29:31 2009 -0700
     8.3 @@ -33,13 +33,34 @@
     8.4  /** The service's domain. */
     8.5  @property (readonly) NSString *domain;
     8.6  
     8.7 +/** The service's full name -- the name, type and domain concatenated together. */
     8.8 +@property (readonly,copy) NSString* fullName;
     8.9 +
    8.10 +/** The index of the network interface on which this service was found. */
    8.11 +@property (readonly) uint32_t interfaceIndex;
    8.12 +
    8.13 +
    8.14 +/** @name Addressing
    8.15 + *  Getting the IP address of the service
    8.16 + */
    8.17 +//@{
    8.18 +
    8.19 +/** The hostname of the machine providing this service. */
    8.20  @property (readonly, copy) NSString *hostname;
    8.21  
    8.22 +/** The IP port number of this service on its host. */
    8.23  @property (readonly) UInt16 port;
    8.24  
    8.25 -@property (readonly) uint32_t interfaceIndex;
    8.26 +/** Returns a MYDNSLookup object that resolves the raw IP address(es) of this service.
    8.27 +    Subsequent calls to this method will always return the same object. */
    8.28 +- (MYAddressLookup*) addressLookup;
    8.29  
    8.30 -@property (readonly,copy) NSString* fullName;
    8.31 +//@}
    8.32 +
    8.33 +
    8.34 +/** @name TXT and other DNS records
    8.35 + */
    8.36 +//@{
    8.37  
    8.38  /** The service's metadata dictionary, from its DNS TXT record */
    8.39  @property (readonly,copy) NSDictionary *txtRecord;
    8.40 @@ -47,42 +68,41 @@
    8.41  /** A convenience to access a single property from the TXT record. */
    8.42  - (NSString*) txtStringForKey: (NSString*)key;
    8.43  
    8.44 -/** Returns a MYDNSLookup object that resolves the IP address(es) of this service.
    8.45 -    Subsequent calls to this method will always return the same object. */
    8.46 -- (MYAddressLookup*) addressLookup;
    8.47 -
    8.48  /** Starts a new MYBonjourQuery for the specified DNS record type of this service.
    8.49      @param recordType  The DNS record type, e.g. kDNSServiceType_TXT; see the enum in <dns_sd.h>. */
    8.50  - (MYBonjourQuery*) queryForRecord: (UInt16)recordType;
    8.51  
    8.52 +//@}
    8.53  
    8.54 -// Protected methods, for subclass use only:
    8.55  
    8.56 -// (for subclasses to override, but not call):
    8.57 +/** @name Protected
    8.58 + *  Advanced methods that may be overridden by subclasses, but should not be called directly.
    8.59 + */
    8.60 +//@{
    8.61 +
    8.62 +/** Designated initializer. You probably don't want to create MYBonjourService instances yourself,
    8.63 +    but if you subclass you might need to override this initializer. */
    8.64  - (id) initWithName: (NSString*)serviceName
    8.65                 type: (NSString*)type
    8.66               domain: (NSString*)domain
    8.67 -          interface: (uint32_t)interfaceIndex;
    8.68 +          interface: (UInt32)interfaceIndex;
    8.69  
    8.70 +/** Called when this service is officially added to its browser's service set.
    8.71 +    You can override this, but be sure to call the superclass method. */
    8.72  - (void) added;
    8.73 +
    8.74 +/** Called when this service is officially removed to its browser's service set.
    8.75 +    You can override this, but be sure to call the superclass method. */
    8.76  - (void) removed;
    8.77 +
    8.78 +/** Called when this service's TXT record changes.
    8.79 +    You can override this, but be sure to call the superclass method. */
    8.80  - (void) txtRecordChanged;
    8.81  
    8.82 -// Internal:
    8.83 -
    8.84 +/** Called when a query started by this service updates.
    8.85 +    You can override this, but be sure to call the superclass method. */
    8.86  - (void) queryDidUpdate: (MYBonjourQuery*)query;
    8.87  
    8.88 -@end
    8.89 -
    8.90 -
    8.91 -
    8.92 -@interface MYBonjourResolveOperation : ConcurrentOperation
    8.93 -{
    8.94 -    MYBonjourService *_service;
    8.95 -    NSSet *_addresses;
    8.96 -}
    8.97 -
    8.98 -@property (readonly) MYBonjourService *service;
    8.99 -@property (readonly,retain) NSSet *addresses;
   8.100 +//@}
   8.101  
   8.102  @end
     9.1 --- a/Bonjour/MYBonjourService.m	Tue Apr 28 10:36:28 2009 -0700
     9.2 +++ b/Bonjour/MYBonjourService.m	Wed Apr 29 13:29:31 2009 -0700
     9.3 @@ -30,36 +30,41 @@
     9.4  - (id) initWithName: (NSString*)serviceName
     9.5                 type: (NSString*)type
     9.6               domain: (NSString*)domain
     9.7 -          interface: (uint32)interfaceIndex
     9.8 +          interface: (UInt32)interfaceIndex
     9.9  {
    9.10 +    Assert(serviceName);
    9.11 +    Assert(type);
    9.12 +    Assert(domain);
    9.13      self = [super init];
    9.14      if (self != nil) {
    9.15          _name = [serviceName copy];
    9.16          _type = [type copy];
    9.17          _domain = [domain copy];
    9.18 +        _fullName = [[[self class] fullNameOfService: _name ofType: _type inDomain: _domain] retain];
    9.19          _interfaceIndex = interfaceIndex;
    9.20      }
    9.21      return self;
    9.22  }
    9.23  
    9.24  - (void) dealloc {
    9.25 -    [_name release];
    9.26 -    [_type release];
    9.27 -    [_domain release];
    9.28 -    [_hostname release];
    9.29      [_txtQuery stop];
    9.30      [_txtQuery release];
    9.31      [_addressLookup stop];
    9.32      [_addressLookup release];
    9.33 +    [_name release];
    9.34 +    [_type release];
    9.35 +    [_domain release];
    9.36 +    [_fullName release];
    9.37 +    [_hostname release];
    9.38      [super dealloc];
    9.39  }
    9.40  
    9.41  
    9.42 -@synthesize name=_name, type=_type, domain=_domain, interfaceIndex=_interfaceIndex;
    9.43 +@synthesize name=_name, type=_type, domain=_domain, fullName=_fullName, interfaceIndex=_interfaceIndex;
    9.44  
    9.45  
    9.46  - (NSString*) description {
    9.47 -    return $sprintf(@"%@['%@'.%@%@]", self.class,_name,_type,_domain);
    9.48 +    return $sprintf(@"%@[%@]", self.class,self.fullName);
    9.49  }
    9.50  
    9.51  
    9.52 @@ -101,18 +106,13 @@
    9.53  
    9.54  
    9.55  - (void) priv_finishResolve {
    9.56 -    // If I haven't finished my resolve yet, run it synchronously now so I can return a valid value:
    9.57 +    // If I haven't finished my resolve yet, run it *synchronously* now so I can return a valid value:
    9.58      if (!_startedResolve )
    9.59          [self start];
    9.60      if (self.serviceRef)
    9.61          [self waitForReply];
    9.62  }    
    9.63  
    9.64 -- (NSString*) fullName {
    9.65 -    if (!_fullName) [self priv_finishResolve];
    9.66 -    return _fullName;
    9.67 -}
    9.68 -
    9.69  - (NSString*) hostname {
    9.70      if (!_hostname) [self priv_finishResolve];
    9.71      return _hostname;
    9.72 @@ -129,9 +129,12 @@
    9.73  
    9.74  
    9.75  - (NSDictionary*) txtRecord {
    9.76 -    // If I haven't started my resolve yet, start it now. (_txtRecord will be nil till it finishes.)
    9.77 -    if (!_startedResolve)
    9.78 -        [self start];
    9.79 +    if (!_txtQuery) {
    9.80 +        _txtQuery = [[MYBonjourQuery alloc] initWithBonjourService: self 
    9.81 +                                                        recordType: kDNSServiceType_TXT];
    9.82 +        _txtQuery.continuous = YES;
    9.83 +        [_txtQuery start];
    9.84 +    }
    9.85      return _txtRecord;
    9.86  }
    9.87  
    9.88 @@ -172,32 +175,24 @@
    9.89  
    9.90  
    9.91  #pragma mark -
    9.92 -#pragma mark FULLNAME/HOSTNAME/PORT RESOLUTION:
    9.93 +#pragma mark HOSTNAME/PORT RESOLUTION:
    9.94  
    9.95  
    9.96 -- (void) priv_resolvedFullName: (NSString*)fullName
    9.97 -                      hostname: (NSString*)hostname
    9.98 +- (void) priv_resolvedHostname: (NSString*)hostname
    9.99                            port: (uint16_t)port
   9.100                       txtRecord: (NSData*)txtData
   9.101  {
   9.102 -    LogTo(Bonjour, @"%@: fullname='%@', hostname=%@, port=%u, txt=%u bytes", 
   9.103 -          self, fullName, hostname, port, txtData.length);
   9.104 +    LogTo(Bonjour, @"%@: hostname=%@, port=%u, txt=%u bytes", 
   9.105 +          self, hostname, port, txtData.length);
   9.106  
   9.107      // Don't call a setter method to set these properties: the getters are synchronous, so
   9.108      // I might already be blocked in a call to one of them, in which case creating a KV
   9.109      // notification could cause trouble...
   9.110 -    _fullName = fullName.copy;
   9.111      _hostname = hostname.copy;
   9.112      _port = port;
   9.113      
   9.114      // TXT getter is async, though, so I can use a setter to announce the data's availability:
   9.115      [self setTxtData: txtData];
   9.116 -    
   9.117 -    // Now that I know my full name, I can start a persistent query to track the TXT:
   9.118 -    _txtQuery = [[MYBonjourQuery alloc] initWithBonjourService: self 
   9.119 -                                                    recordType: kDNSServiceType_TXT];
   9.120 -    _txtQuery.continuous = YES;
   9.121 -    [_txtQuery start];
   9.122  }
   9.123  
   9.124  
   9.125 @@ -215,29 +210,26 @@
   9.126      MYBonjourService *service = context;
   9.127      @try{
   9.128          //LogTo(Bonjour, @"resolveCallback for %@ (err=%i)", service,errorCode);
   9.129 -        if (errorCode) {
   9.130 -            [service setError: errorCode];
   9.131 -        } else {
   9.132 +        if (!errorCode) {
   9.133              NSData *txtData = nil;
   9.134              if (txtRecord)
   9.135                  txtData = [NSData dataWithBytes: txtRecord length: txtLen];
   9.136 -            [service priv_resolvedFullName: [NSString stringWithUTF8String: fullname]
   9.137 -                                  hostname: [NSString stringWithUTF8String: hosttarget]
   9.138 +            [service priv_resolvedHostname: [NSString stringWithUTF8String: hosttarget]
   9.139                                        port: ntohs(port)
   9.140                                   txtRecord: txtData];
   9.141          }
   9.142      }catchAndReport(@"MYBonjourResolver query callback");
   9.143 +    [service gotResponse: errorCode];
   9.144  }
   9.145  
   9.146  
   9.147 -- (DNSServiceRef) createServiceRef {
   9.148 +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
   9.149      _startedResolve = YES;
   9.150 -    DNSServiceRef serviceRef = NULL;
   9.151 -    self.error = DNSServiceResolve(&serviceRef, 0,
   9.152 -                                   _interfaceIndex, 
   9.153 -                                   _name.UTF8String, _type.UTF8String, _domain.UTF8String,
   9.154 -                                   &resolveCallback, self);
   9.155 -    return serviceRef;
   9.156 +    return DNSServiceResolve(sdRefPtr,
   9.157 +                             kDNSServiceFlagsShareConnection,
   9.158 +                             _interfaceIndex, 
   9.159 +                             _name.UTF8String, _type.UTF8String, _domain.UTF8String,
   9.160 +                             &resolveCallback, self);
   9.161  }
   9.162  
   9.163  
    10.1 --- a/MYNetwork-iPhone.xcodeproj/project.pbxproj	Tue Apr 28 10:36:28 2009 -0700
    10.2 +++ b/MYNetwork-iPhone.xcodeproj/project.pbxproj	Wed Apr 29 13:29:31 2009 -0700
    10.3 @@ -41,6 +41,12 @@
    10.4  		278C1B2F0F9F865800954AE1 /* PortMapperTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 278C1B2D0F9F865800954AE1 /* PortMapperTest.m */; };
    10.5  		278C1B350F9F86A100954AE1 /* MYUtilities_Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 278C1B330F9F86A100954AE1 /* MYUtilities_Debug.xcconfig */; };
    10.6  		278C1B360F9F86A100954AE1 /* MYUtilities_Release.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 278C1B340F9F86A100954AE1 /* MYUtilities_Release.xcconfig */; };
    10.7 +		27D915BF0FA8EABC002B0DEC /* MYDNSService.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D915BC0FA8EABC002B0DEC /* MYDNSService.m */; };
    10.8 +		27D915C00FA8EABC002B0DEC /* MYAddressLookup.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D915BE0FA8EABC002B0DEC /* MYAddressLookup.m */; };
    10.9 +		27D915C90FA8EAD0002B0DEC /* MYBonjourBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D915C20FA8EAD0002B0DEC /* MYBonjourBrowser.m */; };
   10.10 +		27D915CA0FA8EAD0002B0DEC /* MYBonjourService.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D915C40FA8EAD0002B0DEC /* MYBonjourService.m */; };
   10.11 +		27D915CB0FA8EAD0002B0DEC /* MYBonjourQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D915C60FA8EAD0002B0DEC /* MYBonjourQuery.m */; };
   10.12 +		27D915CC0FA8EAD0002B0DEC /* MYBonjourRegistration.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D915C80FA8EAD0002B0DEC /* MYBonjourRegistration.m */; };
   10.13  		280E754F0DD40C5E005A515E /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 280E754C0DD40C5E005A515E /* MainWindow.xib */; };
   10.14  /* End PBXBuildFile section */
   10.15  
   10.16 @@ -107,6 +113,18 @@
   10.17  		278C1B2D0F9F865800954AE1 /* PortMapperTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PortMapperTest.m; sourceTree = "<group>"; };
   10.18  		278C1B330F9F86A100954AE1 /* MYUtilities_Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = MYUtilities_Debug.xcconfig; sourceTree = "<group>"; };
   10.19  		278C1B340F9F86A100954AE1 /* MYUtilities_Release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = MYUtilities_Release.xcconfig; sourceTree = "<group>"; };
   10.20 +		27D915BB0FA8EABC002B0DEC /* MYDNSService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYDNSService.h; path = PortMapper/MYDNSService.h; sourceTree = "<group>"; };
   10.21 +		27D915BC0FA8EABC002B0DEC /* MYDNSService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYDNSService.m; path = PortMapper/MYDNSService.m; sourceTree = "<group>"; };
   10.22 +		27D915BD0FA8EABC002B0DEC /* MYAddressLookup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYAddressLookup.h; path = Bonjour/MYAddressLookup.h; sourceTree = "<group>"; };
   10.23 +		27D915BE0FA8EABC002B0DEC /* MYAddressLookup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYAddressLookup.m; path = Bonjour/MYAddressLookup.m; sourceTree = "<group>"; };
   10.24 +		27D915C10FA8EAD0002B0DEC /* MYBonjourBrowser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYBonjourBrowser.h; path = Bonjour/MYBonjourBrowser.h; sourceTree = "<group>"; };
   10.25 +		27D915C20FA8EAD0002B0DEC /* MYBonjourBrowser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYBonjourBrowser.m; path = Bonjour/MYBonjourBrowser.m; sourceTree = "<group>"; };
   10.26 +		27D915C30FA8EAD0002B0DEC /* MYBonjourService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYBonjourService.h; path = Bonjour/MYBonjourService.h; sourceTree = "<group>"; };
   10.27 +		27D915C40FA8EAD0002B0DEC /* MYBonjourService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYBonjourService.m; path = Bonjour/MYBonjourService.m; sourceTree = "<group>"; };
   10.28 +		27D915C50FA8EAD0002B0DEC /* MYBonjourQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYBonjourQuery.h; path = Bonjour/MYBonjourQuery.h; sourceTree = "<group>"; };
   10.29 +		27D915C60FA8EAD0002B0DEC /* MYBonjourQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYBonjourQuery.m; path = Bonjour/MYBonjourQuery.m; sourceTree = "<group>"; };
   10.30 +		27D915C70FA8EAD0002B0DEC /* MYBonjourRegistration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYBonjourRegistration.h; path = Bonjour/MYBonjourRegistration.h; sourceTree = "<group>"; };
   10.31 +		27D915C80FA8EAD0002B0DEC /* MYBonjourRegistration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYBonjourRegistration.m; path = Bonjour/MYBonjourRegistration.m; sourceTree = "<group>"; };
   10.32  		280E754C0DD40C5E005A515E /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = "<group>"; };
   10.33  		29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = iPhone/main.m; sourceTree = "<group>"; };
   10.34  		8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
   10.35 @@ -149,11 +167,11 @@
   10.36  		270E9AA00EE61113003F17CA /* MYNetwork */ = {
   10.37  			isa = PBXGroup;
   10.38  			children = (
   10.39 -				270E9AA10EE61113003F17CA /* IPAddress.h */,
   10.40 -				270E9AA20EE61113003F17CA /* IPAddress.m */,
   10.41 +				27D915B90FA8EA85002B0DEC /* Addressing */,
   10.42 +				27D915BA0FA8EA98002B0DEC /* Bonjour */,
   10.43 +				278C1B2A0F9F865800954AE1 /* PortMapper */,
   10.44  				270E9AA30EE61113003F17CA /* TCP */,
   10.45  				270E9AAF0EE61113003F17CA /* BLIP */,
   10.46 -				278C1B2A0F9F865800954AE1 /* PortMapper */,
   10.47  			);
   10.48  			name = MYNetwork;
   10.49  			sourceTree = "<group>";
   10.50 @@ -219,8 +237,7 @@
   10.51  				270E9ADA0EE6111A003F17CA /* GoogleToolboxSubset */,
   10.52  			);
   10.53  			name = MYUtilities;
   10.54 -			path = ../MYUtilities;
   10.55 -			sourceTree = SOURCE_ROOT;
   10.56 +			sourceTree = MYUtilities;
   10.57  		};
   10.58  		270E9ADA0EE6111A003F17CA /* GoogleToolboxSubset */ = {
   10.59  			isa = PBXGroup;
   10.60 @@ -253,6 +270,34 @@
   10.61  			path = PortMapper;
   10.62  			sourceTree = "<group>";
   10.63  		};
   10.64 +		27D915B90FA8EA85002B0DEC /* Addressing */ = {
   10.65 +			isa = PBXGroup;
   10.66 +			children = (
   10.67 +				270E9AA10EE61113003F17CA /* IPAddress.h */,
   10.68 +				270E9AA20EE61113003F17CA /* IPAddress.m */,
   10.69 +				27D915BB0FA8EABC002B0DEC /* MYDNSService.h */,
   10.70 +				27D915BC0FA8EABC002B0DEC /* MYDNSService.m */,
   10.71 +				27D915BD0FA8EABC002B0DEC /* MYAddressLookup.h */,
   10.72 +				27D915BE0FA8EABC002B0DEC /* MYAddressLookup.m */,
   10.73 +			);
   10.74 +			name = Addressing;
   10.75 +			sourceTree = "<group>";
   10.76 +		};
   10.77 +		27D915BA0FA8EA98002B0DEC /* Bonjour */ = {
   10.78 +			isa = PBXGroup;
   10.79 +			children = (
   10.80 +				27D915C10FA8EAD0002B0DEC /* MYBonjourBrowser.h */,
   10.81 +				27D915C20FA8EAD0002B0DEC /* MYBonjourBrowser.m */,
   10.82 +				27D915C30FA8EAD0002B0DEC /* MYBonjourService.h */,
   10.83 +				27D915C40FA8EAD0002B0DEC /* MYBonjourService.m */,
   10.84 +				27D915C50FA8EAD0002B0DEC /* MYBonjourQuery.h */,
   10.85 +				27D915C60FA8EAD0002B0DEC /* MYBonjourQuery.m */,
   10.86 +				27D915C70FA8EAD0002B0DEC /* MYBonjourRegistration.h */,
   10.87 +				27D915C80FA8EAD0002B0DEC /* MYBonjourRegistration.m */,
   10.88 +			);
   10.89 +			name = Bonjour;
   10.90 +			sourceTree = "<group>";
   10.91 +		};
   10.92  		29B97314FDCFA39411CA2CEA /* CustomTemplate */ = {
   10.93  			isa = PBXGroup;
   10.94  			children = (
   10.95 @@ -387,6 +432,12 @@
   10.96  				270E9BA20EE64B4E003F17CA /* MyViewController.m in Sources */,
   10.97  				278C1B2E0F9F865800954AE1 /* MYPortMapper.m in Sources */,
   10.98  				278C1B2F0F9F865800954AE1 /* PortMapperTest.m in Sources */,
   10.99 +				27D915BF0FA8EABC002B0DEC /* MYDNSService.m in Sources */,
  10.100 +				27D915C00FA8EABC002B0DEC /* MYAddressLookup.m in Sources */,
  10.101 +				27D915C90FA8EAD0002B0DEC /* MYBonjourBrowser.m in Sources */,
  10.102 +				27D915CA0FA8EAD0002B0DEC /* MYBonjourService.m in Sources */,
  10.103 +				27D915CB0FA8EAD0002B0DEC /* MYBonjourQuery.m in Sources */,
  10.104 +				27D915CC0FA8EAD0002B0DEC /* MYBonjourRegistration.m in Sources */,
  10.105  			);
  10.106  			runOnlyForDeploymentPostprocessing = 0;
  10.107  		};
  10.108 @@ -417,7 +468,7 @@
  10.109  				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
  10.110  				EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIES = "*.nib *.lproj *.framework *.gch *.xcode* (*) CVS .svn .hg";
  10.111  				ONLY_ACTIVE_ARCH = YES;
  10.112 -				SDKROOT = iphonesimulator2.0;
  10.113 +				SDKROOT = iphonesimulator2.2.1;
  10.114  			};
  10.115  			name = Debug;
  10.116  		};
  10.117 @@ -427,7 +478,7 @@
  10.118  			buildSettings = {
  10.119  				ARCHS = "$(ARCHS_STANDARD_32_BIT)";
  10.120  				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
  10.121 -				SDKROOT = iphonesimulator2.0;
  10.122 +				SDKROOT = iphonesimulator2.2.1;
  10.123  			};
  10.124  			name = Release;
  10.125  		};
    11.1 --- a/MYNetwork.xcodeproj/project.pbxproj	Tue Apr 28 10:36:28 2009 -0700
    11.2 +++ b/MYNetwork.xcodeproj/project.pbxproj	Wed Apr 29 13:29:31 2009 -0700
    11.3 @@ -25,6 +25,9 @@
    11.4  		270461470DE491A6003D9D3F /* Target.m in Sources */ = {isa = PBXBuildFile; fileRef = 270461460DE491A6003D9D3F /* Target.m */; };
    11.5  		270461890DE49634003D9D3F /* CollectionUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 270461870DE49634003D9D3F /* CollectionUtils.m */; };
    11.6  		2706F1D90F9D3EF300292CCF /* SecurityInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2706F1D80F9D3EF300292CCF /* SecurityInterface.framework */; };
    11.7 +		273B457B0FA681EE00276298 /* MYBonjourRegistration.h in Headers */ = {isa = PBXBuildFile; fileRef = 273B45790FA681EE00276298 /* MYBonjourRegistration.h */; };
    11.8 +		273B457C0FA681EE00276298 /* MYBonjourRegistration.m in Sources */ = {isa = PBXBuildFile; fileRef = 273B457A0FA681EE00276298 /* MYBonjourRegistration.m */; };
    11.9 +		273B457D0FA681EE00276298 /* MYBonjourRegistration.m in Sources */ = {isa = PBXBuildFile; fileRef = 273B457A0FA681EE00276298 /* MYBonjourRegistration.m */; };
   11.10  		2777C9110F7602A7007F8D30 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2777C9100F7602A7007F8D30 /* Security.framework */; };
   11.11  		2779048B0DE9204300C6D295 /* BLIPEchoClient.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2779048A0DE9204300C6D295 /* BLIPEchoClient.xib */; };
   11.12  		277905240DE9E5BC00C6D295 /* BLIPEchoServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 277903D60DE8EE4800C6D295 /* BLIPEchoServer.m */; };
   11.13 @@ -165,6 +168,8 @@
   11.14  		270462C10DE4A64B003D9D3F /* MYUtilitiesTest_main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYUtilitiesTest_main.m; sourceTree = "<group>"; };
   11.15  		270462C30DE4A65B003D9D3F /* BLIP Overview.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "BLIP Overview.txt"; path = "BLIP/BLIP Overview.txt"; sourceTree = "<group>"; wrapsLines = 1; };
   11.16  		2706F1D80F9D3EF300292CCF /* SecurityInterface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SecurityInterface.framework; path = System/Library/Frameworks/SecurityInterface.framework; sourceTree = SDKROOT; };
   11.17 +		273B45790FA681EE00276298 /* MYBonjourRegistration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYBonjourRegistration.h; sourceTree = "<group>"; };
   11.18 +		273B457A0FA681EE00276298 /* MYBonjourRegistration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYBonjourRegistration.m; sourceTree = "<group>"; };
   11.19  		274122DD0F9CDD1600F21842 /* MYUtilities_Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = MYUtilities_Debug.xcconfig; sourceTree = "<group>"; };
   11.20  		274122DE0F9CDD1600F21842 /* MYUtilities_Release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = MYUtilities_Release.xcconfig; sourceTree = "<group>"; };
   11.21  		2777C9100F7602A7007F8D30 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
   11.22 @@ -182,8 +187,8 @@
   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 +		2780F49F0FA2C59000C0FB83 /* MYAddressLookup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYAddressLookup.h; path = Bonjour/MYAddressLookup.h; sourceTree = "<group>"; };
   11.29 +		2780F4A00FA2C59000C0FB83 /* MYAddressLookup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYAddressLookup.m; path = Bonjour/MYAddressLookup.m; sourceTree = "<group>"; };
   11.30  		278C1A340F9F687800954AE1 /* PortMapperTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PortMapperTest.m; sourceTree = "<group>"; };
   11.31  		278C1A350F9F687800954AE1 /* MYPortMapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYPortMapper.h; sourceTree = "<group>"; };
   11.32  		278C1A360F9F687800954AE1 /* MYPortMapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYPortMapper.m; sourceTree = "<group>"; };
   11.33 @@ -389,6 +394,8 @@
   11.34  				270461020DE49030003D9D3F /* IPAddress.m */,
   11.35  				2780F20A0FA194BD00C0FB83 /* MYDNSService.h */,
   11.36  				2780F20B0FA194BD00C0FB83 /* MYDNSService.m */,
   11.37 +				2780F49F0FA2C59000C0FB83 /* MYAddressLookup.h */,
   11.38 +				2780F4A00FA2C59000C0FB83 /* MYAddressLookup.m */,
   11.39  			);
   11.40  			name = Addressing;
   11.41  			sourceTree = "<group>";
   11.42 @@ -412,8 +419,8 @@
   11.43  				278C1BA10F9F92EA00954AE1 /* MYBonjourService.m */,
   11.44  				2780F4360FA28F4400C0FB83 /* MYBonjourQuery.h */,
   11.45  				2780F4370FA28F4400C0FB83 /* MYBonjourQuery.m */,
   11.46 -				2780F49F0FA2C59000C0FB83 /* MYAddressLookup.h */,
   11.47 -				2780F4A00FA2C59000C0FB83 /* MYAddressLookup.m */,
   11.48 +				273B45790FA681EE00276298 /* MYBonjourRegistration.h */,
   11.49 +				273B457A0FA681EE00276298 /* MYBonjourRegistration.m */,
   11.50  			);
   11.51  			path = Bonjour;
   11.52  			sourceTree = "<group>";
   11.53 @@ -438,6 +445,7 @@
   11.54  				2780F20C0FA194BD00C0FB83 /* MYDNSService.h in Headers */,
   11.55  				2780F4380FA28F4400C0FB83 /* MYBonjourQuery.h in Headers */,
   11.56  				2780F4A10FA2C59000C0FB83 /* MYAddressLookup.h in Headers */,
   11.57 +				273B457B0FA681EE00276298 /* MYBonjourRegistration.h in Headers */,
   11.58  			);
   11.59  			runOnlyForDeploymentPostprocessing = 0;
   11.60  		};
   11.61 @@ -595,6 +603,7 @@
   11.62  				2780F20D0FA194BD00C0FB83 /* MYDNSService.m in Sources */,
   11.63  				2780F4390FA28F4400C0FB83 /* MYBonjourQuery.m in Sources */,
   11.64  				2780F4A20FA2C59000C0FB83 /* MYAddressLookup.m in Sources */,
   11.65 +				273B457C0FA681EE00276298 /* MYBonjourRegistration.m in Sources */,
   11.66  			);
   11.67  			runOnlyForDeploymentPostprocessing = 0;
   11.68  		};
   11.69 @@ -631,6 +640,7 @@
   11.70  				2780F20E0FA194BD00C0FB83 /* MYDNSService.m in Sources */,
   11.71  				2780F43A0FA28F4400C0FB83 /* MYBonjourQuery.m in Sources */,
   11.72  				2780F4A30FA2C59000C0FB83 /* MYAddressLookup.m in Sources */,
   11.73 +				273B457D0FA681EE00276298 /* MYBonjourRegistration.m in Sources */,
   11.74  			);
   11.75  			runOnlyForDeploymentPostprocessing = 0;
   11.76  		};
    12.1 --- a/PortMapper/MYDNSService.h	Tue Apr 28 10:36:28 2009 -0700
    12.2 +++ b/PortMapper/MYDNSService.h	Wed Apr 29 13:29:31 2009 -0700
    12.3 @@ -8,17 +8,20 @@
    12.4  
    12.5  #import <Foundation/Foundation.h>
    12.6  #import <CoreFoundation/CFSocket.h>
    12.7 +@class MYDNSConnection;
    12.8  
    12.9  
   12.10  /** Abstract superclass for services based on DNSServiceRefs, such as MYPortMapper. */
   12.11  @interface MYDNSService : NSObject
   12.12  {
   12.13      @private
   12.14 +    BOOL _usePrivateConnection;
   12.15 +    MYDNSConnection *_connection;
   12.16      struct _DNSServiceRef_t *_serviceRef;
   12.17      CFSocketRef _socket;
   12.18      CFRunLoopSourceRef _socketSource;
   12.19      SInt32 _error;
   12.20 -    BOOL _continuous;
   12.21 +    BOOL _continuous, _gotResponse;
   12.22  }
   12.23  
   12.24  /** If NO (the default), the service will stop after it gets a result.
   12.25 @@ -40,12 +43,30 @@
   12.26      This property is KV observable. */
   12.27  @property int32_t error;
   12.28  
   12.29 +
   12.30 +/** Utility to construct a service's full name. */
   12.31 ++ (NSString*) fullNameOfService: (NSString*)serviceName
   12.32 +                         ofType: (NSString*)type
   12.33 +                       inDomain: (NSString*)domain;
   12.34 +
   12.35 +
   12.36 +
   12.37  // PROTECTED:
   12.38  
   12.39 +
   12.40 +@property BOOL usePrivateConnection;
   12.41 +
   12.42  /** Subclass must implement this abstract method to create a new DNSServiceRef.
   12.43      This method is called by -open.
   12.44 -    If an error occurs, the method should set self.error and return NULL.*/
   12.45 -- (struct _DNSServiceRef_t*) createServiceRef;
   12.46 +    The implementation MUST pass the given sdRefPtr directly to the DNSService function
   12.47 +    that creates the new ref, without setting it to NULL first.
   12.48 +    It MUST also set the kDNSServiceFlagsShareConnection flag. */
   12.49 +- (int32_t/*DNSServiceErrorType*/) createServiceRef: (struct _DNSServiceRef_t**)sdRefPtr;
   12.50 +
   12.51 +/** Subclass's callback must call this method after doing its own work.
   12.52 +    This method will update the error state, and will stop the service if it's not set to be
   12.53 +    continuous. */
   12.54 +- (void) gotResponse: (int32_t/*DNSServiceErrorType*/)errorCode;
   12.55  
   12.56  @property (readonly) struct _DNSServiceRef_t* serviceRef;
   12.57  
   12.58 @@ -58,4 +79,23 @@
   12.59      @return  YES if a message is received, NO on error (or if the service isn't started) */
   12.60  - (BOOL) waitForReply;
   12.61  
   12.62 +
   12.63  @end
   12.64 +
   12.65 +
   12.66 +
   12.67 +
   12.68 +@interface MYDNSConnection : NSObject
   12.69 +{
   12.70 +    struct _DNSServiceRef_t* _connectionRef;
   12.71 +    CFSocketRef _socket;
   12.72 +    CFRunLoopSourceRef _runLoopSource;
   12.73 +}
   12.74 +
   12.75 ++ (MYDNSConnection*) sharedConnection;
   12.76 +- (id) initWithServiceRef: (struct _DNSServiceRef_t *)serviceRef;
   12.77 +@property (readonly) struct _DNSServiceRef_t* connectionRef;
   12.78 +- (BOOL) processResult;
   12.79 +- (void) close;
   12.80 +
   12.81 +@end
    13.1 --- a/PortMapper/MYDNSService.m	Tue Apr 28 10:36:28 2009 -0700
    13.2 +++ b/PortMapper/MYDNSService.m	Wed Apr 29 13:29:31 2009 -0700
    13.3 @@ -20,7 +20,7 @@
    13.4                              CFDataRef address,
    13.5                              const void *data,
    13.6                              void *clientCallBackInfo);
    13.7 -
    13.8 +        
    13.9  
   13.10  @implementation MYDNSService
   13.11  
   13.12 @@ -52,14 +52,25 @@
   13.13  }
   13.14  
   13.15  
   13.16 -@synthesize continuous=_continuous, serviceRef=_serviceRef;
   13.17 +@synthesize continuous=_continuous, serviceRef=_serviceRef, usePrivateConnection=_usePrivateConnection;
   13.18  
   13.19  
   13.20 -- (DNSServiceRef) createServiceRef {
   13.21 +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
   13.22      AssertAbstractMethod();
   13.23  }
   13.24  
   13.25  
   13.26 +- (void) gotResponse: (DNSServiceErrorType)errorCode {
   13.27 +    _gotResponse = YES;
   13.28 +    if (!_continuous)
   13.29 +        [self cancel];
   13.30 +    if (errorCode && errorCode != _error) {
   13.31 +        Log(@"%@ got error %i", self,errorCode);
   13.32 +        self.error = errorCode;
   13.33 +    }
   13.34 +}
   13.35 +
   13.36 +
   13.37  - (BOOL) start
   13.38  {
   13.39      if (_serviceRef)
   13.40 @@ -67,55 +78,60 @@
   13.41  
   13.42      if (_error)
   13.43          self.error = 0;
   13.44 +    _gotResponse = NO;
   13.45  
   13.46 +    if (!_usePrivateConnection) {
   13.47 +        _connection = [[MYDNSConnection sharedConnection] retain];
   13.48 +        if (!_connection) {
   13.49 +            self.error = kDNSServiceErr_Unknown;
   13.50 +            return NO;
   13.51 +        }
   13.52 +        _serviceRef = _connection.connectionRef;
   13.53 +    }
   13.54 +    
   13.55      // Ask the subclass to create a DNSServiceRef:
   13.56 -    _serviceRef = [self createServiceRef];
   13.57 +    _error = [self createServiceRef: &_serviceRef];
   13.58 +    if (_error) {
   13.59 +        _serviceRef = NULL;
   13.60 +        setObj(&_connection,nil);
   13.61 +        if (!_error)
   13.62 +            self.error = kDNSServiceErr_Unknown;
   13.63 +        LogTo(DNS,@"Failed to open %@ -- err=%i",self,_error);
   13.64 +        return NO;
   13.65 +    }
   13.66      
   13.67 -    if (_serviceRef) {
   13.68 -        // Wrap a CFSocket around the service's socket:
   13.69 -        CFSocketContext ctxt = { 0, self, CFRetain, CFRelease, NULL };
   13.70 -        _socket = CFSocketCreateWithNative(NULL, 
   13.71 -                                           DNSServiceRefSockFD(_serviceRef), 
   13.72 -                                           kCFSocketReadCallBack, 
   13.73 -                                           &serviceCallback, &ctxt);
   13.74 -        if( _socket ) {
   13.75 -            CFSocketSetSocketFlags(_socket, CFSocketGetSocketFlags(_socket) & ~kCFSocketCloseOnInvalidate);
   13.76 -            // Attach the socket to the runloop so the serviceCallback will be invoked:
   13.77 -            _socketSource = CFSocketCreateRunLoopSource(NULL, _socket, 0);
   13.78 -            if( _socketSource ) {
   13.79 -                CFRunLoopAddSource(CFRunLoopGetCurrent(), _socketSource, kCFRunLoopCommonModes);
   13.80 -                LogTo(DNS,@"Opening %@ -- service=%p",self,_serviceRef);
   13.81 -                return YES; // success
   13.82 -            }
   13.83 -        }
   13.84 -    }
   13.85 -    if (!_error)
   13.86 -        self.error = kDNSServiceErr_Unknown;
   13.87 -    LogTo(DNS,@"Failed to open %@ -- err=%i",self,_error);
   13.88 -    [self cancel];
   13.89 -    return NO;
   13.90 +    if (!_connection)
   13.91 +        _connection = [[MYDNSConnection alloc] initWithServiceRef: _serviceRef];
   13.92 +    
   13.93 +    LogTo(DNS,@"Started %@",self);
   13.94 +    return YES; // Succeeded
   13.95 +}
   13.96 +
   13.97 +
   13.98 +- (BOOL) waitForReply {
   13.99 +    if( ! _serviceRef )
  13.100 +        if( ! [self start] )
  13.101 +            return NO;
  13.102 +    // Run the runloop until there's either an error or a result:
  13.103 +    _gotResponse = NO;
  13.104 +    LogTo(DNS,@"Waiting for reply to %@...", self);
  13.105 +    while( !_gotResponse )
  13.106 +        if( ! [_connection processResult] )
  13.107 +            break;
  13.108 +    LogTo(DNS,@"    ...got reply");
  13.109 +    return (self.error==0);
  13.110  }
  13.111  
  13.112  
  13.113  - (void) cancel
  13.114  {
  13.115 -    [self retain];            // Prevents _socket's dealloc from releasing & deallocing me!
  13.116 -    if( _socketSource ) {
  13.117 -        CFRunLoopSourceInvalidate(_socketSource);
  13.118 -        CFRelease(_socketSource);
  13.119 -        _socketSource = NULL;
  13.120 -    }
  13.121 -    if( _socket ) {
  13.122 -        CFSocketInvalidate(_socket);
  13.123 -        CFRelease(_socket);
  13.124 -        _socket = NULL;
  13.125 -    }
  13.126      if( _serviceRef ) {
  13.127          LogTo(DNS,@"Stopped %@",self);
  13.128          DNSServiceRefDeallocate(_serviceRef);
  13.129          _serviceRef = NULL;
  13.130 +        
  13.131 +        setObj(&_connection,nil);
  13.132      }
  13.133 -    [self release];
  13.134  }
  13.135  
  13.136  
  13.137 @@ -127,31 +143,159 @@
  13.138  }
  13.139  
  13.140  
  13.141 -- (BOOL) priv_processResult
  13.142 ++ (NSString*) fullNameOfService: (NSString*)serviceName
  13.143 +                         ofType: (NSString*)type
  13.144 +                       inDomain: (NSString*)domain
  13.145  {
  13.146 -    Assert(_serviceRef);
  13.147 -    DNSServiceErrorType err = DNSServiceProcessResult(_serviceRef);
  13.148 -    if (err) {
  13.149 -        // An error here means the socket has failed and should be closed.
  13.150 -        self.error = err;
  13.151 -        [self cancel];
  13.152 -        return NO;
  13.153 -    } else {
  13.154 -        if (!_continuous)
  13.155 -            [self cancel];
  13.156 -        return YES;
  13.157 +    //FIX: Do I need to un-escape the serviceName?
  13.158 +    Assert(type);
  13.159 +    Assert(domain);
  13.160 +    char fullName[kDNSServiceMaxDomainName];
  13.161 +    if (DNSServiceConstructFullName(fullName, serviceName.UTF8String, type.UTF8String, domain.UTF8String) == 0)
  13.162 +        return [NSString stringWithUTF8String: fullName];
  13.163 +    else
  13.164 +        return nil;
  13.165 +}
  13.166 +
  13.167 +
  13.168 +@end
  13.169 +
  13.170 +
  13.171 +#pragma mark -
  13.172 +#pragma mark SHARED CONNECTION:
  13.173 +
  13.174 +
  13.175 +@interface MYDNSConnection ()
  13.176 +- (BOOL) open;
  13.177 +@end
  13.178 +
  13.179 +
  13.180 +@implementation MYDNSConnection
  13.181 +
  13.182 +
  13.183 +MYDNSConnection *sSharedConnection;
  13.184 +
  13.185 +
  13.186 +- (id) init
  13.187 +{
  13.188 +    DNSServiceRef connectionRef = NULL;
  13.189 +    DNSServiceErrorType err = DNSServiceCreateConnection(&connectionRef);
  13.190 +    if (err || !connectionRef) {
  13.191 +        Warn(@"MYDNSConnection: DNSServiceCreateConnection failed, err=%i", err);
  13.192 +        [self release];
  13.193 +        return nil;
  13.194 +    }
  13.195 +    return [self initWithServiceRef: connectionRef];
  13.196 +}
  13.197 +
  13.198 +
  13.199 +- (id) initWithServiceRef: (DNSServiceRef)serviceRef
  13.200 +{
  13.201 +    Assert(serviceRef);
  13.202 +    self = [super init];
  13.203 +    if (self != nil) {
  13.204 +        _connectionRef = serviceRef;
  13.205 +        LogTo(DNS,@"INIT %@", self);
  13.206 +        if (![self open]) {
  13.207 +            [self release];
  13.208 +            return nil;
  13.209 +        }
  13.210 +    }
  13.211 +    return self;
  13.212 +}
  13.213 +
  13.214 +
  13.215 ++ (MYDNSConnection*) sharedConnection {
  13.216 +    @synchronized(self) {
  13.217 +        if (!sSharedConnection)
  13.218 +            sSharedConnection = [[[self alloc] init] autorelease];
  13.219 +    }
  13.220 +    return sSharedConnection;
  13.221 +}
  13.222 +
  13.223 +
  13.224 +- (void) dealloc
  13.225 +{
  13.226 +    LogTo(DNS,@"DEALLOC %@", self);
  13.227 +    [self close];
  13.228 +    [super dealloc];
  13.229 +}
  13.230 +
  13.231 +- (void) finalize {
  13.232 +    [self close];
  13.233 +    [super finalize];
  13.234 +}
  13.235 +
  13.236 +
  13.237 +@synthesize connectionRef=_connectionRef;
  13.238 +
  13.239 +- (NSString*) description {
  13.240 +    return $sprintf(@"%@[conn=%p]", self.class,_connectionRef);
  13.241 +}
  13.242 +
  13.243 +- (BOOL) open {
  13.244 +    if (_runLoopSource)
  13.245 +        return YES;        // Already opened
  13.246 +    
  13.247 +    // Wrap a CFSocket around the service's socket:
  13.248 +    CFSocketContext ctxt = { 0, self, CFRetain, CFRelease, NULL };
  13.249 +    _socket = CFSocketCreateWithNative(NULL, 
  13.250 +                                                       DNSServiceRefSockFD(_connectionRef), 
  13.251 +                                                       kCFSocketReadCallBack, 
  13.252 +                                                       &serviceCallback, &ctxt);
  13.253 +    if( _socket ) {
  13.254 +        CFSocketSetSocketFlags(_socket, 
  13.255 +                               CFSocketGetSocketFlags(_socket) & ~kCFSocketCloseOnInvalidate);
  13.256 +        // Attach the socket to the runloop so the serviceCallback will be invoked:
  13.257 +        _runLoopSource = CFSocketCreateRunLoopSource(NULL, _socket, 0);
  13.258 +        if( _runLoopSource ) {
  13.259 +            CFRunLoopAddSource(CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes);
  13.260 +            // Success!
  13.261 +            LogTo(DNS,@"Successfully opened %@", self);
  13.262 +            return YES;
  13.263 +        }
  13.264 +    }
  13.265 +    
  13.266 +    // Failure:
  13.267 +    Warn(@"Failed to connect %@ to runloop", self);
  13.268 +    [self close];
  13.269 +    return NO;
  13.270 +}
  13.271 +
  13.272 +
  13.273 +- (void) close {
  13.274 +    @synchronized(self) {
  13.275 +        if( _runLoopSource ) {
  13.276 +            CFRunLoopSourceInvalidate(_runLoopSource);
  13.277 +            CFRelease(_runLoopSource);
  13.278 +            _runLoopSource = NULL;
  13.279 +        }
  13.280 +        if( _socket ) {
  13.281 +            CFSocketInvalidate(_socket);
  13.282 +            CFRelease(_socket);
  13.283 +            _socket = NULL;
  13.284 +        }
  13.285 +        if( _connectionRef ) {
  13.286 +            LogTo(DNS,@"Closed %@",self);
  13.287 +            DNSServiceRefDeallocate(_connectionRef);
  13.288 +            _connectionRef = NULL;
  13.289 +        }
  13.290 +        
  13.291 +        if (self==sSharedConnection)
  13.292 +            sSharedConnection = nil;
  13.293      }
  13.294  }
  13.295  
  13.296 -- (BOOL) waitForReply
  13.297 -{
  13.298 -    if (!_serviceRef)
  13.299 -        return NO;
  13.300 -    LogTo(DNS,@"Waiting for %@ ...", self);
  13.301 -    BOOL ok = [self priv_processResult];
  13.302 -    LogTo(DNS,@"    ...done waiting");
  13.303 -    return ok;
  13.304 -}    
  13.305 +
  13.306 +- (BOOL) processResult {
  13.307 +    NSAutoreleasePool *pool = [NSAutoreleasePool new];
  13.308 +    LogTo(DNS,@"---serviceCallback----");
  13.309 +    DNSServiceErrorType err = DNSServiceProcessResult(_connectionRef);
  13.310 +    if (err)
  13.311 +        Warn(@"%@: DNSServiceProcessResult failed, err=%i !!!", self,err);
  13.312 +    [pool drain];
  13.313 +    return !err;
  13.314 +}
  13.315  
  13.316  
  13.317  /** CFSocket callback, informing us that _socket has data available, which means
  13.318 @@ -161,11 +305,8 @@
  13.319                              CFSocketCallBackType type,
  13.320                              CFDataRef address, const void *data, void *clientCallBackInfo)
  13.321  {
  13.322 -    NSAutoreleasePool *pool = [NSAutoreleasePool new];
  13.323 -    @try{
  13.324 -        [(MYDNSService*)clientCallBackInfo priv_processResult];
  13.325 -    }catchAndReport(@"PortMapper serviceCallback");
  13.326 -    [pool drain];
  13.327 +    MYDNSConnection *connection = clientCallBackInfo;
  13.328 +    [connection processResult];
  13.329  }
  13.330  
  13.331  
    14.1 --- a/PortMapper/MYPortMapper.m	Tue Apr 28 10:36:28 2009 -0700
    14.2 +++ b/PortMapper/MYPortMapper.m	Wed Apr 29 13:29:31 2009 -0700
    14.3 @@ -94,8 +94,6 @@
    14.4              errorCode = kDNSServiceErr_NATPortMappingUnsupported;
    14.5          }
    14.6      }
    14.7 -    if( errorCode != self.error )
    14.8 -        self.error = errorCode;
    14.9  
   14.10      [self priv_updateLocalAddress];
   14.11      IPAddress *publicAddress = makeIPAddr(rawPublicAddress,publicPort);
   14.12 @@ -106,6 +104,8 @@
   14.13          LogTo(PortMapper,@"%@: Public addr is %@ (mapped=%i)",
   14.14                self, self.publicAddress, self.isMapped);
   14.15      }
   14.16 +
   14.17 +    [self gotResponse: errorCode];
   14.18      [[NSNotificationCenter defaultCenter] postNotificationName: MYPortMapperChangedNotification
   14.19                                                          object: self];
   14.20  }
   14.21 @@ -135,22 +135,19 @@
   14.22  }
   14.23  
   14.24  
   14.25 -- (DNSServiceRef) createServiceRef
   14.26 -{
   14.27 +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
   14.28      DNSServiceProtocol protocols = 0;
   14.29      if( _mapTCP ) protocols |= kDNSServiceProtocol_TCP;
   14.30      if( _mapUDP ) protocols |= kDNSServiceProtocol_UDP;
   14.31 -    DNSServiceRef serviceRef = NULL;
   14.32 -    self.error = DNSServiceNATPortMappingCreate(&serviceRef, 
   14.33 -                                                0 /*flags*/, 
   14.34 -                                                0 /*interfaceIndex*/, 
   14.35 -                                                protocols,
   14.36 -                                                htons(_localPort),
   14.37 -                                                htons(_desiredPublicPort),
   14.38 -                                                0 /*ttl*/,
   14.39 -                                                &portMapCallback, 
   14.40 -                                                self);
   14.41 -    return serviceRef;
   14.42 +    return DNSServiceNATPortMappingCreate(sdRefPtr, 
   14.43 +                                          kDNSServiceFlagsShareConnection, 
   14.44 +                                          0 /*interfaceIndex*/, 
   14.45 +                                          protocols,
   14.46 +                                          htons(_localPort),
   14.47 +                                          htons(_desiredPublicPort),
   14.48 +                                          0 /*ttl*/,
   14.49 +                                          &portMapCallback, 
   14.50 +                                          self);
   14.51  }
   14.52  
   14.53