# HG changeset patch # User Jens Alfke # Date 1241036971 25200 # Node ID 1d6924779df74b3d9a59f0dbc47cf4fec3dffb09 # Parent 59689fbdcf772dbf23f9e1c5cd7cc596af3e8703# Parent 096cf03b65d410a9fbde149f21a64c63ff4ae242 More work on Bonjour classes. They now support registering services. diff -r 59689fbdcf77 -r 1d6924779df7 Bonjour/MYAddressLookup.h --- a/Bonjour/MYAddressLookup.h Tue Apr 28 10:36:28 2009 -0700 +++ b/Bonjour/MYAddressLookup.h Wed Apr 29 13:29:31 2009 -0700 @@ -19,14 +19,17 @@ CFAbsoluteTime _expires; } -/** Initializes the lookup with a DNS hostname. */ +/** Initializes the lookup with a DNS hostname. + (If you've got a Bonjour service already, as a MYBonjourService object, it's more convenient + to access its addressLookup property instead of creating your own instance.) */ - (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. */ +/** The index of the network interface to use, or zero (the default) for any interface. + You usually don't need to set this. */ @property UInt16 interfaceIndex; /** The resulting address(es) of the host, as HostAddress objects. */ diff -r 59689fbdcf77 -r 1d6924779df7 Bonjour/MYAddressLookup.m --- a/Bonjour/MYAddressLookup.m Tue Apr 28 10:36:28 2009 -0700 +++ b/Bonjour/MYAddressLookup.m Wed Apr 29 13:29:31 2009 -0700 @@ -60,17 +60,17 @@ sockaddr: sockaddr port: _port]; if (address) { - if (flags & kDNSServiceFlagsAdd) + if (flags & kDNSServiceFlagsAdd) { + LogTo(DNS,@"%@ got %@ [TTL = %u]", self, address, ttl); [_addresses addObject: address]; - else + } else { + LogTo(DNS,@"%@ lost %@ [TTL = %u]", self, address, ttl); [_addresses removeObject: address]; + } [address release]; } _expires = CFAbsoluteTimeGetCurrent() + ttl; - - if (!(flags & kDNSServiceFlagsMoreComing)) - LogTo(DNS,@"Got addresses of %@: %@ [TTL = %u]", self, _addresses, ttl); } @@ -91,17 +91,17 @@ else [lookup priv_resolvedAddress: address ttl: ttl flags: flags]; }catchAndReport(@"MYDNSLookup query callback"); + [lookup gotResponse: errorCode]; } -- (DNSServiceRef) createServiceRef { +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr { [_addresses removeAllObjects]; - DNSServiceRef serviceRef = NULL; - self.error = DNSServiceGetAddrInfo(&serviceRef, 0, - _interfaceIndex, 0, - _hostname.UTF8String, - &lookupCallback, self); - return serviceRef; + return DNSServiceGetAddrInfo(sdRefPtr, + kDNSServiceFlagsShareConnection, + _interfaceIndex, 0, + _hostname.UTF8String, + &lookupCallback, self); } @@ -120,3 +120,26 @@ [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 10]]; [lookup release]; } + + +/* + Copyright (c) 2009, Jens Alfke . All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 59689fbdcf77 -r 1d6924779df7 Bonjour/MYBonjourBrowser.h --- a/Bonjour/MYBonjourBrowser.h Tue Apr 28 10:36:28 2009 -0700 +++ b/Bonjour/MYBonjourBrowser.h Wed Apr 29 13:29:31 2009 -0700 @@ -7,6 +7,7 @@ // #import "MYDNSService.h" +@class MYBonjourRegistration; /** Searches for Bonjour services of a specific type. */ @@ -17,6 +18,8 @@ BOOL _browsing; Class _serviceClass; NSMutableSet *_services, *_addServices, *_rmvServices; + BOOL _pendingUpdate; + MYBonjourRegistration *_myRegistration; } /** Initializes a new BonjourBrowser. @@ -37,4 +40,10 @@ to a subclass of that. */ @property Class serviceClass; +/** My own registration for this service type. + This object is created on demand and won't be started up until you tell it to. + Before starting it, you'll need to set its port, and optionally its name. + Your own registration will _not_ be visible in the set of services.*/ +@property (readonly) MYBonjourRegistration *myRegistration; + @end diff -r 59689fbdcf77 -r 1d6924779df7 Bonjour/MYBonjourBrowser.m --- a/Bonjour/MYBonjourBrowser.m Tue Apr 28 10:36:28 2009 -0700 +++ b/Bonjour/MYBonjourBrowser.m Wed Apr 29 13:29:31 2009 -0700 @@ -8,6 +8,7 @@ #import "MYBonjourBrowser.h" #import "MYBonjourService.h" +#import "MYBonjourRegistration.h" #import "ExceptionUtils.h" #import "Test.h" #import "Logging.h" @@ -51,6 +52,8 @@ - (void) dealloc { LogTo(Bonjour,@"DEALLOC BonjourBrowser"); + [_myRegistration cancel]; + [_myRegistration release]; [_serviceType release]; [_services release]; [_addServices release]; @@ -68,12 +71,12 @@ } -- (DNSServiceRef) createServiceRef { - DNSServiceRef serviceRef = NULL; - self.error = DNSServiceBrowse(&serviceRef, 0, 0, - _serviceType.UTF8String, NULL, - &browseCallback, self); - return serviceRef; +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr { + return DNSServiceBrowse(sdRefPtr, + kDNSServiceFlagsShareConnection, + 0, + _serviceType.UTF8String, NULL, + &browseCallback, self); } @@ -93,8 +96,15 @@ type: regtype domain: domain interface: interfaceIndex]; + if ([_myRegistration isSameAsService: service]) { + // This is an echo of my own registration, so ignore it + LogTo(Bonjour,@"%@ ignoring echo %@", self,service); + [service release]; + return; + } MYBonjourService *existingService = [_services member: service]; if( existingService ) { + // Use existing service object instead of creating a new one [service release]; service = [existingService retain]; } @@ -114,14 +124,17 @@ [addTo addObject: service]; [service release]; - // After a round of updates is done, do the update: - if( ! (flags & kDNSServiceFlagsMoreComing) ) - [self _updateServiceList]; + // Schedule a (single) call to _updateServiceList: + if (!_pendingUpdate) { + [self performSelector: @selector(_updateServiceList) withObject: nil afterDelay: 0]; + _pendingUpdate = YES; + } } - (void) _updateServiceList { + _pendingUpdate = NO; if( _rmvServices.count ) { [self willChangeValueForKey: @"services" withSetMutation: NSKeyValueMinusSetMutation @@ -156,17 +169,30 @@ const char *replyDomain, void *context) { + MYBonjourBrowser *browser = context; @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]; + if (!errorCode) + [browser priv_gotServiceName: [NSString stringWithUTF8String: serviceName] + type: [NSString stringWithUTF8String: regtype] + domain: [NSString stringWithUTF8String: replyDomain] + interface: interfaceIndex + flags: flags]; }catchAndReport(@"Bonjour"); + [browser gotResponse: errorCode]; +} + + +- (void) cancel { + [_myRegistration stop]; + [super cancel]; +} + + +- (MYBonjourRegistration *) myRegistration { + if (!_myRegistration) + _myRegistration = [[MYBonjourRegistration alloc] initWithServiceType: _serviceType port: 0]; + return _myRegistration; } @@ -198,6 +224,10 @@ [_browser addObserver: self forKeyPath: @"services" options: NSKeyValueObservingOptionNew context: NULL]; [_browser addObserver: self forKeyPath: @"browsing" options: NSKeyValueObservingOptionNew context: NULL]; [_browser start]; + + MYBonjourRegistration *myReg = _browser.myRegistration; + myReg.port = 12346; + Assert([myReg start]); } return self; } diff -r 59689fbdcf77 -r 1d6924779df7 Bonjour/MYBonjourQuery.m --- a/Bonjour/MYBonjourQuery.m Tue Apr 28 10:36:28 2009 -0700 +++ b/Bonjour/MYBonjourQuery.m Wed Apr 29 13:29:31 2009 -0700 @@ -105,31 +105,55 @@ uint32_t ttl, void *context) { + MYBonjourQuery *query = 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]; + if (!errorCode) + [query priv_gotRecordBytes: rdata + length: rdlen + type: rrtype + ttl: ttl + flags: flags]; }catchAndReport(@"MYBonjourResolver query callback"); + [query gotResponse: errorCode]; } -- (DNSServiceRef) createServiceRef { - DNSServiceRef serviceRef = NULL; +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr { const char *fullName = _bonjourService.fullName.UTF8String; if (fullName) - self.error = DNSServiceQueryRecord(&serviceRef, 0, - _bonjourService.interfaceIndex, - fullName, - _recordType, kDNSServiceClass_IN, - &queryCallback, self); - return serviceRef; + return DNSServiceQueryRecord(sdRefPtr, + kDNSServiceFlagsShareConnection, + _bonjourService.interfaceIndex, + fullName, + _recordType, kDNSServiceClass_IN, + &queryCallback, self); + else + return kDNSServiceErr_NoSuchName; } @end + + +/* + Copyright (c) 2009, Jens Alfke . All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 59689fbdcf77 -r 1d6924779df7 Bonjour/MYBonjourRegistration.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Bonjour/MYBonjourRegistration.h Wed Apr 29 13:29:31 2009 -0700 @@ -0,0 +1,81 @@ +// +// MYBonjourRegistration.h +// MYNetwork +// +// Created by Jens Alfke on 4/27/09. +// Copyright 2009 Jens Alfke. All rights reserved. +// + +#import "MYDNSService.h" +@class MYBonjourService; + + +/** Registers a local network service with Bonjour, so it can be browsed by other computers. */ +@interface MYBonjourRegistration : MYDNSService +{ + NSString *_name, *_type, *_domain; + UInt16 _port; + BOOL _autoRename; + BOOL _registered; + NSMutableDictionary *_txtRecord; +} + +/** Initializes a new registration. + If you're also browsing for the same service type, you should instead get an instance of this via + the MYBonjourBrowser's 'myRegistration' property -- that way the browser knows about the + registration and won't echo it back to you. + Either way, don't forget to call -start! */ +- (id) initWithServiceType: (NSString*)serviceType port: (UInt16)port; + +/** The name to register this service under. + This is often left nil, in which case the user's chosen "Computer Name" (from the Sharing system + pref pane) will be used. + This can only be set before calling -start! */ +@property (copy) NSString *name; + +/** The registration's service type. */ +@property (readonly) NSString *type; + +/** The registration's IP port number. + You'll need to set this if you got this object from MYBonjourBrowser's 'myRegistration' property, + as that object doesn't have a pre-set port number yet. + This can only be set before calling -start! */ +@property UInt16 port; + +/** The registration's full name -- the name, type and domain concatenated together. */ +@property (readonly) NSString *fullName; + +/** Is the registration currently active? */ +@property (readonly) BOOL registered; + +/** The service's metadata dictionary, stored in its DNS TXT record */ +@property (copy) NSDictionary *txtRecord; + +/** Convenience to store a string value in a single TXT record key. */ +- (void) setString: (NSString*)value forTxtKey: (NSString*)key; + + +/** @name Expert + * Advanced methods you likely won't need + */ +//@{ + +/** The registration's domain name. + This is almost always left nil, in which case the default registration domain is used + (usually ".local".) + This can only be set before calling -start! */ +@property (copy) NSString *domain; + +/** Determines what to do if there's a name conflict with an already-registered service on the + network. + If set to YES (the default), a number will be appended to the name to make it unique. + If set to NO, the registration will fail, and you can choose a different name and try again. + This can only be set before calling -start! */ +@property BOOL autoRename; + +/** Is this browsed service an echo of this local registration? (Compares fullNames.) */ +- (BOOL) isSameAsService: (MYBonjourService*)service; + +//@} + +@end diff -r 59689fbdcf77 -r 1d6924779df7 Bonjour/MYBonjourRegistration.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Bonjour/MYBonjourRegistration.m Wed Apr 29 13:29:31 2009 -0700 @@ -0,0 +1,289 @@ +// +// MYBonjourRegistration.m +// MYNetwork +// +// Created by Jens Alfke on 4/27/09. +// Copyright 2009 Jens Alfke. All rights reserved. +// + +#import "MYBonjourRegistration.h" +#import "MYBonjourService.h" +#import "ExceptionUtils.h" +#import "Test.h" +#import "Logging.h" +#import + + +#define kTXTTTL 60 // TTL in seconds for TXT records I register + + +@interface MYBonjourRegistration () +@property BOOL registered; +@end + + +@implementation MYBonjourRegistration + + +static NSMutableDictionary *sAllRegistrations; + + ++ (void) priv_addRegistration: (MYBonjourRegistration*)reg { + if (!sAllRegistrations) + sAllRegistrations = [[NSMutableDictionary alloc] init]; + [sAllRegistrations setObject: reg forKey: reg.fullName]; +} + ++ (void) priv_removeRegistration: (MYBonjourRegistration*)reg { + [sAllRegistrations removeObjectForKey: reg.fullName]; +} + ++ (MYBonjourRegistration*) registrationWithFullName: (NSString*)fullName { + return [sAllRegistrations objectForKey: fullName]; +} + + +- (id) initWithServiceType: (NSString*)serviceType port: (UInt16)port +{ + self = [super init]; + if (self != nil) { + self.continuous = YES; + self.usePrivateConnection = YES; // DNSServiceUpdateRecord doesn't work with shared conn :( + _type = [serviceType copy]; + _port = port; + _autoRename = YES; + } + return self; +} + +- (void) dealloc { + [_name release]; + [_type release]; + [_domain release]; + [super dealloc]; +} + + +@synthesize name=_name, type=_type, domain=_domain, port=_port, autoRename=_autoRename; +@synthesize registered=_registered; + + +- (NSString*) fullName { + return [[self class] fullNameOfService: _name ofType: _type inDomain: _domain]; +} + + +- (BOOL) isSameAsService: (MYBonjourService*)service { + return _name && _domain && [self.fullName isEqualToString: service.fullName]; +} + + +- (NSString*) description +{ + return $sprintf(@"%@['%@'.%@%@]", self.class,_name,_type,_domain); +} + + +- (void) priv_registeredAsName: (NSString*)name + type: (NSString*)regtype + domain: (NSString*)domain +{ + if (!$equal(name,_name)) + self.name = name; + if (!$equal(domain,_domain)) + self.domain = domain; + LogTo(Bonjour,@"Registered %@", self); + self.registered = YES; + [[self class] priv_addRegistration: self]; +} + + +static void regCallback(DNSServiceRef sdRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + const char *name, + const char *regtype, + const char *domain, + void *context) +{ + MYBonjourRegistration *reg = context; + @try{ + if (!errorCode) + [reg priv_registeredAsName: [NSString stringWithUTF8String: name] + type: [NSString stringWithUTF8String: regtype] + domain: [NSString stringWithUTF8String: domain]]; + }catchAndReport(@"MYBonjourRegistration callback"); + [reg gotResponse: errorCode]; +} + + +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr { + DNSServiceFlags flags = 0; + if (!_autoRename) + flags |= kDNSServiceFlagsNoAutoRename; + NSData *txtData = nil; + if (_txtRecord) + txtData = [NSNetService dataFromTXTRecordDictionary: _txtRecord]; + return DNSServiceRegister(sdRefPtr, + flags, + 0, + _name.UTF8String, // _name is likely to be nil + _type.UTF8String, + _domain.UTF8String, // _domain is most likely nil + NULL, + htons(_port), + txtData.length, + txtData.bytes, + ®Callback, + self); +} + + +- (void) cancel { + [super cancel]; + if (_registered) { + [[self class] priv_removeRegistration: self]; + self.registered = NO; + } +} + + +- (void) updateTxtRecord { + [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil]; + if (self.serviceRef) { + NSData *data = [NSNetService dataFromTXTRecordDictionary: _txtRecord]; + Assert(data!=nil, @"Can't convert dictionary to TXT record"); + DNSServiceErrorType err = DNSServiceUpdateRecord(self.serviceRef, + NULL, + 0, + data.length, + data.bytes, + kTXTTTL); + if (err) + Warn(@"%@ failed to update TXT (err=%i)", self,err); + else + LogTo(Bonjour,@"%@ updated TXT to %@", self,data); + } +} + + +- (NSDictionary*) txtRecord { + return _txtRecord; +} + +- (void) setTxtRecord: (NSDictionary*)txtDict { + if (!$equal(_txtRecord,txtDict)) { + if (txtDict) + [_txtRecord setDictionary: txtDict]; + else + setObj(&_txtRecord,nil); + [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil]; + [self performSelector: @selector(updateTxtRecord) withObject: nil afterDelay: 0.1]; + } +} + +- (void) setString: (NSString*)value forTxtKey: (NSString*)key +{ + NSData *data = [value dataUsingEncoding: NSUTF8StringEncoding]; + if (!$equal(data, [_txtRecord objectForKey: key])) { + if (data) { + if (!_txtRecord) _txtRecord = [[NSMutableDictionary alloc] init]; + [_txtRecord setObject: data forKey: key]; + } else + [_txtRecord removeObjectForKey: key]; + [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil]; + [self performSelector: @selector(updateTxtRecord) withObject: nil afterDelay: 0.1]; + } +} + +@end + + + + +#pragma mark - +#pragma mark TESTING: + +#if DEBUG + +#import "MYBonjourQuery.h" +#import "MYAddressLookup.h" + +@interface BonjourRegTester : NSObject +{ + MYBonjourRegistration *_reg; + BOOL _updating; +} +@end + +@implementation BonjourRegTester + +- (void) updateTXT { + NSDictionary *txt = $dict({@"time", $sprintf(@"%.3lf", CFAbsoluteTimeGetCurrent())}); + _reg.txtRecord = txt; + [self performSelector: @selector(updateTXT) withObject: nil afterDelay: 3.0]; +} + +- (id) init +{ + self = [super init]; + if (self != nil) { + _reg = [[MYBonjourRegistration alloc] initWithServiceType: @"_foo._tcp" port: 12345]; + [_reg addObserver: self forKeyPath: @"registered" options: NSKeyValueObservingOptionNew context: NULL]; + [_reg addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: NULL]; + + [self updateTXT]; + [_reg start]; + } + return self; +} + +- (void) dealloc +{ + [_reg stop]; + [_reg removeObserver: self forKeyPath: @"registered"]; + [_reg removeObserver: self forKeyPath: @"name"]; + [_reg release]; + [super dealloc]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change); +} + +@end + +TestCase(BonjourReg) { + EnableLogTo(Bonjour,YES); + EnableLogTo(DNS,YES); + [NSRunLoop currentRunLoop]; // create runloop + BonjourRegTester *tester = [[BonjourRegTester alloc] init]; + [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 15]]; + [tester release]; +} + +#endif + + +/* + Copyright (c) 2008-2009, Jens Alfke . All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 59689fbdcf77 -r 1d6924779df7 Bonjour/MYBonjourService.h --- a/Bonjour/MYBonjourService.h Tue Apr 28 10:36:28 2009 -0700 +++ b/Bonjour/MYBonjourService.h Wed Apr 29 13:29:31 2009 -0700 @@ -33,13 +33,34 @@ /** The service's domain. */ @property (readonly) NSString *domain; +/** The service's full name -- the name, type and domain concatenated together. */ +@property (readonly,copy) NSString* fullName; + +/** The index of the network interface on which this service was found. */ +@property (readonly) uint32_t interfaceIndex; + + +/** @name Addressing + * Getting the IP address of the service + */ +//@{ + +/** The hostname of the machine providing this service. */ @property (readonly, copy) NSString *hostname; +/** The IP port number of this service on its host. */ @property (readonly) UInt16 port; -@property (readonly) uint32_t interfaceIndex; +/** Returns a MYDNSLookup object that resolves the raw IP address(es) of this service. + Subsequent calls to this method will always return the same object. */ +- (MYAddressLookup*) addressLookup; -@property (readonly,copy) NSString* fullName; +//@} + + +/** @name TXT and other DNS records + */ +//@{ /** The service's metadata dictionary, from its DNS TXT record */ @property (readonly,copy) NSDictionary *txtRecord; @@ -47,42 +68,41 @@ /** A convenience to access a single property from the TXT record. */ - (NSString*) txtStringForKey: (NSString*)key; -/** 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 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: -// (for subclasses to override, but not call): +/** @name Protected + * Advanced methods that may be overridden by subclasses, but should not be called directly. + */ +//@{ + +/** Designated initializer. You probably don't want to create MYBonjourService instances yourself, + but if you subclass you might need to override this initializer. */ - (id) initWithName: (NSString*)serviceName type: (NSString*)type domain: (NSString*)domain - interface: (uint32_t)interfaceIndex; + interface: (UInt32)interfaceIndex; +/** Called when this service is officially added to its browser's service set. + You can override this, but be sure to call the superclass method. */ - (void) added; + +/** Called when this service is officially removed to its browser's service set. + You can override this, but be sure to call the superclass method. */ - (void) removed; + +/** Called when this service's TXT record changes. + You can override this, but be sure to call the superclass method. */ - (void) txtRecordChanged; -// Internal: - +/** Called when a query started by this service updates. + You can override this, but be sure to call the superclass method. */ - (void) queryDidUpdate: (MYBonjourQuery*)query; -@end - - - -@interface MYBonjourResolveOperation : ConcurrentOperation -{ - MYBonjourService *_service; - NSSet *_addresses; -} - -@property (readonly) MYBonjourService *service; -@property (readonly,retain) NSSet *addresses; +//@} @end diff -r 59689fbdcf77 -r 1d6924779df7 Bonjour/MYBonjourService.m --- a/Bonjour/MYBonjourService.m Tue Apr 28 10:36:28 2009 -0700 +++ b/Bonjour/MYBonjourService.m Wed Apr 29 13:29:31 2009 -0700 @@ -30,36 +30,41 @@ - (id) initWithName: (NSString*)serviceName type: (NSString*)type domain: (NSString*)domain - interface: (uint32)interfaceIndex + interface: (UInt32)interfaceIndex { + Assert(serviceName); + Assert(type); + Assert(domain); self = [super init]; if (self != nil) { _name = [serviceName copy]; _type = [type copy]; _domain = [domain copy]; + _fullName = [[[self class] fullNameOfService: _name ofType: _type inDomain: _domain] retain]; _interfaceIndex = interfaceIndex; } return self; } - (void) dealloc { - [_name release]; - [_type release]; - [_domain release]; - [_hostname release]; [_txtQuery stop]; [_txtQuery release]; [_addressLookup stop]; [_addressLookup release]; + [_name release]; + [_type release]; + [_domain release]; + [_fullName release]; + [_hostname release]; [super dealloc]; } -@synthesize name=_name, type=_type, domain=_domain, interfaceIndex=_interfaceIndex; +@synthesize name=_name, type=_type, domain=_domain, fullName=_fullName, interfaceIndex=_interfaceIndex; - (NSString*) description { - return $sprintf(@"%@['%@'.%@%@]", self.class,_name,_type,_domain); + return $sprintf(@"%@[%@]", self.class,self.fullName); } @@ -101,18 +106,13 @@ - (void) priv_finishResolve { - // If I haven't finished my resolve yet, run it synchronously now so I can return a valid value: + // 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; @@ -129,9 +129,12 @@ - (NSDictionary*) txtRecord { - // If I haven't started my resolve yet, start it now. (_txtRecord will be nil till it finishes.) - if (!_startedResolve) - [self start]; + if (!_txtQuery) { + _txtQuery = [[MYBonjourQuery alloc] initWithBonjourService: self + recordType: kDNSServiceType_TXT]; + _txtQuery.continuous = YES; + [_txtQuery start]; + } return _txtRecord; } @@ -172,32 +175,24 @@ #pragma mark - -#pragma mark FULLNAME/HOSTNAME/PORT RESOLUTION: +#pragma mark HOSTNAME/PORT RESOLUTION: -- (void) priv_resolvedFullName: (NSString*)fullName - hostname: (NSString*)hostname +- (void) priv_resolvedHostname: (NSString*)hostname port: (uint16_t)port txtRecord: (NSData*)txtData { - LogTo(Bonjour, @"%@: fullname='%@', hostname=%@, port=%u, txt=%u bytes", - self, fullName, hostname, port, txtData.length); + LogTo(Bonjour, @"%@: hostname=%@, port=%u, txt=%u bytes", + self, 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]; } @@ -215,29 +210,26 @@ MYBonjourService *service = context; @try{ //LogTo(Bonjour, @"resolveCallback for %@ (err=%i)", service,errorCode); - if (errorCode) { - [service setError: errorCode]; - } else { + if (!errorCode) { NSData *txtData = nil; if (txtRecord) txtData = [NSData dataWithBytes: txtRecord length: txtLen]; - [service priv_resolvedFullName: [NSString stringWithUTF8String: fullname] - hostname: [NSString stringWithUTF8String: hosttarget] + [service priv_resolvedHostname: [NSString stringWithUTF8String: hosttarget] port: ntohs(port) txtRecord: txtData]; } }catchAndReport(@"MYBonjourResolver query callback"); + [service gotResponse: errorCode]; } -- (DNSServiceRef) createServiceRef { +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr { _startedResolve = YES; - DNSServiceRef serviceRef = NULL; - self.error = DNSServiceResolve(&serviceRef, 0, - _interfaceIndex, - _name.UTF8String, _type.UTF8String, _domain.UTF8String, - &resolveCallback, self); - return serviceRef; + return DNSServiceResolve(sdRefPtr, + kDNSServiceFlagsShareConnection, + _interfaceIndex, + _name.UTF8String, _type.UTF8String, _domain.UTF8String, + &resolveCallback, self); } diff -r 59689fbdcf77 -r 1d6924779df7 MYNetwork-iPhone.xcodeproj/project.pbxproj --- a/MYNetwork-iPhone.xcodeproj/project.pbxproj Tue Apr 28 10:36:28 2009 -0700 +++ b/MYNetwork-iPhone.xcodeproj/project.pbxproj Wed Apr 29 13:29:31 2009 -0700 @@ -41,6 +41,12 @@ 278C1B2F0F9F865800954AE1 /* PortMapperTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 278C1B2D0F9F865800954AE1 /* PortMapperTest.m */; }; 278C1B350F9F86A100954AE1 /* MYUtilities_Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 278C1B330F9F86A100954AE1 /* MYUtilities_Debug.xcconfig */; }; 278C1B360F9F86A100954AE1 /* MYUtilities_Release.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 278C1B340F9F86A100954AE1 /* MYUtilities_Release.xcconfig */; }; + 27D915BF0FA8EABC002B0DEC /* MYDNSService.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D915BC0FA8EABC002B0DEC /* MYDNSService.m */; }; + 27D915C00FA8EABC002B0DEC /* MYAddressLookup.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D915BE0FA8EABC002B0DEC /* MYAddressLookup.m */; }; + 27D915C90FA8EAD0002B0DEC /* MYBonjourBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D915C20FA8EAD0002B0DEC /* MYBonjourBrowser.m */; }; + 27D915CA0FA8EAD0002B0DEC /* MYBonjourService.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D915C40FA8EAD0002B0DEC /* MYBonjourService.m */; }; + 27D915CB0FA8EAD0002B0DEC /* MYBonjourQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D915C60FA8EAD0002B0DEC /* MYBonjourQuery.m */; }; + 27D915CC0FA8EAD0002B0DEC /* MYBonjourRegistration.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D915C80FA8EAD0002B0DEC /* MYBonjourRegistration.m */; }; 280E754F0DD40C5E005A515E /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 280E754C0DD40C5E005A515E /* MainWindow.xib */; }; /* End PBXBuildFile section */ @@ -107,6 +113,18 @@ 278C1B2D0F9F865800954AE1 /* PortMapperTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PortMapperTest.m; sourceTree = ""; }; 278C1B330F9F86A100954AE1 /* MYUtilities_Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = MYUtilities_Debug.xcconfig; sourceTree = ""; }; 278C1B340F9F86A100954AE1 /* MYUtilities_Release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = MYUtilities_Release.xcconfig; sourceTree = ""; }; + 27D915BB0FA8EABC002B0DEC /* MYDNSService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYDNSService.h; path = PortMapper/MYDNSService.h; sourceTree = ""; }; + 27D915BC0FA8EABC002B0DEC /* MYDNSService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYDNSService.m; path = PortMapper/MYDNSService.m; sourceTree = ""; }; + 27D915BD0FA8EABC002B0DEC /* MYAddressLookup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYAddressLookup.h; path = Bonjour/MYAddressLookup.h; sourceTree = ""; }; + 27D915BE0FA8EABC002B0DEC /* MYAddressLookup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYAddressLookup.m; path = Bonjour/MYAddressLookup.m; sourceTree = ""; }; + 27D915C10FA8EAD0002B0DEC /* MYBonjourBrowser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYBonjourBrowser.h; path = Bonjour/MYBonjourBrowser.h; sourceTree = ""; }; + 27D915C20FA8EAD0002B0DEC /* MYBonjourBrowser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYBonjourBrowser.m; path = Bonjour/MYBonjourBrowser.m; sourceTree = ""; }; + 27D915C30FA8EAD0002B0DEC /* MYBonjourService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYBonjourService.h; path = Bonjour/MYBonjourService.h; sourceTree = ""; }; + 27D915C40FA8EAD0002B0DEC /* MYBonjourService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYBonjourService.m; path = Bonjour/MYBonjourService.m; sourceTree = ""; }; + 27D915C50FA8EAD0002B0DEC /* MYBonjourQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYBonjourQuery.h; path = Bonjour/MYBonjourQuery.h; sourceTree = ""; }; + 27D915C60FA8EAD0002B0DEC /* MYBonjourQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYBonjourQuery.m; path = Bonjour/MYBonjourQuery.m; sourceTree = ""; }; + 27D915C70FA8EAD0002B0DEC /* MYBonjourRegistration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYBonjourRegistration.h; path = Bonjour/MYBonjourRegistration.h; sourceTree = ""; }; + 27D915C80FA8EAD0002B0DEC /* MYBonjourRegistration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYBonjourRegistration.m; path = Bonjour/MYBonjourRegistration.m; sourceTree = ""; }; 280E754C0DD40C5E005A515E /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = iPhone/main.m; sourceTree = ""; }; 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -149,11 +167,11 @@ 270E9AA00EE61113003F17CA /* MYNetwork */ = { isa = PBXGroup; children = ( - 270E9AA10EE61113003F17CA /* IPAddress.h */, - 270E9AA20EE61113003F17CA /* IPAddress.m */, + 27D915B90FA8EA85002B0DEC /* Addressing */, + 27D915BA0FA8EA98002B0DEC /* Bonjour */, + 278C1B2A0F9F865800954AE1 /* PortMapper */, 270E9AA30EE61113003F17CA /* TCP */, 270E9AAF0EE61113003F17CA /* BLIP */, - 278C1B2A0F9F865800954AE1 /* PortMapper */, ); name = MYNetwork; sourceTree = ""; @@ -219,8 +237,7 @@ 270E9ADA0EE6111A003F17CA /* GoogleToolboxSubset */, ); name = MYUtilities; - path = ../MYUtilities; - sourceTree = SOURCE_ROOT; + sourceTree = MYUtilities; }; 270E9ADA0EE6111A003F17CA /* GoogleToolboxSubset */ = { isa = PBXGroup; @@ -253,6 +270,34 @@ path = PortMapper; sourceTree = ""; }; + 27D915B90FA8EA85002B0DEC /* Addressing */ = { + isa = PBXGroup; + children = ( + 270E9AA10EE61113003F17CA /* IPAddress.h */, + 270E9AA20EE61113003F17CA /* IPAddress.m */, + 27D915BB0FA8EABC002B0DEC /* MYDNSService.h */, + 27D915BC0FA8EABC002B0DEC /* MYDNSService.m */, + 27D915BD0FA8EABC002B0DEC /* MYAddressLookup.h */, + 27D915BE0FA8EABC002B0DEC /* MYAddressLookup.m */, + ); + name = Addressing; + sourceTree = ""; + }; + 27D915BA0FA8EA98002B0DEC /* Bonjour */ = { + isa = PBXGroup; + children = ( + 27D915C10FA8EAD0002B0DEC /* MYBonjourBrowser.h */, + 27D915C20FA8EAD0002B0DEC /* MYBonjourBrowser.m */, + 27D915C30FA8EAD0002B0DEC /* MYBonjourService.h */, + 27D915C40FA8EAD0002B0DEC /* MYBonjourService.m */, + 27D915C50FA8EAD0002B0DEC /* MYBonjourQuery.h */, + 27D915C60FA8EAD0002B0DEC /* MYBonjourQuery.m */, + 27D915C70FA8EAD0002B0DEC /* MYBonjourRegistration.h */, + 27D915C80FA8EAD0002B0DEC /* MYBonjourRegistration.m */, + ); + name = Bonjour; + sourceTree = ""; + }; 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { isa = PBXGroup; children = ( @@ -387,6 +432,12 @@ 270E9BA20EE64B4E003F17CA /* MyViewController.m in Sources */, 278C1B2E0F9F865800954AE1 /* MYPortMapper.m in Sources */, 278C1B2F0F9F865800954AE1 /* PortMapperTest.m in Sources */, + 27D915BF0FA8EABC002B0DEC /* MYDNSService.m in Sources */, + 27D915C00FA8EABC002B0DEC /* MYAddressLookup.m in Sources */, + 27D915C90FA8EAD0002B0DEC /* MYBonjourBrowser.m in Sources */, + 27D915CA0FA8EAD0002B0DEC /* MYBonjourService.m in Sources */, + 27D915CB0FA8EAD0002B0DEC /* MYBonjourQuery.m in Sources */, + 27D915CC0FA8EAD0002B0DEC /* MYBonjourRegistration.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -417,7 +468,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIES = "*.nib *.lproj *.framework *.gch *.xcode* (*) CVS .svn .hg"; ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphonesimulator2.0; + SDKROOT = iphonesimulator2.2.1; }; name = Debug; }; @@ -427,7 +478,7 @@ buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - SDKROOT = iphonesimulator2.0; + SDKROOT = iphonesimulator2.2.1; }; name = Release; }; diff -r 59689fbdcf77 -r 1d6924779df7 MYNetwork.xcodeproj/project.pbxproj --- a/MYNetwork.xcodeproj/project.pbxproj Tue Apr 28 10:36:28 2009 -0700 +++ b/MYNetwork.xcodeproj/project.pbxproj Wed Apr 29 13:29:31 2009 -0700 @@ -25,6 +25,9 @@ 270461470DE491A6003D9D3F /* Target.m in Sources */ = {isa = PBXBuildFile; fileRef = 270461460DE491A6003D9D3F /* Target.m */; }; 270461890DE49634003D9D3F /* CollectionUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 270461870DE49634003D9D3F /* CollectionUtils.m */; }; 2706F1D90F9D3EF300292CCF /* SecurityInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2706F1D80F9D3EF300292CCF /* SecurityInterface.framework */; }; + 273B457B0FA681EE00276298 /* MYBonjourRegistration.h in Headers */ = {isa = PBXBuildFile; fileRef = 273B45790FA681EE00276298 /* MYBonjourRegistration.h */; }; + 273B457C0FA681EE00276298 /* MYBonjourRegistration.m in Sources */ = {isa = PBXBuildFile; fileRef = 273B457A0FA681EE00276298 /* MYBonjourRegistration.m */; }; + 273B457D0FA681EE00276298 /* MYBonjourRegistration.m in Sources */ = {isa = PBXBuildFile; fileRef = 273B457A0FA681EE00276298 /* MYBonjourRegistration.m */; }; 2777C9110F7602A7007F8D30 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2777C9100F7602A7007F8D30 /* Security.framework */; }; 2779048B0DE9204300C6D295 /* BLIPEchoClient.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2779048A0DE9204300C6D295 /* BLIPEchoClient.xib */; }; 277905240DE9E5BC00C6D295 /* BLIPEchoServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 277903D60DE8EE4800C6D295 /* BLIPEchoServer.m */; }; @@ -165,6 +168,8 @@ 270462C10DE4A64B003D9D3F /* MYUtilitiesTest_main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYUtilitiesTest_main.m; sourceTree = ""; }; 270462C30DE4A65B003D9D3F /* BLIP Overview.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "BLIP Overview.txt"; path = "BLIP/BLIP Overview.txt"; sourceTree = ""; wrapsLines = 1; }; 2706F1D80F9D3EF300292CCF /* SecurityInterface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SecurityInterface.framework; path = System/Library/Frameworks/SecurityInterface.framework; sourceTree = SDKROOT; }; + 273B45790FA681EE00276298 /* MYBonjourRegistration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYBonjourRegistration.h; sourceTree = ""; }; + 273B457A0FA681EE00276298 /* MYBonjourRegistration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYBonjourRegistration.m; sourceTree = ""; }; 274122DD0F9CDD1600F21842 /* MYUtilities_Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = MYUtilities_Debug.xcconfig; sourceTree = ""; }; 274122DE0F9CDD1600F21842 /* MYUtilities_Release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = MYUtilities_Release.xcconfig; sourceTree = ""; }; 2777C9100F7602A7007F8D30 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; @@ -182,8 +187,8 @@ 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 = ""; }; + 2780F49F0FA2C59000C0FB83 /* MYAddressLookup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MYAddressLookup.h; path = Bonjour/MYAddressLookup.h; sourceTree = ""; }; + 2780F4A00FA2C59000C0FB83 /* MYAddressLookup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MYAddressLookup.m; path = Bonjour/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 = ""; }; @@ -389,6 +394,8 @@ 270461020DE49030003D9D3F /* IPAddress.m */, 2780F20A0FA194BD00C0FB83 /* MYDNSService.h */, 2780F20B0FA194BD00C0FB83 /* MYDNSService.m */, + 2780F49F0FA2C59000C0FB83 /* MYAddressLookup.h */, + 2780F4A00FA2C59000C0FB83 /* MYAddressLookup.m */, ); name = Addressing; sourceTree = ""; @@ -412,8 +419,8 @@ 278C1BA10F9F92EA00954AE1 /* MYBonjourService.m */, 2780F4360FA28F4400C0FB83 /* MYBonjourQuery.h */, 2780F4370FA28F4400C0FB83 /* MYBonjourQuery.m */, - 2780F49F0FA2C59000C0FB83 /* MYAddressLookup.h */, - 2780F4A00FA2C59000C0FB83 /* MYAddressLookup.m */, + 273B45790FA681EE00276298 /* MYBonjourRegistration.h */, + 273B457A0FA681EE00276298 /* MYBonjourRegistration.m */, ); path = Bonjour; sourceTree = ""; @@ -438,6 +445,7 @@ 2780F20C0FA194BD00C0FB83 /* MYDNSService.h in Headers */, 2780F4380FA28F4400C0FB83 /* MYBonjourQuery.h in Headers */, 2780F4A10FA2C59000C0FB83 /* MYAddressLookup.h in Headers */, + 273B457B0FA681EE00276298 /* MYBonjourRegistration.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -595,6 +603,7 @@ 2780F20D0FA194BD00C0FB83 /* MYDNSService.m in Sources */, 2780F4390FA28F4400C0FB83 /* MYBonjourQuery.m in Sources */, 2780F4A20FA2C59000C0FB83 /* MYAddressLookup.m in Sources */, + 273B457C0FA681EE00276298 /* MYBonjourRegistration.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -631,6 +640,7 @@ 2780F20E0FA194BD00C0FB83 /* MYDNSService.m in Sources */, 2780F43A0FA28F4400C0FB83 /* MYBonjourQuery.m in Sources */, 2780F4A30FA2C59000C0FB83 /* MYAddressLookup.m in Sources */, + 273B457D0FA681EE00276298 /* MYBonjourRegistration.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff -r 59689fbdcf77 -r 1d6924779df7 PortMapper/MYDNSService.h --- a/PortMapper/MYDNSService.h Tue Apr 28 10:36:28 2009 -0700 +++ b/PortMapper/MYDNSService.h Wed Apr 29 13:29:31 2009 -0700 @@ -8,17 +8,20 @@ #import #import +@class MYDNSConnection; /** Abstract superclass for services based on DNSServiceRefs, such as MYPortMapper. */ @interface MYDNSService : NSObject { @private + BOOL _usePrivateConnection; + MYDNSConnection *_connection; struct _DNSServiceRef_t *_serviceRef; CFSocketRef _socket; CFRunLoopSourceRef _socketSource; SInt32 _error; - BOOL _continuous; + BOOL _continuous, _gotResponse; } /** If NO (the default), the service will stop after it gets a result. @@ -40,12 +43,30 @@ This property is KV observable. */ @property int32_t error; + +/** Utility to construct a service's full name. */ ++ (NSString*) fullNameOfService: (NSString*)serviceName + ofType: (NSString*)type + inDomain: (NSString*)domain; + + + // PROTECTED: + +@property BOOL usePrivateConnection; + /** Subclass must implement this abstract method to create a new DNSServiceRef. This method is called by -open. - If an error occurs, the method should set self.error and return NULL.*/ -- (struct _DNSServiceRef_t*) createServiceRef; + The implementation MUST pass the given sdRefPtr directly to the DNSService function + that creates the new ref, without setting it to NULL first. + It MUST also set the kDNSServiceFlagsShareConnection flag. */ +- (int32_t/*DNSServiceErrorType*/) createServiceRef: (struct _DNSServiceRef_t**)sdRefPtr; + +/** Subclass's callback must call this method after doing its own work. + This method will update the error state, and will stop the service if it's not set to be + continuous. */ +- (void) gotResponse: (int32_t/*DNSServiceErrorType*/)errorCode; @property (readonly) struct _DNSServiceRef_t* serviceRef; @@ -58,4 +79,23 @@ @return YES if a message is received, NO on error (or if the service isn't started) */ - (BOOL) waitForReply; + @end + + + + +@interface MYDNSConnection : NSObject +{ + struct _DNSServiceRef_t* _connectionRef; + CFSocketRef _socket; + CFRunLoopSourceRef _runLoopSource; +} + ++ (MYDNSConnection*) sharedConnection; +- (id) initWithServiceRef: (struct _DNSServiceRef_t *)serviceRef; +@property (readonly) struct _DNSServiceRef_t* connectionRef; +- (BOOL) processResult; +- (void) close; + +@end diff -r 59689fbdcf77 -r 1d6924779df7 PortMapper/MYDNSService.m --- a/PortMapper/MYDNSService.m Tue Apr 28 10:36:28 2009 -0700 +++ b/PortMapper/MYDNSService.m Wed Apr 29 13:29:31 2009 -0700 @@ -20,7 +20,7 @@ CFDataRef address, const void *data, void *clientCallBackInfo); - + @implementation MYDNSService @@ -52,14 +52,25 @@ } -@synthesize continuous=_continuous, serviceRef=_serviceRef; +@synthesize continuous=_continuous, serviceRef=_serviceRef, usePrivateConnection=_usePrivateConnection; -- (DNSServiceRef) createServiceRef { +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr { AssertAbstractMethod(); } +- (void) gotResponse: (DNSServiceErrorType)errorCode { + _gotResponse = YES; + if (!_continuous) + [self cancel]; + if (errorCode && errorCode != _error) { + Log(@"%@ got error %i", self,errorCode); + self.error = errorCode; + } +} + + - (BOOL) start { if (_serviceRef) @@ -67,55 +78,60 @@ if (_error) self.error = 0; + _gotResponse = NO; + if (!_usePrivateConnection) { + _connection = [[MYDNSConnection sharedConnection] retain]; + if (!_connection) { + self.error = kDNSServiceErr_Unknown; + return NO; + } + _serviceRef = _connection.connectionRef; + } + // Ask the subclass to create a DNSServiceRef: - _serviceRef = [self createServiceRef]; + _error = [self createServiceRef: &_serviceRef]; + if (_error) { + _serviceRef = NULL; + setObj(&_connection,nil); + if (!_error) + self.error = kDNSServiceErr_Unknown; + LogTo(DNS,@"Failed to open %@ -- err=%i",self,_error); + return NO; + } - if (_serviceRef) { - // Wrap a CFSocket around the service's socket: - CFSocketContext ctxt = { 0, self, CFRetain, CFRelease, NULL }; - _socket = CFSocketCreateWithNative(NULL, - DNSServiceRefSockFD(_serviceRef), - kCFSocketReadCallBack, - &serviceCallback, &ctxt); - if( _socket ) { - CFSocketSetSocketFlags(_socket, CFSocketGetSocketFlags(_socket) & ~kCFSocketCloseOnInvalidate); - // Attach the socket to the runloop so the serviceCallback will be invoked: - _socketSource = CFSocketCreateRunLoopSource(NULL, _socket, 0); - if( _socketSource ) { - CFRunLoopAddSource(CFRunLoopGetCurrent(), _socketSource, kCFRunLoopCommonModes); - LogTo(DNS,@"Opening %@ -- service=%p",self,_serviceRef); - return YES; // success - } - } - } - if (!_error) - self.error = kDNSServiceErr_Unknown; - LogTo(DNS,@"Failed to open %@ -- err=%i",self,_error); - [self cancel]; - return NO; + if (!_connection) + _connection = [[MYDNSConnection alloc] initWithServiceRef: _serviceRef]; + + LogTo(DNS,@"Started %@",self); + return YES; // Succeeded +} + + +- (BOOL) waitForReply { + if( ! _serviceRef ) + if( ! [self start] ) + return NO; + // Run the runloop until there's either an error or a result: + _gotResponse = NO; + LogTo(DNS,@"Waiting for reply to %@...", self); + while( !_gotResponse ) + if( ! [_connection processResult] ) + break; + LogTo(DNS,@" ...got reply"); + return (self.error==0); } - (void) cancel { - [self retain]; // Prevents _socket's dealloc from releasing & deallocing me! - if( _socketSource ) { - CFRunLoopSourceInvalidate(_socketSource); - CFRelease(_socketSource); - _socketSource = NULL; - } - if( _socket ) { - CFSocketInvalidate(_socket); - CFRelease(_socket); - _socket = NULL; - } if( _serviceRef ) { LogTo(DNS,@"Stopped %@",self); DNSServiceRefDeallocate(_serviceRef); _serviceRef = NULL; + + setObj(&_connection,nil); } - [self release]; } @@ -127,31 +143,159 @@ } -- (BOOL) priv_processResult ++ (NSString*) fullNameOfService: (NSString*)serviceName + ofType: (NSString*)type + inDomain: (NSString*)domain { - 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; + //FIX: Do I need to un-escape the serviceName? + Assert(type); + Assert(domain); + char fullName[kDNSServiceMaxDomainName]; + if (DNSServiceConstructFullName(fullName, serviceName.UTF8String, type.UTF8String, domain.UTF8String) == 0) + return [NSString stringWithUTF8String: fullName]; + else + return nil; +} + + +@end + + +#pragma mark - +#pragma mark SHARED CONNECTION: + + +@interface MYDNSConnection () +- (BOOL) open; +@end + + +@implementation MYDNSConnection + + +MYDNSConnection *sSharedConnection; + + +- (id) init +{ + DNSServiceRef connectionRef = NULL; + DNSServiceErrorType err = DNSServiceCreateConnection(&connectionRef); + if (err || !connectionRef) { + Warn(@"MYDNSConnection: DNSServiceCreateConnection failed, err=%i", err); + [self release]; + return nil; + } + return [self initWithServiceRef: connectionRef]; +} + + +- (id) initWithServiceRef: (DNSServiceRef)serviceRef +{ + Assert(serviceRef); + self = [super init]; + if (self != nil) { + _connectionRef = serviceRef; + LogTo(DNS,@"INIT %@", self); + if (![self open]) { + [self release]; + return nil; + } + } + return self; +} + + ++ (MYDNSConnection*) sharedConnection { + @synchronized(self) { + if (!sSharedConnection) + sSharedConnection = [[[self alloc] init] autorelease]; + } + return sSharedConnection; +} + + +- (void) dealloc +{ + LogTo(DNS,@"DEALLOC %@", self); + [self close]; + [super dealloc]; +} + +- (void) finalize { + [self close]; + [super finalize]; +} + + +@synthesize connectionRef=_connectionRef; + +- (NSString*) description { + return $sprintf(@"%@[conn=%p]", self.class,_connectionRef); +} + +- (BOOL) open { + if (_runLoopSource) + return YES; // Already opened + + // Wrap a CFSocket around the service's socket: + CFSocketContext ctxt = { 0, self, CFRetain, CFRelease, NULL }; + _socket = CFSocketCreateWithNative(NULL, + DNSServiceRefSockFD(_connectionRef), + kCFSocketReadCallBack, + &serviceCallback, &ctxt); + if( _socket ) { + CFSocketSetSocketFlags(_socket, + CFSocketGetSocketFlags(_socket) & ~kCFSocketCloseOnInvalidate); + // Attach the socket to the runloop so the serviceCallback will be invoked: + _runLoopSource = CFSocketCreateRunLoopSource(NULL, _socket, 0); + if( _runLoopSource ) { + CFRunLoopAddSource(CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes); + // Success! + LogTo(DNS,@"Successfully opened %@", self); + return YES; + } + } + + // Failure: + Warn(@"Failed to connect %@ to runloop", self); + [self close]; + return NO; +} + + +- (void) close { + @synchronized(self) { + if( _runLoopSource ) { + CFRunLoopSourceInvalidate(_runLoopSource); + CFRelease(_runLoopSource); + _runLoopSource = NULL; + } + if( _socket ) { + CFSocketInvalidate(_socket); + CFRelease(_socket); + _socket = NULL; + } + if( _connectionRef ) { + LogTo(DNS,@"Closed %@",self); + DNSServiceRefDeallocate(_connectionRef); + _connectionRef = NULL; + } + + if (self==sSharedConnection) + sSharedConnection = nil; } } -- (BOOL) waitForReply -{ - if (!_serviceRef) - return NO; - LogTo(DNS,@"Waiting for %@ ...", self); - BOOL ok = [self priv_processResult]; - LogTo(DNS,@" ...done waiting"); - return ok; -} + +- (BOOL) processResult { + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + LogTo(DNS,@"---serviceCallback----"); + DNSServiceErrorType err = DNSServiceProcessResult(_connectionRef); + if (err) + Warn(@"%@: DNSServiceProcessResult failed, err=%i !!!", self,err); + [pool drain]; + return !err; +} /** CFSocket callback, informing us that _socket has data available, which means @@ -161,11 +305,8 @@ CFSocketCallBackType type, CFDataRef address, const void *data, void *clientCallBackInfo) { - NSAutoreleasePool *pool = [NSAutoreleasePool new]; - @try{ - [(MYDNSService*)clientCallBackInfo priv_processResult]; - }catchAndReport(@"PortMapper serviceCallback"); - [pool drain]; + MYDNSConnection *connection = clientCallBackInfo; + [connection processResult]; } diff -r 59689fbdcf77 -r 1d6924779df7 PortMapper/MYPortMapper.m --- a/PortMapper/MYPortMapper.m Tue Apr 28 10:36:28 2009 -0700 +++ b/PortMapper/MYPortMapper.m Wed Apr 29 13:29:31 2009 -0700 @@ -94,8 +94,6 @@ errorCode = kDNSServiceErr_NATPortMappingUnsupported; } } - if( errorCode != self.error ) - self.error = errorCode; [self priv_updateLocalAddress]; IPAddress *publicAddress = makeIPAddr(rawPublicAddress,publicPort); @@ -106,6 +104,8 @@ LogTo(PortMapper,@"%@: Public addr is %@ (mapped=%i)", self, self.publicAddress, self.isMapped); } + + [self gotResponse: errorCode]; [[NSNotificationCenter defaultCenter] postNotificationName: MYPortMapperChangedNotification object: self]; } @@ -135,22 +135,19 @@ } -- (DNSServiceRef) createServiceRef -{ +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr { DNSServiceProtocol protocols = 0; if( _mapTCP ) protocols |= kDNSServiceProtocol_TCP; if( _mapUDP ) protocols |= kDNSServiceProtocol_UDP; - DNSServiceRef serviceRef = NULL; - self.error = DNSServiceNATPortMappingCreate(&serviceRef, - 0 /*flags*/, - 0 /*interfaceIndex*/, - protocols, - htons(_localPort), - htons(_desiredPublicPort), - 0 /*ttl*/, - &portMapCallback, - self); - return serviceRef; + return DNSServiceNATPortMappingCreate(sdRefPtr, + kDNSServiceFlagsShareConnection, + 0 /*interfaceIndex*/, + protocols, + htons(_localPort), + htons(_desiredPublicPort), + 0 /*ttl*/, + &portMapCallback, + self); }