jens@31: // jens@31: // MYBonjourRegistration.m jens@31: // MYNetwork jens@31: // jens@31: // Created by Jens Alfke on 4/27/09. jens@31: // Copyright 2009 Jens Alfke. All rights reserved. jens@31: // jens@31: jens@31: #import "MYBonjourRegistration.h" jens@31: #import "MYBonjourService.h" jens@31: #import "ExceptionUtils.h" jens@31: #import "Test.h" jens@31: #import "Logging.h" jens@31: #import jens@31: jens@31: jens@31: #define kTXTTTL 60 // TTL in seconds for TXT records I register jens@31: jens@31: jens@31: @interface MYBonjourRegistration () jens@31: @property BOOL registered; jens@31: @end jens@31: jens@31: jens@31: @implementation MYBonjourRegistration jens@31: jens@31: jens@31: static NSMutableDictionary *sAllRegistrations; jens@31: jens@31: jens@31: + (void) priv_addRegistration: (MYBonjourRegistration*)reg { jens@31: if (!sAllRegistrations) jens@31: sAllRegistrations = [[NSMutableDictionary alloc] init]; jens@31: [sAllRegistrations setObject: reg forKey: reg.fullName]; jens@31: } jens@31: jens@31: + (void) priv_removeRegistration: (MYBonjourRegistration*)reg { jens@31: [sAllRegistrations removeObjectForKey: reg.fullName]; jens@31: } jens@31: jens@31: + (MYBonjourRegistration*) registrationWithFullName: (NSString*)fullName { jens@31: return [sAllRegistrations objectForKey: fullName]; jens@31: } jens@31: jens@31: jens@31: - (id) initWithServiceType: (NSString*)serviceType port: (UInt16)port jens@31: { jens@31: self = [super init]; jens@31: if (self != nil) { jens@31: self.continuous = YES; jens@31: self.usePrivateConnection = YES; // DNSServiceUpdateRecord doesn't work with shared conn :( jens@31: _type = [serviceType copy]; jens@31: _port = port; jens@31: _autoRename = YES; jens@31: } jens@31: return self; jens@31: } jens@31: jens@31: - (void) dealloc { jens@31: [_name release]; jens@31: [_type release]; jens@31: [_domain release]; jens@31: [super dealloc]; jens@31: } jens@31: jens@31: jens@31: @synthesize name=_name, type=_type, domain=_domain, port=_port, autoRename=_autoRename; jens@31: @synthesize registered=_registered; jens@31: jens@31: jens@31: - (NSString*) fullName { jens@31: return [[self class] fullNameOfService: _name ofType: _type inDomain: _domain]; jens@31: } jens@31: jens@31: jens@31: - (BOOL) isSameAsService: (MYBonjourService*)service { jens@31: return _name && _domain && [self.fullName isEqualToString: service.fullName]; jens@31: } jens@31: jens@31: jens@31: - (NSString*) description jens@31: { jens@31: return $sprintf(@"%@['%@'.%@%@]", self.class,_name,_type,_domain); jens@31: } jens@31: jens@31: jens@31: - (void) priv_registeredAsName: (NSString*)name jens@31: type: (NSString*)regtype jens@31: domain: (NSString*)domain jens@31: { jens@31: if (!$equal(name,_name)) jens@31: self.name = name; jens@31: if (!$equal(domain,_domain)) jens@31: self.domain = domain; jens@31: LogTo(Bonjour,@"Registered %@", self); jens@31: self.registered = YES; jens@31: [[self class] priv_addRegistration: self]; jens@31: } jens@31: jens@31: jens@31: static void regCallback(DNSServiceRef sdRef, jens@31: DNSServiceFlags flags, jens@31: DNSServiceErrorType errorCode, jens@31: const char *name, jens@31: const char *regtype, jens@31: const char *domain, jens@31: void *context) jens@31: { jens@31: MYBonjourRegistration *reg = context; jens@31: @try{ jens@31: if (!errorCode) jens@31: [reg priv_registeredAsName: [NSString stringWithUTF8String: name] jens@31: type: [NSString stringWithUTF8String: regtype] jens@31: domain: [NSString stringWithUTF8String: domain]]; jens@31: }catchAndReport(@"MYBonjourRegistration callback"); jens@31: [reg gotResponse: errorCode]; jens@31: } jens@31: jens@31: jens@31: - (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr { jens@31: DNSServiceFlags flags = 0; jens@31: if (!_autoRename) jens@31: flags |= kDNSServiceFlagsNoAutoRename; jens@31: NSData *txtData = nil; jens@31: if (_txtRecord) jens@31: txtData = [NSNetService dataFromTXTRecordDictionary: _txtRecord]; jens@31: return DNSServiceRegister(sdRefPtr, jens@31: flags, jens@31: 0, jens@31: _name.UTF8String, // _name is likely to be nil jens@31: _type.UTF8String, jens@31: _domain.UTF8String, // _domain is most likely nil jens@31: NULL, jens@31: htons(_port), jens@31: txtData.length, jens@31: txtData.bytes, jens@31: ®Callback, jens@31: self); jens@31: } jens@31: jens@31: jens@31: - (void) cancel { jens@31: [super cancel]; jens@31: if (_registered) { jens@31: [[self class] priv_removeRegistration: self]; jens@31: self.registered = NO; jens@31: } jens@31: } jens@31: jens@31: jens@47: + (NSData*) dataFromTXTRecordDictionary: (NSDictionary*)txtDict { jens@47: // First translate any non-NSData values into UTF-8 formatted description data: jens@47: NSMutableDictionary *encodedDict = $mdict(); jens@47: for (NSString *key in txtDict) { jens@47: id value = [txtDict objectForKey: key]; jens@47: if (![value isKindOfClass: [NSData class]]) { jens@47: value = [[value description] dataUsingEncoding: NSUTF8StringEncoding]; jens@47: } jens@47: [encodedDict setObject: value forKey: key]; jens@47: } jens@47: return [NSNetService dataFromTXTRecordDictionary: encodedDict]; jens@47: } jens@47: jens@47: jens@31: - (void) updateTxtRecord { jens@31: [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil]; jens@31: if (self.serviceRef) { jens@47: NSData *data = [[self class] dataFromTXTRecordDictionary: _txtRecord]; jens@31: Assert(data!=nil, @"Can't convert dictionary to TXT record"); jens@31: DNSServiceErrorType err = DNSServiceUpdateRecord(self.serviceRef, jens@31: NULL, jens@31: 0, jens@31: data.length, jens@31: data.bytes, jens@31: kTXTTTL); jens@31: if (err) jens@31: Warn(@"%@ failed to update TXT (err=%i)", self,err); jens@31: else jens@31: LogTo(Bonjour,@"%@ updated TXT to %@", self,data); jens@31: } jens@31: } jens@31: jens@31: jens@31: - (NSDictionary*) txtRecord { jens@31: return _txtRecord; jens@31: } jens@31: jens@31: - (void) setTxtRecord: (NSDictionary*)txtDict { jens@31: if (!$equal(_txtRecord,txtDict)) { jens@31: if (txtDict) jens@31: [_txtRecord setDictionary: txtDict]; jens@31: else jens@31: setObj(&_txtRecord,nil); jens@31: [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil]; jens@31: [self performSelector: @selector(updateTxtRecord) withObject: nil afterDelay: 0.1]; jens@31: } jens@31: } jens@31: jens@31: - (void) setString: (NSString*)value forTxtKey: (NSString*)key jens@31: { jens@31: NSData *data = [value dataUsingEncoding: NSUTF8StringEncoding]; jens@31: if (!$equal(data, [_txtRecord objectForKey: key])) { jens@31: if (data) { jens@31: if (!_txtRecord) _txtRecord = [[NSMutableDictionary alloc] init]; jens@31: [_txtRecord setObject: data forKey: key]; jens@31: } else jens@31: [_txtRecord removeObjectForKey: key]; jens@31: [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil]; jens@31: [self performSelector: @selector(updateTxtRecord) withObject: nil afterDelay: 0.1]; jens@31: } jens@31: } jens@31: jens@31: @end jens@31: jens@31: jens@31: jens@31: jens@31: #pragma mark - jens@31: #pragma mark TESTING: jens@31: jens@31: #if DEBUG jens@31: jens@31: #import "MYBonjourQuery.h" jens@31: #import "MYAddressLookup.h" jens@31: jens@31: @interface BonjourRegTester : NSObject jens@31: { jens@31: MYBonjourRegistration *_reg; jens@31: BOOL _updating; jens@31: } jens@31: @end jens@31: jens@31: @implementation BonjourRegTester jens@31: jens@31: - (void) updateTXT { jens@31: NSDictionary *txt = $dict({@"time", $sprintf(@"%.3lf", CFAbsoluteTimeGetCurrent())}); jens@31: _reg.txtRecord = txt; jens@31: [self performSelector: @selector(updateTXT) withObject: nil afterDelay: 3.0]; jens@31: } jens@31: jens@31: - (id) init jens@31: { jens@31: self = [super init]; jens@31: if (self != nil) { jens@31: _reg = [[MYBonjourRegistration alloc] initWithServiceType: @"_foo._tcp" port: 12345]; jens@31: [_reg addObserver: self forKeyPath: @"registered" options: NSKeyValueObservingOptionNew context: NULL]; jens@31: [_reg addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: NULL]; jens@31: jens@31: [self updateTXT]; jens@31: [_reg start]; jens@31: } jens@31: return self; jens@31: } jens@31: jens@31: - (void) dealloc jens@31: { jens@31: [_reg stop]; jens@31: [_reg removeObserver: self forKeyPath: @"registered"]; jens@31: [_reg removeObserver: self forKeyPath: @"name"]; jens@31: [_reg release]; jens@31: [super dealloc]; jens@31: } jens@31: jens@31: - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context jens@31: { jens@31: LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change); jens@31: } jens@31: jens@31: @end jens@31: jens@31: TestCase(BonjourReg) { jens@31: EnableLogTo(Bonjour,YES); jens@31: EnableLogTo(DNS,YES); jens@31: [NSRunLoop currentRunLoop]; // create runloop jens@31: BonjourRegTester *tester = [[BonjourRegTester alloc] init]; jens@31: [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 15]]; jens@31: [tester release]; jens@31: } jens@31: jens@31: #endif jens@31: jens@31: jens@31: /* jens@31: Copyright (c) 2008-2009, Jens Alfke . All rights reserved. jens@31: jens@31: Redistribution and use in source and binary forms, with or without modification, are permitted jens@31: provided that the following conditions are met: jens@31: jens@31: * Redistributions of source code must retain the above copyright notice, this list of conditions jens@31: and the following disclaimer. jens@31: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions jens@31: and the following disclaimer in the documentation and/or other materials provided with the jens@31: distribution. jens@31: jens@31: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR jens@31: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND jens@31: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- jens@31: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES jens@31: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR jens@31: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN jens@31: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF jens@31: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jens@31: */