# HG changeset patch # User Jens Alfke # Date 1240848236 25200 # Node ID 732576fa8a0d3ee58ab4572e1f12345ba03fc3fe # Parent 92581f26073e08387dc223657965bc5a48b1b847 Rewrote the Bonjour classes, using the low-level API. They're now subclasses of MYDNSService. diff -r 92581f26073e -r 732576fa8a0d Bonjour/MYAddressLookup.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Bonjour/MYAddressLookup.h Mon Apr 27 09:03:56 2009 -0700 @@ -0,0 +1,42 @@ +// +// MYAddressLookup.h +// MYNetwork +// +// Created by Jens Alfke on 4/24/09. +// Copyright 2009 Jens Alfke. All rights reserved. +// + +#import "MYDNSService.h" + + +/** An asynchronous DNS address lookup. Supports both Bonjour services and traditional hostnames. */ +@interface MYAddressLookup : MYDNSService +{ + NSString *_hostname; + UInt16 _interfaceIndex; + NSMutableSet *_addresses; + UInt16 _port; + CFAbsoluteTime _expires; +} + +/** Initializes the lookup with a DNS hostname. */ +- (id) initWithHostname: (NSString*)hostname; + +/** The port number; this will be copied into the resulting IPAddress objects. + Defaults to zero, but you can set it before calling -start. */ +@property UInt16 port; + +/** The index of the network interface. You usually don't need to set this. */ +@property UInt16 interfaceIndex; + +/** The resulting address(es) of the host, as HostAddress objects. */ +@property (readonly) NSSet *addresses; + +/** How much longer the addresses will remain valid. + If the value is zero, the addresses are no longer valid, and you should instead + call -start again and wait for the 'addresses' property to update. + If you set the service to continuous mode, addresses will never expire since the + query will continue to update them. */ +@property (readonly) NSTimeInterval timeToLive; + +@end diff -r 92581f26073e -r 732576fa8a0d Bonjour/MYAddressLookup.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Bonjour/MYAddressLookup.m Mon Apr 27 09:03:56 2009 -0700 @@ -0,0 +1,122 @@ +// +// MYAddressLookup.m +// MYNetwork +// +// Created by Jens Alfke on 4/24/09. +// Copyright 2009 Jens Alfke. All rights reserved. +// + +#import "MYAddressLookup.h" +#import "IPAddress.h" +#import "ExceptionUtils.h" +#import "Test.h" +#import "Logging.h" +#import + + +@implementation MYAddressLookup + +- (id) initWithHostname: (NSString*)hostname +{ + self = [super init]; + if (self != nil) { + if (!hostname) { + [self release]; + return nil; + } + _hostname = [hostname copy]; + _addresses = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void) dealloc +{ + [_hostname release]; + [_addresses release]; + [super dealloc]; +} + + +- (NSString*) description +{ + return $sprintf(@"%@[%@]", self.class,_hostname); +} + + +@synthesize port=_port, interfaceIndex=_interfaceIndex, addresses=_addresses; + + +- (NSTimeInterval) timeToLive { + return MAX(0.0, _expires - CFAbsoluteTimeGetCurrent()); +} + + +- (void) priv_resolvedAddress: (const struct sockaddr*)sockaddr + ttl: (uint32_t)ttl + flags: (DNSServiceFlags)flags +{ + HostAddress *address = [[HostAddress alloc] initWithHostname: _hostname + sockaddr: sockaddr + port: _port]; + if (address) { + if (flags & kDNSServiceFlagsAdd) + [_addresses addObject: address]; + else + [_addresses removeObject: address]; + [address release]; + } + + _expires = CFAbsoluteTimeGetCurrent() + ttl; + + if (!(flags & kDNSServiceFlagsMoreComing)) + LogTo(DNS,@"Got addresses of %@: %@ [TTL = %u]", self, _addresses, ttl); +} + + +static void lookupCallback(DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *hostname, + const struct sockaddr *address, + uint32_t ttl, + void *context) +{ + MYAddressLookup *lookup = context; + @try{ + //LogTo(Bonjour, @"lookupCallback for %s (err=%i)", hostname,errorCode); + if (errorCode) + [lookup setError: errorCode]; + else + [lookup priv_resolvedAddress: address ttl: ttl flags: flags]; + }catchAndReport(@"MYDNSLookup query callback"); +} + + +- (DNSServiceRef) createServiceRef { + [_addresses removeAllObjects]; + DNSServiceRef serviceRef = NULL; + self.error = DNSServiceGetAddrInfo(&serviceRef, 0, + _interfaceIndex, 0, + _hostname.UTF8String, + &lookupCallback, self); + return serviceRef; +} + + +@end + + + +TestCase(MYDNSLookup) { + EnableLogTo(Bonjour,YES); + EnableLogTo(DNS,YES); + [NSRunLoop currentRunLoop]; // create runloop + + MYAddressLookup *lookup = [[MYAddressLookup alloc] initWithHostname: @"www.apple.com" port: 80]; + [lookup start]; + + [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 10]]; + [lookup release]; +} diff -r 92581f26073e -r 732576fa8a0d Bonjour/MYBonjourBrowser.h --- a/Bonjour/MYBonjourBrowser.h Fri Apr 24 10:10:32 2009 -0700 +++ b/Bonjour/MYBonjourBrowser.h Mon Apr 27 09:03:56 2009 -0700 @@ -6,17 +6,15 @@ // Copyright 2008 Jens Alfke. All rights reserved. // -#import +#import "MYDNSService.h" /** Searches for Bonjour services of a specific type. */ -@interface MYBonjourBrowser : NSObject +@interface MYBonjourBrowser : MYDNSService { @private NSString *_serviceType; - NSNetServiceBrowser *_browser; BOOL _browsing; - NSError *_error; Class _serviceClass; NSMutableSet *_services, *_addServices, *_rmvServices; } @@ -26,19 +24,9 @@ @param serviceType The name of the service type to look for, e.g. "_http._tcp". */ - (id) initWithServiceType: (NSString*)serviceType; -/** Starts browsing. This is asynchronous, so nothing will happen immediately. */ -- (void) start; - -/** Stops browsing. */ -- (void) stop; - /** Is the browser currently browsing? */ @property (readonly) BOOL browsing; -/** The current error status, if any. - This is KV-observable. */ -@property (readonly,retain) NSError* error; - /** The set of currently found services. These are instances of the serviceClass, which is BonjourService by default. This is KV-observable. */ diff -r 92581f26073e -r 732576fa8a0d Bonjour/MYBonjourBrowser.m --- a/Bonjour/MYBonjourBrowser.m Fri Apr 24 10:10:32 2009 -0700 +++ b/Bonjour/MYBonjourBrowser.m Mon Apr 27 09:03:56 2009 -0700 @@ -8,13 +8,24 @@ #import "MYBonjourBrowser.h" #import "MYBonjourService.h" +#import "ExceptionUtils.h" #import "Test.h" #import "Logging.h" +#import +static void browseCallback (DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *serviceName, + const char *regtype, + const char *replyDomain, + void *context); + @interface MYBonjourBrowser () @property BOOL browsing; -@property (retain) NSError* error; +- (void) _updateServiceList; @end @@ -26,9 +37,8 @@ Assert(serviceType); self = [super init]; if (self != nil) { + self.continuous = YES; _serviceType = [serviceType copy]; - _browser = [[NSNetServiceBrowser alloc] init]; - _browser.delegate = self; _services = [[NSMutableSet alloc] init]; _addServices = [[NSMutableSet alloc] init]; _rmvServices = [[NSMutableSet alloc] init]; @@ -41,11 +51,7 @@ - (void) dealloc { LogTo(Bonjour,@"DEALLOC BonjourBrowser"); - [_browser stop]; - _browser.delegate = nil; - [_browser release]; [_serviceType release]; - [_error release]; [_services release]; [_addServices release]; [_rmvServices release]; @@ -53,40 +59,64 @@ } -@synthesize browsing=_browsing, error=_error, services=_services, serviceClass=_serviceClass; +@synthesize browsing=_browsing, services=_services, serviceClass=_serviceClass; -- (void) start +- (NSString*) description { - [_browser searchForServicesOfType: _serviceType inDomain: @"local."]; + return $sprintf(@"%@[%@]", self.class,_serviceType); } -- (void) stop -{ - [_browser stop]; + +- (DNSServiceRef) createServiceRef { + DNSServiceRef serviceRef = NULL; + self.error = DNSServiceBrowse(&serviceRef, 0, 0, + _serviceType.UTF8String, NULL, + &browseCallback, self); + return serviceRef; } -- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)netServiceBrowser -{ - LogTo(Bonjour,@"%@ started browsing",self); - self.browsing = YES; +- (void) priv_gotError: (DNSServiceErrorType)errorCode { + LogTo(Bonjour,@"%@ got error %i", self,errorCode); + self.error = errorCode; } -- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)netServiceBrowser +- (void) priv_gotServiceName: (NSString*)serviceName + type: (NSString*)regtype + domain: (NSString*)domain + interface: (uint32_t)interfaceIndex + flags: (DNSServiceFlags)flags { - LogTo(Bonjour,@"%@ stopped browsing",self); - self.browsing = NO; -} - -- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser - didNotSearch:(NSDictionary *)errorDict -{ - NSString *domain = [errorDict objectForKey: NSNetServicesErrorDomain]; - int err = [[errorDict objectForKey: NSNetServicesErrorCode] intValue]; - self.error = [NSError errorWithDomain: domain code: err userInfo: nil]; - LogTo(Bonjour,@"%@ got error: ",self,self.error); - self.browsing = NO; + // Create (or reuse existing) MYBonjourService object: + MYBonjourService *service = [[_serviceClass alloc] initWithName: serviceName + type: regtype + domain: domain + interface: interfaceIndex]; + MYBonjourService *existingService = [_services member: service]; + if( existingService ) { + [service release]; + service = [existingService retain]; + } + + // Add it to the add/remove sets: + NSMutableSet *addTo, *removeFrom; + if (flags & kDNSServiceFlagsAdd) { + addTo = _addServices; + removeFrom = _rmvServices; + } else { + addTo = _rmvServices; + removeFrom = _addServices; + } + if( [removeFrom containsObject: service] ) + [removeFrom removeObject: service]; + else + [addTo addObject: service]; + [service release]; + + // After a round of updates is done, do the update: + if( ! (flags & kDNSServiceFlagsMoreComing) ) + [self _updateServiceList]; } @@ -117,42 +147,26 @@ } -- (void) _handleService: (NSNetService*)netService - addTo: (NSMutableSet*)addTo - removeFrom: (NSMutableSet*)removeFrom - moreComing: (BOOL)moreComing +static void browseCallback (DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *serviceName, + const char *regtype, + const char *replyDomain, + void *context) { - // Wrap the NSNetService in a BonjourService, using an existing instance if possible: - MYBonjourService *service = [[_serviceClass alloc] initWithNetService: netService]; - MYBonjourService *existingService = [_services member: service]; - if( existingService ) { - [service release]; - service = [existingService retain]; - } - - if( [removeFrom containsObject: service] ) - [removeFrom removeObject: service]; - else - [addTo addObject: service]; - [service release]; - if( ! moreComing ) - [self _updateServiceList]; -} - -- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser - didFindService:(NSNetService *)netService - moreComing:(BOOL)moreComing -{ - //LogTo(Bonjour,@"Add service %@",netService); - [self _handleService: netService addTo: _addServices removeFrom: _rmvServices moreComing: moreComing]; -} - -- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser - didRemoveService:(NSNetService *)netService - moreComing:(BOOL)moreComing -{ - //LogTo(Bonjour,@"Remove service %@",netService); - [self _handleService: netService addTo: _rmvServices removeFrom: _addServices moreComing: moreComing]; + @try{ + //LogTo(Bonjour,@"browseCallback (error=%i, name='%s')", errorCode,serviceName); + if (errorCode) + [(MYBonjourBrowser*)context priv_gotError: errorCode]; + else + [(MYBonjourBrowser*)context priv_gotServiceName: [NSString stringWithUTF8String: serviceName] + type: [NSString stringWithUTF8String: regtype] + domain: [NSString stringWithUTF8String: replyDomain] + interface: interfaceIndex + flags: flags]; + }catchAndReport(@"Bonjour"); } @@ -163,6 +177,11 @@ #pragma mark - #pragma mark TESTING: +#if DEBUG + +#import "MYBonjourQuery.h" +#import "MYAddressLookup.h" + @interface BonjourTester : NSObject { MYBonjourBrowser *_browser; @@ -175,7 +194,7 @@ { self = [super init]; if (self != nil) { - _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_http._tcp"]; + _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_presence._tcp"]; [_browser addObserver: self forKeyPath: @"services" options: NSKeyValueObservingOptionNew context: NULL]; [_browser addObserver: self forKeyPath: @"browsing" options: NSKeyValueObservingOptionNew context: NULL]; [_browser start]; @@ -199,7 +218,11 @@ if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) { NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey]; for( MYBonjourService *service in newServices ) { - LogTo(Bonjour,@" --> %@ : TXT=%@", service,service.txtRecord); + NSString *hostname = service.hostname; // block till it's resolved + Log(@"##### %@ : at %@:%hu, TXT=%@", + service, hostname, service.port, service.txtRecord); + service.addressLookup.continuous = YES; + [service queryForRecord: kDNSServiceType_NULL]; } } } @@ -208,12 +231,15 @@ @end TestCase(Bonjour) { + EnableLogTo(Bonjour,YES); + EnableLogTo(DNS,YES); [NSRunLoop currentRunLoop]; // create runloop BonjourTester *tester = [[BonjourTester alloc] init]; - [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 15]]; + [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1500]]; [tester release]; } +#endif /* diff -r 92581f26073e -r 732576fa8a0d Bonjour/MYBonjourQuery.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Bonjour/MYBonjourQuery.h Mon Apr 27 09:03:56 2009 -0700 @@ -0,0 +1,35 @@ +// +// MYBonjourQuery.h +// MYNetwork +// +// Created by Jens Alfke on 4/24/09. +// Copyright 2009 Jens Alfke. All rights reserved. +// + +#import "MYDNSService.h" +@class MYBonjourService; + + +/** A query for a particular DNS record (TXT, NULL, etc.) of a Bonjour service. + This class is used internally by MYBonjourService to track the TXT record; + you won't need to use it directly, unless you're interested in the contents of some other + record (such as the NULL record that iChat's _presence._tcp service uses for buddy icons.) */ +@interface MYBonjourQuery : MYDNSService +{ + @private + MYBonjourService *_bonjourService; + uint16_t _recordType; + NSData *_recordData; +} + +/** Initializes a query for a particular service and record type. + @param service The Bonjour service to query + @param recordType The DNS record type, e.g. kDNSServiceType_TXT; see the enum in . */ +- (id) initWithBonjourService: (MYBonjourService*)service + recordType: (uint16_t)recordType; + +/** The data of the DNS record, once it's been found. + This property is KV-observable. */ +@property (readonly,copy) NSData *recordData; + +@end diff -r 92581f26073e -r 732576fa8a0d Bonjour/MYBonjourQuery.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Bonjour/MYBonjourQuery.m Mon Apr 27 09:03:56 2009 -0700 @@ -0,0 +1,135 @@ +// +// MYBonjourQuery.m +// MYNetwork +// +// Created by Jens Alfke on 4/24/09. +// Copyright 2009 Jens Alfke. All rights reserved. +// + +#import "MYBonjourQuery.h" +#import "MYBonjourService.h" +#import "Test.h" +#import "Logging.h" +#import "ExceptionUtils.h" +#import + + +static NSString* kRecordTypeNames[] = { + @"0", + @"A", // = 1, /* Host address. */ + @"NS", // = 2, /* Authoritative server. */ + @"MD", // = 3, /* Mail destination. */ + @"MF", // = 4, /* Mail forwarder. */ + @"CNAME", // = 5, /* Canonical name. */ + @"SOA", // = 6, /* Start of authority zone. */ + @"MB", // = 7, /* Mailbox domain name. */ + @"MG", // = 8, /* Mail group member. */ + @"MR", // = 9, /* Mail rename name. */ + @"NULL", // = 10, /* Null resource record. */ + @"WKS", // = 11, /* Well known service. */ + @"PTR", // = 12, /* Domain name pointer. */ + @"HINFO", // = 13, /* Host information. */ + @"MINFO", // = 14, /* Mailbox information. */ + @"MX", // = 15, /* Mail routing information. */ + @"TXT" // = 16, /* One or more text strings (NOT "zero or more..."). */ + // this isn't a complete list; it just includes the most common ones. + // For the full list, see the "kDNSServiceType_..." constants in . +}; + +@interface MYBonjourQuery () +@property (copy) NSData *recordData; +@end + + +@implementation MYBonjourQuery + + +- (id) initWithBonjourService: (MYBonjourService*)service recordType: (uint16_t)recordType; +{ + self = [super init]; + if (self) { + _bonjourService = service; + _recordType = recordType; + } + return self; +} + +- (void) dealloc +{ + [_recordData release]; + [super dealloc]; +} + + +- (NSString*) description +{ + NSString *typeName; + if (_recordType <= 16) + typeName = kRecordTypeNames[_recordType]; + else + typeName = $sprintf(@"%u", _recordType); + return $sprintf(@"%@[%@ /%@]", self.class, _bonjourService.name, typeName); +} + + +@synthesize recordData=_recordData; + + +- (void) priv_gotRecordBytes: (const void *)rdata + length: (uint16_t)rdlen + type: (uint16_t)rrtype + ttl: (uint32_t)ttl + flags: (DNSServiceFlags)flags +{ + NSData *data = [NSData dataWithBytes: rdata length: rdlen]; + if (!$equal(data,_recordData)) { + if (data.length <= 16) + LogTo(Bonjour,@"%@ = %@", self, data); + else + LogTo(Bonjour,@"%@ = %@...", self, [data subdataWithRange: NSMakeRange(0,16)]); + self.recordData = data; + } + [_bonjourService queryDidUpdate: self]; +} + + +static void queryCallback( DNSServiceRef DNSServiceRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata, + uint32_t ttl, + void *context) +{ + @try{ + //LogTo(Bonjour, @"queryCallback for %@ (err=%i)", context,errorCode); + if (errorCode) + [(MYBonjourQuery*)context setError: errorCode]; + else + [(MYBonjourQuery*)context priv_gotRecordBytes: rdata + length: rdlen + type: rrtype + ttl: ttl + flags: flags]; + }catchAndReport(@"MYBonjourResolver query callback"); +} + + +- (DNSServiceRef) createServiceRef { + DNSServiceRef serviceRef = NULL; + const char *fullName = _bonjourService.fullName.UTF8String; + if (fullName) + self.error = DNSServiceQueryRecord(&serviceRef, 0, + _bonjourService.interfaceIndex, + fullName, + _recordType, kDNSServiceClass_IN, + &queryCallback, self); + return serviceRef; +} + + +@end diff -r 92581f26073e -r 732576fa8a0d Bonjour/MYBonjourService.h --- a/Bonjour/MYBonjourService.h Fri Apr 24 10:10:32 2009 -0700 +++ b/Bonjour/MYBonjourService.h Mon Apr 27 09:03:56 2009 -0700 @@ -6,54 +6,72 @@ // Copyright 2008 Jens Alfke. All rights reserved. // -#import +#import "MYDNSService.h" #import "ConcurrentOperation.h" -@class MYBonjourResolveOperation; +@class MYBonjourQuery, MYAddressLookup; /** Represents a Bonjour service discovered by a BonjourBrowser. */ -@interface MYBonjourService : NSObject +@interface MYBonjourService : MYDNSService { @private - NSNetService *_netService; + NSString *_name, *_fullName, *_type, *_domain, *_hostname; + uint32_t _interfaceIndex; + BOOL _startedResolve; + UInt16 _port; NSDictionary *_txtRecord; - NSSet *_addresses; - CFAbsoluteTime _addressesExpireAt; - MYBonjourResolveOperation *_resolveOp; + MYBonjourQuery *_txtQuery; + MYAddressLookup *_addressLookup; } /** The service's name. */ @property (readonly) NSString *name; +/** The service's type. */ +@property (readonly) NSString *type; + +/** The service's domain. */ +@property (readonly) NSString *domain; + +@property (readonly, copy) NSString *hostname; + +@property (readonly) UInt16 port; + +@property (readonly) uint32_t interfaceIndex; + +@property (readonly,copy) NSString* fullName; + /** The service's metadata dictionary, from its DNS TXT record */ @property (readonly,copy) NSDictionary *txtRecord; /** A convenience to access a single property from the TXT record. */ - (NSString*) txtStringForKey: (NSString*)key; -/** Returns a set of IPAddress objects; may be the empty set if address resolution failed, - or nil if addresses have not been resolved yet (or expired). - In the latter case, call -resolve and wait for the returned Operation to finish. */ -@property (readonly,copy) NSSet* addresses; +/** Returns a MYDNSLookup object that resolves the IP address(es) of this service. + Subsequent calls to this method will always return the same object. */ +- (MYAddressLookup*) addressLookup; -/** Starts looking up the IP address(es) of this service. - @return The NSOperation representing the lookup; you can observe this to see when it - completes, or you can observe the service's 'addresses' property. */ -- (MYBonjourResolveOperation*) resolve; - -/** The underlying NSNetSerice object. */ -@property (readonly) NSNetService *netService; +/** Starts a new MYBonjourQuery for the specified DNS record type of this service. + @param recordType The DNS record type, e.g. kDNSServiceType_TXT; see the enum in . */ +- (MYBonjourQuery*) queryForRecord: (UInt16)recordType; // Protected methods, for subclass use only: -- (id) initWithNetService: (NSNetService*)netService; +// (for subclasses to override, but not call): +- (id) initWithName: (NSString*)serviceName + type: (NSString*)type + domain: (NSString*)domain + interface: (uint32_t)interfaceIndex; -// (for subclasses to override, but not call): - (void) added; - (void) removed; - (void) txtRecordChanged; +// Internal: + +- (void) queryDidUpdate: (MYBonjourQuery*)query; + @end diff -r 92581f26073e -r 732576fa8a0d Bonjour/MYBonjourService.m --- a/Bonjour/MYBonjourService.m Fri Apr 24 10:10:32 2009 -0700 +++ b/Bonjour/MYBonjourService.m Mon Apr 27 09:03:56 2009 -0700 @@ -7,84 +7,120 @@ // #import "MYBonjourService.h" +#import "MYBonjourQuery.h" +#import "MYAddressLookup.h" #import "IPAddress.h" #import "ConcurrentOperation.h" #import "Test.h" #import "Logging.h" +#import "ExceptionUtils.h" +#import NSString* const kBonjourServiceResolvedAddressesNotification = @"BonjourServiceResolvedAddresses"; @interface MYBonjourService () -@property (copy) NSSet* addresses; @end -@interface MYBonjourResolveOperation () -@property (assign) MYBonjourService *service; -@property (retain) NSSet *addresses; -@end - - @implementation MYBonjourService -- (id) initWithNetService: (NSNetService*)netService +- (id) initWithName: (NSString*)serviceName + type: (NSString*)type + domain: (NSString*)domain + interface: (uint32)interfaceIndex { self = [super init]; if (self != nil) { - _netService = [netService retain]; - _netService.delegate = self; + _name = [serviceName copy]; + _type = [type copy]; + _domain = [domain copy]; + _interfaceIndex = interfaceIndex; } return self; } -- (void) dealloc -{ - Log(@"DEALLOC %@",self); - _netService.delegate = nil; - [_netService release]; - [_txtRecord release]; - [_addresses release]; +- (void) dealloc { + [_name release]; + [_type release]; + [_domain release]; + [_hostname release]; + [_txtQuery stop]; + [_txtQuery release]; + [_addressLookup stop]; + [_addressLookup release]; [super dealloc]; } -- (NSString*) description -{ - return $sprintf(@"%@['%@'.%@%@]", self.class,self.name,_netService.type,_netService.domain); +@synthesize name=_name, type=_type, domain=_domain, interfaceIndex=_interfaceIndex; + + +- (NSString*) description { + return $sprintf(@"%@['%@'.%@%@]", self.class,_name,_type,_domain); } -- (NSComparisonResult) compare: (id)obj -{ - return [self.name caseInsensitiveCompare: [obj name]]; +- (NSComparisonResult) compare: (id)obj { + return [_name caseInsensitiveCompare: [obj name]]; } - -- (NSNetService*) netService {return _netService;} -- (BOOL) isEqual: (id)obj {return [obj isKindOfClass: [MYBonjourService class]] && [_netService isEqual: [obj netService]];} -- (NSUInteger) hash {return _netService.hash;} -- (NSString*) name {return _netService.name;} - - -- (void) added -{ - LogTo(Bonjour,@"Added %@",_netService); +- (BOOL) isEqual: (id)obj { + if ([obj isKindOfClass: [MYBonjourService class]]) { + MYBonjourService *service = obj; + return [_name caseInsensitiveCompare: [service name]] == 0 + && $equal(_type, service->_type) + && $equal(_domain, service->_domain) + && _interfaceIndex == service->_interfaceIndex; + } else { + return NO; + } } -- (void) removed -{ - LogTo(Bonjour,@"Removed %@",_netService); - [_netService stopMonitoring]; - _netService.delegate = nil; +- (NSUInteger) hash { + return _name.hash ^ _type.hash ^ _domain.hash; +} + + +- (void) added { + LogTo(Bonjour,@"Added %@",self); +} + +- (void) removed { + LogTo(Bonjour,@"Removed %@",self); + [self stop]; - if( _resolveOp ) { - [_resolveOp cancel]; - [_resolveOp release]; - _resolveOp = nil; - } + [_txtQuery stop]; + [_txtQuery release]; + _txtQuery = nil; + + [_addressLookup stop]; +} + + +- (void) priv_finishResolve { + // If I haven't finished my resolve yet, run it synchronously now so I can return a valid value: + if (!_startedResolve ) + [self start]; + if (self.serviceRef) + [self waitForReply]; +} + +- (NSString*) fullName { + if (!_fullName) [self priv_finishResolve]; + return _fullName; +} + +- (NSString*) hostname { + if (!_hostname) [self priv_finishResolve]; + return _hostname; +} + +- (UInt16) port { + if (!_port) [self priv_finishResolve]; + return _port; } @@ -92,19 +128,18 @@ #pragma mark TXT RECORD: -- (NSDictionary*) txtRecord -{ - [_netService startMonitoring]; +- (NSDictionary*) txtRecord { + // If I haven't started my resolve yet, start it now. (_txtRecord will be nil till it finishes.) + if (!_startedResolve) + [self start]; return _txtRecord; } -- (void) txtRecordChanged -{ +- (void) txtRecordChanged { // no-op (this is here for subclassers to override) } -- (NSString*) txtStringForKey: (NSString*)key -{ +- (NSString*) txtStringForKey: (NSString*)key { NSData *value = [self.txtRecord objectForKey: key]; if( ! value ) return nil; @@ -118,95 +153,112 @@ return [str autorelease]; } - -- (void)netService:(NSNetService *)sender didUpdateTXTRecordData:(NSData *)data -{ - NSDictionary *txtDict = [NSNetService dictionaryFromTXTRecordData: data]; - if( ! $equal(txtDict,_txtRecord) ) { - LogTo(Bonjour,@"%@ got TXT record (%u bytes)",self,data.length); +- (void) setTxtData: (NSData*)txtData { + NSDictionary *txtRecord = txtData ?[NSNetService dictionaryFromTXTRecordData: txtData] :nil; + if (!$equal(txtRecord,_txtRecord)) { + LogTo(Bonjour,@"%@ TXT = %@", self,txtRecord); [self willChangeValueForKey: @"txtRecord"]; - setObj(&_txtRecord,txtDict); + setObj(&_txtRecord, txtRecord); [self didChangeValueForKey: @"txtRecord"]; [self txtRecordChanged]; } } -#pragma mark - -#pragma mark ADDRESS RESOLUTION: - - -#define kAddressResolveTimeout 10.0 -#define kAddressExpirationInterval 60.0 -#define kAddressErrorRetryInterval 5.0 - - -- (NSSet*) addresses -{ - if( _addresses && CFAbsoluteTimeGetCurrent() >= _addressesExpireAt ) { - setObj(&_addresses,nil); // eww, toss 'em and get new ones - [self resolve]; - } - return _addresses; +- (void) queryDidUpdate: (MYBonjourQuery*)query { + if (query==_txtQuery) + [self setTxtData: query.recordData]; } -- (MYBonjourResolveOperation*) resolve +#pragma mark - +#pragma mark FULLNAME/HOSTNAME/PORT RESOLUTION: + + +- (void) priv_resolvedFullName: (NSString*)fullName + hostname: (NSString*)hostname + port: (uint16_t)port + txtRecord: (NSData*)txtData { - if( ! _resolveOp ) { - LogTo(Bonjour,@"Resolving %@",self); - _resolveOp = [[MYBonjourResolveOperation alloc] init]; - _resolveOp.service = self; - [_resolveOp start]; - Assert(_netService); - Assert(_netService.delegate=self); - [_netService resolveWithTimeout: kAddressResolveTimeout]; - } - return _resolveOp; + LogTo(Bonjour, @"%@: fullname='%@', hostname=%@, port=%u, txt=%u bytes", + self, fullName, hostname, port, txtData.length); + + // Don't call a setter method to set these properties: the getters are synchronous, so + // I might already be blocked in a call to one of them, in which case creating a KV + // notification could cause trouble... + _fullName = fullName.copy; + _hostname = hostname.copy; + _port = port; + + // TXT getter is async, though, so I can use a setter to announce the data's availability: + [self setTxtData: txtData]; + + // Now that I know my full name, I can start a persistent query to track the TXT: + _txtQuery = [[MYBonjourQuery alloc] initWithBonjourService: self + recordType: kDNSServiceType_TXT]; + _txtQuery.continuous = YES; + [_txtQuery start]; } -- (void) setAddresses: (NSSet*)addresses + +static void resolveCallback(DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *fullname, + const char *hosttarget, + uint16_t port, + uint16_t txtLen, + const unsigned char *txtRecord, + void *context) { - setObj(&_addresses,addresses); + MYBonjourService *service = context; + @try{ + //LogTo(Bonjour, @"resolveCallback for %@ (err=%i)", service,errorCode); + if (errorCode) { + [service setError: errorCode]; + } else { + NSData *txtData = nil; + if (txtRecord) + txtData = [NSData dataWithBytes: txtRecord length: txtLen]; + [service priv_resolvedFullName: [NSString stringWithUTF8String: fullname] + hostname: [NSString stringWithUTF8String: hosttarget] + port: ntohs(port) + txtRecord: txtData]; + } + }catchAndReport(@"MYBonjourResolver query callback"); } -- (void) _finishedResolving: (NSSet*)addresses expireIn: (NSTimeInterval)expirationInterval -{ - _addressesExpireAt = CFAbsoluteTimeGetCurrent() + expirationInterval; - self.addresses = addresses; - _resolveOp.addresses = addresses; - [_resolveOp finish]; - [_resolveOp release]; - _resolveOp = nil; +- (DNSServiceRef) createServiceRef { + _startedResolve = YES; + DNSServiceRef serviceRef = NULL; + self.error = DNSServiceResolve(&serviceRef, 0, + _interfaceIndex, + _name.UTF8String, _type.UTF8String, _domain.UTF8String, + &resolveCallback, self); + return serviceRef; } -- (void)netServiceDidResolveAddress:(NSNetService *)sender -{ - // Convert the raw sockaddrs into IPAddress objects: - NSMutableSet *addresses = [NSMutableSet setWithCapacity: 2]; - for( NSData *rawAddr in _netService.addresses ) { - IPAddress *addr = [[IPAddress alloc] initWithSockAddr: rawAddr.bytes]; - if( addr ) { - [addresses addObject: addr]; - [addr release]; - } +- (MYAddressLookup*) addressLookup { + if (!_addressLookup) { + // Create the lookup the first time this is called: + _addressLookup = [[MYAddressLookup alloc] initWithHostname: self.hostname]; + _addressLookup.port = _port; + _addressLookup.interfaceIndex = _interfaceIndex; } - LogTo(Bonjour,@"Resolved %@: %@",self,addresses); - [self _finishedResolving: addresses expireIn: kAddressExpirationInterval]; + // (Re)start the lookup if it's expired: + if (_addressLookup && _addressLookup.timeToLive <= 0.0) + [_addressLookup start]; + return _addressLookup; } -- (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict -{ - LogTo(Bonjour,@"Error resolving %@ -- %@",self,errorDict); - [self _finishedResolving: [NSArray array] expireIn: kAddressErrorRetryInterval]; -} -- (void)netServiceDidStop:(NSNetService *)sender -{ - LogTo(Bonjour,@"Resolve stopped for %@",self); - [self _finishedResolving: [NSArray array] expireIn: kAddressErrorRetryInterval]; +- (MYBonjourQuery*) queryForRecord: (UInt16)recordType { + MYBonjourQuery *query = [[[MYBonjourQuery alloc] initWithBonjourService: self recordType: recordType] + autorelease]; + return [query start] ?query :nil; } @@ -214,20 +266,6 @@ - -@implementation MYBonjourResolveOperation - -@synthesize service=_service, addresses=_addresses; - -- (void) dealloc -{ - [_addresses release]; - [super dealloc]; -} - -@end - - /* Copyright (c) 2008-2009, Jens Alfke . All rights reserved. diff -r 92581f26073e -r 732576fa8a0d IPAddress.h --- a/IPAddress.h Fri Apr 24 10:10:32 2009 -0700 +++ b/IPAddress.h Mon Apr 27 09:03:56 2009 -0700 @@ -80,6 +80,10 @@ - (id) initWithHostname: (NSString*)hostname port: (UInt16)port; +- (id) initWithHostname: (NSString*)hostname + sockaddr: (const struct sockaddr*)sockaddr + port: (UInt16)port; + @end diff -r 92581f26073e -r 732576fa8a0d IPAddress.m --- a/IPAddress.m Fri Apr 24 10:10:32 2009 -0700 +++ b/IPAddress.m Mon Apr 27 09:03:56 2009 -0700 @@ -233,6 +233,22 @@ return self; } +- (id) initWithHostname: (NSString*)hostname + sockaddr: (const struct sockaddr*)sockaddr + port: (UInt16)port; +{ + if( [hostname length]==0 ) { + [self release]; + return nil; + } + self = [super initWithSockAddr: sockaddr]; + if( self ) { + _hostname = [hostname copy]; + _port = port; + } + return self; +} + - (void)encodeWithCoder:(NSCoder *)coder { @@ -256,6 +272,18 @@ } +- (NSString*) description +{ + NSMutableString *desc = [_hostname mutableCopy]; + NSString *addr = self.ipv4name; + if (addr) + [desc appendFormat: @"(%@)", addr]; + if( _port ) + [desc appendFormat: @":%hu",_port]; + return desc; +} + + - (NSUInteger) hash { return [_hostname hash] ^ _port; diff -r 92581f26073e -r 732576fa8a0d MYNetwork.xcodeproj/project.pbxproj --- a/MYNetwork.xcodeproj/project.pbxproj Fri Apr 24 10:10:32 2009 -0700 +++ b/MYNetwork.xcodeproj/project.pbxproj Mon Apr 27 09:03:56 2009 -0700 @@ -33,6 +33,12 @@ 2780F20C0FA194BD00C0FB83 /* MYDNSService.h in Headers */ = {isa = PBXBuildFile; fileRef = 2780F20A0FA194BD00C0FB83 /* MYDNSService.h */; }; 2780F20D0FA194BD00C0FB83 /* MYDNSService.m in Sources */ = {isa = PBXBuildFile; fileRef = 2780F20B0FA194BD00C0FB83 /* MYDNSService.m */; }; 2780F20E0FA194BD00C0FB83 /* MYDNSService.m in Sources */ = {isa = PBXBuildFile; fileRef = 2780F20B0FA194BD00C0FB83 /* MYDNSService.m */; }; + 2780F4380FA28F4400C0FB83 /* MYBonjourQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 2780F4360FA28F4400C0FB83 /* MYBonjourQuery.h */; }; + 2780F4390FA28F4400C0FB83 /* MYBonjourQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 2780F4370FA28F4400C0FB83 /* MYBonjourQuery.m */; }; + 2780F43A0FA28F4400C0FB83 /* MYBonjourQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 2780F4370FA28F4400C0FB83 /* MYBonjourQuery.m */; }; + 2780F4A10FA2C59000C0FB83 /* MYAddressLookup.h in Headers */ = {isa = PBXBuildFile; fileRef = 2780F49F0FA2C59000C0FB83 /* MYAddressLookup.h */; }; + 2780F4A20FA2C59000C0FB83 /* MYAddressLookup.m in Sources */ = {isa = PBXBuildFile; fileRef = 2780F4A00FA2C59000C0FB83 /* MYAddressLookup.m */; }; + 2780F4A30FA2C59000C0FB83 /* MYAddressLookup.m in Sources */ = {isa = PBXBuildFile; fileRef = 2780F4A00FA2C59000C0FB83 /* MYAddressLookup.m */; }; 278C1A3D0F9F687800954AE1 /* PortMapperTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 278C1A340F9F687800954AE1 /* PortMapperTest.m */; }; 278C1A3E0F9F687800954AE1 /* MYPortMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 278C1A360F9F687800954AE1 /* MYPortMapper.m */; }; 278C1BA60F9F92EA00954AE1 /* MYBonjourBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = 278C1B9F0F9F92EA00954AE1 /* MYBonjourBrowser.m */; }; @@ -172,8 +178,12 @@ 277904280DE91C7900C6D295 /* BLIP Echo Client-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "BLIP Echo Client-Info.plist"; sourceTree = ""; }; 2779048A0DE9204300C6D295 /* BLIPEchoClient.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BLIPEchoClient.xib; sourceTree = ""; }; 2779052D0DE9E5BC00C6D295 /* BLIPEchoServer */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = BLIPEchoServer; sourceTree = BUILT_PRODUCTS_DIR; }; - 2780F20A0FA194BD00C0FB83 /* MYDNSService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYDNSService.h; sourceTree = ""; }; - 2780F20B0FA194BD00C0FB83 /* MYDNSService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYDNSService.m; sourceTree = ""; }; + 2780F20A0FA194BD00C0FB83 /* MYDNSService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYDNSService.h; path = PortMapper/MYDNSService.h; sourceTree = ""; }; + 2780F20B0FA194BD00C0FB83 /* MYDNSService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYDNSService.m; path = PortMapper/MYDNSService.m; sourceTree = ""; }; + 2780F4360FA28F4400C0FB83 /* MYBonjourQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYBonjourQuery.h; sourceTree = ""; }; + 2780F4370FA28F4400C0FB83 /* MYBonjourQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYBonjourQuery.m; sourceTree = ""; }; + 2780F49F0FA2C59000C0FB83 /* MYAddressLookup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYAddressLookup.h; sourceTree = ""; }; + 2780F4A00FA2C59000C0FB83 /* MYAddressLookup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYAddressLookup.m; sourceTree = ""; }; 278C1A340F9F687800954AE1 /* PortMapperTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PortMapperTest.m; sourceTree = ""; }; 278C1A350F9F687800954AE1 /* MYPortMapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYPortMapper.h; sourceTree = ""; }; 278C1A360F9F687800954AE1 /* MYPortMapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYPortMapper.m; sourceTree = ""; }; @@ -283,12 +293,11 @@ isa = PBXGroup; children = ( 279DDCCB0F9E381500D75D91 /* MYNetwork.h */, - 270461010DE49030003D9D3F /* IPAddress.h */, - 270461020DE49030003D9D3F /* IPAddress.m */, + 2780F5710FA2E38100C0FB83 /* Addressing */, + 278C1A320F9F687800954AE1 /* PortMapper */, + 278C1B9D0F9F92D600954AE1 /* Bonjour */, 270461070DE49030003D9D3F /* TCP */, - 278C1A320F9F687800954AE1 /* PortMapper */, 270460F10DE49030003D9D3F /* BLIP */, - 278C1B9D0F9F92D600954AE1 /* Bonjour */, ); name = MYNetwork; sourceTree = ""; @@ -356,8 +365,7 @@ 27E0DBEC0DF3450F00E7F648 /* GoogleToolboxSubset */, ); name = MYUtilities; - path = ../MYUtilities; - sourceTree = ""; + sourceTree = MYUtilities; }; 277903E70DE8F05F00C6D295 /* Demo */ = { isa = PBXGroup; @@ -374,11 +382,20 @@ path = BLIP/Demo; sourceTree = ""; }; + 2780F5710FA2E38100C0FB83 /* Addressing */ = { + isa = PBXGroup; + children = ( + 270461010DE49030003D9D3F /* IPAddress.h */, + 270461020DE49030003D9D3F /* IPAddress.m */, + 2780F20A0FA194BD00C0FB83 /* MYDNSService.h */, + 2780F20B0FA194BD00C0FB83 /* MYDNSService.m */, + ); + name = Addressing; + sourceTree = ""; + }; 278C1A320F9F687800954AE1 /* PortMapper */ = { isa = PBXGroup; children = ( - 2780F20A0FA194BD00C0FB83 /* MYDNSService.h */, - 2780F20B0FA194BD00C0FB83 /* MYDNSService.m */, 278C1A350F9F687800954AE1 /* MYPortMapper.h */, 278C1A360F9F687800954AE1 /* MYPortMapper.m */, 278C1A340F9F687800954AE1 /* PortMapperTest.m */, @@ -393,6 +410,10 @@ 278C1B9F0F9F92EA00954AE1 /* MYBonjourBrowser.m */, 278C1BA00F9F92EA00954AE1 /* MYBonjourService.h */, 278C1BA10F9F92EA00954AE1 /* MYBonjourService.m */, + 2780F4360FA28F4400C0FB83 /* MYBonjourQuery.h */, + 2780F4370FA28F4400C0FB83 /* MYBonjourQuery.m */, + 2780F49F0FA2C59000C0FB83 /* MYAddressLookup.h */, + 2780F4A00FA2C59000C0FB83 /* MYAddressLookup.m */, ); path = Bonjour; sourceTree = ""; @@ -415,6 +436,8 @@ buildActionMask = 2147483647; files = ( 2780F20C0FA194BD00C0FB83 /* MYDNSService.h in Headers */, + 2780F4380FA28F4400C0FB83 /* MYBonjourQuery.h in Headers */, + 2780F4A10FA2C59000C0FB83 /* MYAddressLookup.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -570,6 +593,8 @@ 279E8FB70F9FDD2600608D8D /* MYBonjourService.m in Sources */, 279E8FB80F9FDD2600608D8D /* ConcurrentOperation.m in Sources */, 2780F20D0FA194BD00C0FB83 /* MYDNSService.m in Sources */, + 2780F4390FA28F4400C0FB83 /* MYBonjourQuery.m in Sources */, + 2780F4A20FA2C59000C0FB83 /* MYAddressLookup.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -604,6 +629,8 @@ 278C1BA70F9F92EA00954AE1 /* MYBonjourService.m in Sources */, 278C1BB90F9F975700954AE1 /* ConcurrentOperation.m in Sources */, 2780F20E0FA194BD00C0FB83 /* MYDNSService.m in Sources */, + 2780F43A0FA28F4400C0FB83 /* MYBonjourQuery.m in Sources */, + 2780F4A30FA2C59000C0FB83 /* MYAddressLookup.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff -r 92581f26073e -r 732576fa8a0d PortMapper/MYDNSService.h --- a/PortMapper/MYDNSService.h Fri Apr 24 10:10:32 2009 -0700 +++ b/PortMapper/MYDNSService.h Mon Apr 27 09:03:56 2009 -0700 @@ -18,23 +18,27 @@ CFSocketRef _socket; CFRunLoopSourceRef _socketSource; SInt32 _error; + BOOL _continuous; } +/** If NO (the default), the service will stop after it gets a result. + If YES, it will continue to run until stopped. */ +@property BOOL continuous; + /** Starts the service. Returns immediately; you can find out when the service actually starts (or fails to) by observing the isOpen and error properties. It's very unlikely that this call itself will fail (return NO). If it does, it probably means that the mDNSResponder process isn't working. */ -- (BOOL) open; +- (BOOL) start; -- (void) close; +/** Stops the service. */ +- (void) stop; -@property (readonly) struct _DNSServiceRef_t* serviceRef; - /** The error status, a DNSServiceErrorType enum; nonzero if something went wrong. This property is KV observable. */ -@property SInt32 error; +@property int32_t error; // PROTECTED: @@ -43,6 +47,15 @@ If an error occurs, the method should set self.error and return NULL.*/ - (struct _DNSServiceRef_t*) createServiceRef; -- (void) stopService; +@property (readonly) struct _DNSServiceRef_t* serviceRef; + +/** Same as -stop, but does not clear the error property. + (The stop method actually calls this first.) */ +- (void) cancel; + +/** Block until a message is received from the daemon. + This will cause the service's callback (defined by the subclass) to be invoked. + @return YES if a message is received, NO on error (or if the service isn't started) */ +- (BOOL) waitForReply; @end diff -r 92581f26073e -r 732576fa8a0d PortMapper/MYDNSService.m --- a/PortMapper/MYDNSService.m Fri Apr 24 10:10:32 2009 -0700 +++ b/PortMapper/MYDNSService.m Mon Apr 27 09:03:56 2009 -0700 @@ -27,20 +27,32 @@ - (void) dealloc { + Log(@"DEALLOC %@ %p", self.class,self); if( _serviceRef ) - [self stopService]; + [self cancel]; [super dealloc]; } - (void) finalize { if( _serviceRef ) - [self stopService]; + [self cancel]; [super finalize]; } -@synthesize serviceRef=_serviceRef, error=_error; +- (DNSServiceErrorType) error { + return _error; +} + +- (void) setError: (DNSServiceErrorType)error { + if (error) + Warn(@"%@ error := %i", self,error); + _error = error; +} + + +@synthesize continuous=_continuous, serviceRef=_serviceRef; - (DNSServiceRef) createServiceRef { @@ -48,11 +60,17 @@ } -- (BOOL) open +- (BOOL) start { if (_serviceRef) - return YES; + return YES; // already started + + if (_error) + self.error = 0; + + // Ask the subclass to create a DNSServiceRef: _serviceRef = [self createServiceRef]; + if (_serviceRef) { // Wrap a CFSocket around the service's socket: CFSocketContext ctxt = { 0, self, CFRetain, CFRelease, NULL }; @@ -66,7 +84,7 @@ _socketSource = CFSocketCreateRunLoopSource(NULL, _socket, 0); if( _socketSource ) { CFRunLoopAddSource(CFRunLoopGetCurrent(), _socketSource, kCFRunLoopCommonModes); - LogTo(DNS,@"Opening %@",self); + LogTo(DNS,@"Opening %@ -- service=%p",self,_serviceRef); return YES; // success } } @@ -74,13 +92,14 @@ if (!_error) self.error = kDNSServiceErr_Unknown; LogTo(DNS,@"Failed to open %@ -- err=%i",self,_error); - [self stopService]; + [self cancel]; return NO; } -- (void) stopService +- (void) cancel { + [self retain]; // Prevents _socket's dealloc from releasing & deallocing me! if( _socketSource ) { CFRunLoopSourceInvalidate(_socketSource); CFRelease(_socketSource); @@ -96,17 +115,45 @@ DNSServiceRefDeallocate(_serviceRef); _serviceRef = NULL; } + [self release]; } -- (void) close +- (void) stop { - [self stopService]; + [self cancel]; if (_error) self.error = 0; } +- (BOOL) priv_processResult +{ + Assert(_serviceRef); + DNSServiceErrorType err = DNSServiceProcessResult(_serviceRef); + if (err) { + // An error here means the socket has failed and should be closed. + self.error = err; + [self cancel]; + return NO; + } else { + if (!_continuous) + [self cancel]; + return YES; + } +} + +- (BOOL) waitForReply +{ + if (!_serviceRef) + return NO; + LogTo(DNS,@"Waiting for %@ ...", self); + BOOL ok = [self priv_processResult]; + LogTo(DNS,@" ...done waiting"); + return ok; +} + + /** CFSocket callback, informing us that _socket has data available, which means that the DNS service has an incoming result to be processed. This will end up invoking the service's specific callback. */ @@ -114,14 +161,11 @@ CFSocketCallBackType type, CFDataRef address, const void *data, void *clientCallBackInfo) { - MYDNSService *serviceObj = (MYDNSService*)clientCallBackInfo; - DNSServiceRef service = serviceObj.serviceRef; - DNSServiceErrorType err = DNSServiceProcessResult(service); - if( err ) { - // An error here means the socket has failed and should be closed. - serviceObj.error = err; - [serviceObj stopService]; - } + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + @try{ + [(MYDNSService*)clientCallBackInfo priv_processResult]; + }catchAndReport(@"PortMapper serviceCallback"); + [pool drain]; } diff -r 92581f26073e -r 732576fa8a0d PortMapper/MYPortMapper.m --- a/PortMapper/MYPortMapper.m Fri Apr 24 10:10:32 2009 -0700 +++ b/PortMapper/MYPortMapper.m Mon Apr 27 09:03:56 2009 -0700 @@ -33,6 +33,7 @@ if (self != nil) { _localPort = localPort; _mapTCP = YES; + self.continuous = YES; [self priv_updateLocalAddress]; } return self; @@ -87,11 +88,9 @@ publicAddress: (UInt32)rawPublicAddress publicPort: (UInt16)publicPort { - LogTo(PortMapper,@"Callback got err %i, addr %08X:%hu", - errorCode, rawPublicAddress, publicPort); if( errorCode==kDNSServiceErr_NoError ) { if( rawPublicAddress==0 || (publicPort==0 && (_mapTCP || _mapUDP)) ) { - LogTo(PortMapper,@"(Callback reported no mapping available)"); + LogTo(PortMapper,@"%@: No port-map available", self); errorCode = kDNSServiceErr_NATPortMappingUnsupported; } } @@ -104,8 +103,8 @@ self.publicAddress = publicAddress; if( ! errorCode ) { - LogTo(PortMapper,@"Callback got %08X:%hu -> %@ (mapped=%i)", - rawPublicAddress,publicPort, self.publicAddress, self.isMapped); + LogTo(PortMapper,@"%@: Public addr is %@ (mapped=%i)", + self, self.publicAddress, self.isMapped); } [[NSNotificationCenter defaultCenter] postNotificationName: MYPortMapperChangedNotification object: self]; @@ -128,13 +127,11 @@ void *context ) { - NSAutoreleasePool *pool = [NSAutoreleasePool new]; @try{ [(MYPortMapper*)context priv_portMapStatus: errorCode publicAddress: publicAddress publicPort: ntohs(publicPort)]; // port #s in network byte order! }catchAndReport(@"PortMapper"); - [pool drain]; } @@ -157,24 +154,12 @@ } -- (void) stopService -{ - [super stopService]; - if (_publicAddress) - self.publicAddress = nil; -} - - - (BOOL) waitTillOpened { if( ! self.serviceRef ) - if( ! [self open] ) + if( ! [self start] ) return NO; - // Run the runloop until there's either an error or a result: - while( self.error==0 && _publicAddress==nil ) - if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode - beforeDate: [NSDate distantFuture]] ) - break; + [self waitForReply]; return (self.error==0); } @@ -183,9 +168,10 @@ { IPAddress *addr = nil; MYPortMapper *mapper = [[self alloc] initWithNullMapping]; + mapper.continuous = NO; if( [mapper waitTillOpened] ) addr = [mapper.publicAddress retain]; - [mapper close]; + [mapper stop]; [mapper release]; return [addr autorelease]; } diff -r 92581f26073e -r 732576fa8a0d PortMapper/PortMapperTest.m --- a/PortMapper/PortMapperTest.m Fri Apr 24 10:10:32 2009 -0700 +++ b/PortMapper/PortMapperTest.m Mon Apr 27 09:03:56 2009 -0700 @@ -42,7 +42,7 @@ _mapper.desiredPublicPort = 22222; // Now open the mapping (asynchronously): - if( [_mapper open] ) { + if( [_mapper start] ) { Log(@"Opening port mapping..."); // Now listen for notifications to find out when the mapping opens, fails, or changes: [[NSNotificationCenter defaultCenter] addObserver: self @@ -78,7 +78,7 @@ - (void) dealloc { - [_mapper close]; + [_mapper stop]; [_mapper release]; [super dealloc]; } @@ -90,7 +90,9 @@ TestCase(MYPortMapper) { + EnableLogTo(DNS,YES); EnableLogTo(PortMapper,YES); + // Here's how to simply obtain your local and public address(es): IPAddress *addr = [IPAddress localAddress]; Log(@"** Local address is %@%@ ...getting public addr...",