Rewrote the Bonjour classes, using the low-level <dns_sd.h> API. They're now subclasses of MYDNSService.
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...",