Merged Jens' latest changes.
2 // MYBonjourRegistration.m
5 // Created by Jens Alfke on 4/27/09.
6 // Copyright 2009 Jens Alfke. All rights reserved.
9 #import "MYBonjourRegistration.h"
10 #import "MYBonjourService.h"
11 #import "ExceptionUtils.h"
17 #define kTXTTTL 60 // TTL in seconds for TXT records I register
20 @interface MYBonjourRegistration ()
21 @property BOOL registered;
25 @implementation MYBonjourRegistration
28 static NSMutableDictionary *sAllRegistrations;
31 + (void) priv_addRegistration: (MYBonjourRegistration*)reg {
32 if (!sAllRegistrations)
33 sAllRegistrations = [[NSMutableDictionary alloc] init];
34 [sAllRegistrations setObject: reg forKey: reg.fullName];
37 + (void) priv_removeRegistration: (MYBonjourRegistration*)reg {
38 [sAllRegistrations removeObjectForKey: reg.fullName];
41 + (MYBonjourRegistration*) registrationWithFullName: (NSString*)fullName {
42 return [sAllRegistrations objectForKey: fullName];
46 - (id) initWithServiceType: (NSString*)serviceType port: (UInt16)port
50 self.continuous = YES;
51 self.usePrivateConnection = YES; // DNSServiceUpdateRecord doesn't work with shared conn :(
52 _type = [serviceType copy];
68 @synthesize name=_name, type=_type, domain=_domain, port=_port, autoRename=_autoRename;
69 @synthesize registered=_registered;
72 - (NSString*) fullName {
73 return [[self class] fullNameOfService: _name ofType: _type inDomain: _domain];
77 - (BOOL) isSameAsService: (MYBonjourService*)service {
78 return _name && _domain && [self.fullName isEqualToString: service.fullName];
82 - (NSString*) description
84 return $sprintf(@"%@['%@'.%@%@]", self.class,_name,_type,_domain);
88 - (void) priv_registeredAsName: (NSString*)name
89 type: (NSString*)regtype
90 domain: (NSString*)domain
92 if (!$equal(name,_name))
94 if (!$equal(domain,_domain))
96 LogTo(Bonjour,@"Registered %@", self);
97 self.registered = YES;
98 [[self class] priv_addRegistration: self];
102 static void regCallback(DNSServiceRef sdRef,
103 DNSServiceFlags flags,
104 DNSServiceErrorType errorCode,
110 MYBonjourRegistration *reg = context;
113 [reg priv_registeredAsName: [NSString stringWithUTF8String: name]
114 type: [NSString stringWithUTF8String: regtype]
115 domain: [NSString stringWithUTF8String: domain]];
116 }catchAndReport(@"MYBonjourRegistration callback");
117 [reg gotResponse: errorCode];
121 - (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
122 DNSServiceFlags flags = 0;
124 flags |= kDNSServiceFlagsNoAutoRename;
125 NSData *txtData = nil;
127 txtData = [NSNetService dataFromTXTRecordDictionary: _txtRecord];
128 return DNSServiceRegister(sdRefPtr,
131 _name.UTF8String, // _name is likely to be nil
133 _domain.UTF8String, // _domain is most likely nil
146 [[self class] priv_removeRegistration: self];
147 self.registered = NO;
152 + (NSData*) dataFromTXTRecordDictionary: (NSDictionary*)txtDict {
155 // First translate any non-NSData values into UTF-8 formatted description data:
156 NSMutableDictionary *encodedDict = $mdict();
157 for (NSString *key in txtDict) {
158 id value = [txtDict objectForKey: key];
159 if (![value isKindOfClass: [NSData class]]) {
160 value = [[value description] dataUsingEncoding: NSUTF8StringEncoding];
162 [encodedDict setObject: value forKey: key];
164 return [NSNetService dataFromTXTRecordDictionary: encodedDict];
168 - (void) updateTxtRecord {
169 [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
170 if (self.serviceRef) {
171 NSData *data = [[self class] dataFromTXTRecordDictionary: _txtRecord];
172 Assert(data!=nil || _txtRecord==nil, @"Can't convert dictionary to TXT record: %@", _txtRecord);
173 DNSServiceErrorType err = DNSServiceUpdateRecord(self.serviceRef,
180 Warn(@"%@ failed to update TXT (err=%i)", self,err);
182 LogTo(Bonjour,@"%@ updated TXT to %@", self,data);
187 - (NSDictionary*) txtRecord {
191 - (void) setTxtRecord: (NSDictionary*)txtDict {
192 if (!$equal(_txtRecord,txtDict)) {
193 setObjCopy(&_txtRecord, txtDict);
194 [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
195 [self performSelector: @selector(updateTxtRecord) withObject: nil afterDelay: 0.1];
199 - (void) setString: (NSString*)value forTxtKey: (NSString*)key
201 NSData *data = [value dataUsingEncoding: NSUTF8StringEncoding];
202 if (!$equal(data, [_txtRecord objectForKey: key])) {
204 if (!_txtRecord) _txtRecord = [[NSMutableDictionary alloc] init];
205 [_txtRecord setObject: data forKey: key];
207 [_txtRecord removeObjectForKey: key];
208 [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
209 [self performSelector: @selector(updateTxtRecord) withObject: nil afterDelay: 0.1];
219 #pragma mark TESTING:
223 #import "MYBonjourQuery.h"
224 #import "MYAddressLookup.h"
226 @interface BonjourRegTester : NSObject
228 MYBonjourRegistration *_reg;
233 @implementation BonjourRegTester
236 NSDictionary *txt = $dict({@"time", $sprintf(@"%.3lf", CFAbsoluteTimeGetCurrent())});
237 _reg.txtRecord = txt;
238 CAssertEqual(_reg.txtRecord, txt);
239 [self performSelector: @selector(updateTXT) withObject: nil afterDelay: 3.0];
246 _reg = [[MYBonjourRegistration alloc] initWithServiceType: @"_foo._tcp" port: 12345];
247 [_reg addObserver: self forKeyPath: @"registered" options: NSKeyValueObservingOptionNew context: NULL];
248 [_reg addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: NULL];
259 [_reg removeObserver: self forKeyPath: @"registered"];
260 [_reg removeObserver: self forKeyPath: @"name"];
265 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
267 LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change);
272 TestCase(BonjourReg) {
273 EnableLogTo(Bonjour,YES);
274 EnableLogTo(DNS,YES);
275 [NSRunLoop currentRunLoop]; // create runloop
276 BonjourRegTester *tester = [[BonjourRegTester alloc] init];
277 [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 15]];
285 Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
287 Redistribution and use in source and binary forms, with or without modification, are permitted
288 provided that the following conditions are met:
290 * Redistributions of source code must retain the above copyright notice, this list of conditions
291 and the following disclaimer.
292 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
293 and the following disclaimer in the documentation and/or other materials provided with the
296 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
297 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
298 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
299 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
300 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
301 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
302 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
303 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.