jens@31
|
1 |
//
|
jens@31
|
2 |
// MYBonjourRegistration.m
|
jens@31
|
3 |
// MYNetwork
|
jens@31
|
4 |
//
|
jens@31
|
5 |
// Created by Jens Alfke on 4/27/09.
|
jens@31
|
6 |
// Copyright 2009 Jens Alfke. All rights reserved.
|
jens@31
|
7 |
//
|
jens@31
|
8 |
|
jens@31
|
9 |
#import "MYBonjourRegistration.h"
|
jens@31
|
10 |
#import "MYBonjourService.h"
|
jens@31
|
11 |
#import "ExceptionUtils.h"
|
jens@31
|
12 |
#import "Test.h"
|
jens@31
|
13 |
#import "Logging.h"
|
jens@31
|
14 |
#import <dns_sd.h>
|
jens@31
|
15 |
|
jens@31
|
16 |
|
jens@31
|
17 |
#define kTXTTTL 60 // TTL in seconds for TXT records I register
|
jens@31
|
18 |
|
jens@31
|
19 |
|
jens@31
|
20 |
@interface MYBonjourRegistration ()
|
jens@31
|
21 |
@property BOOL registered;
|
jens@31
|
22 |
@end
|
jens@31
|
23 |
|
jens@31
|
24 |
|
jens@31
|
25 |
@implementation MYBonjourRegistration
|
jens@31
|
26 |
|
jens@31
|
27 |
|
jens@31
|
28 |
static NSMutableDictionary *sAllRegistrations;
|
jens@31
|
29 |
|
jens@31
|
30 |
|
jens@31
|
31 |
+ (void) priv_addRegistration: (MYBonjourRegistration*)reg {
|
jens@31
|
32 |
if (!sAllRegistrations)
|
jens@31
|
33 |
sAllRegistrations = [[NSMutableDictionary alloc] init];
|
jens@31
|
34 |
[sAllRegistrations setObject: reg forKey: reg.fullName];
|
jens@31
|
35 |
}
|
jens@31
|
36 |
|
jens@31
|
37 |
+ (void) priv_removeRegistration: (MYBonjourRegistration*)reg {
|
jens@31
|
38 |
[sAllRegistrations removeObjectForKey: reg.fullName];
|
jens@31
|
39 |
}
|
jens@31
|
40 |
|
jens@31
|
41 |
+ (MYBonjourRegistration*) registrationWithFullName: (NSString*)fullName {
|
jens@31
|
42 |
return [sAllRegistrations objectForKey: fullName];
|
jens@31
|
43 |
}
|
jens@31
|
44 |
|
jens@31
|
45 |
|
jens@31
|
46 |
- (id) initWithServiceType: (NSString*)serviceType port: (UInt16)port
|
jens@31
|
47 |
{
|
jens@31
|
48 |
self = [super init];
|
jens@31
|
49 |
if (self != nil) {
|
jens@31
|
50 |
self.continuous = YES;
|
jens@31
|
51 |
self.usePrivateConnection = YES; // DNSServiceUpdateRecord doesn't work with shared conn :(
|
jens@31
|
52 |
_type = [serviceType copy];
|
jens@31
|
53 |
_port = port;
|
jens@31
|
54 |
_autoRename = YES;
|
jens@31
|
55 |
}
|
jens@31
|
56 |
return self;
|
jens@31
|
57 |
}
|
jens@31
|
58 |
|
jens@31
|
59 |
- (void) dealloc {
|
jens@31
|
60 |
[_name release];
|
jens@31
|
61 |
[_type release];
|
jens@31
|
62 |
[_domain release];
|
jens@31
|
63 |
[super dealloc];
|
jens@31
|
64 |
}
|
jens@31
|
65 |
|
jens@31
|
66 |
|
jens@31
|
67 |
@synthesize name=_name, type=_type, domain=_domain, port=_port, autoRename=_autoRename;
|
jens@31
|
68 |
@synthesize registered=_registered;
|
jens@31
|
69 |
|
jens@31
|
70 |
|
jens@31
|
71 |
- (NSString*) fullName {
|
jens@31
|
72 |
return [[self class] fullNameOfService: _name ofType: _type inDomain: _domain];
|
jens@31
|
73 |
}
|
jens@31
|
74 |
|
jens@31
|
75 |
|
jens@31
|
76 |
- (BOOL) isSameAsService: (MYBonjourService*)service {
|
jens@31
|
77 |
return _name && _domain && [self.fullName isEqualToString: service.fullName];
|
jens@31
|
78 |
}
|
jens@31
|
79 |
|
jens@31
|
80 |
|
jens@31
|
81 |
- (NSString*) description
|
jens@31
|
82 |
{
|
jens@31
|
83 |
return $sprintf(@"%@['%@'.%@%@]", self.class,_name,_type,_domain);
|
jens@31
|
84 |
}
|
jens@31
|
85 |
|
jens@31
|
86 |
|
jens@31
|
87 |
- (void) priv_registeredAsName: (NSString*)name
|
jens@31
|
88 |
type: (NSString*)regtype
|
jens@31
|
89 |
domain: (NSString*)domain
|
jens@31
|
90 |
{
|
jens@31
|
91 |
if (!$equal(name,_name))
|
jens@31
|
92 |
self.name = name;
|
jens@31
|
93 |
if (!$equal(domain,_domain))
|
jens@31
|
94 |
self.domain = domain;
|
jens@31
|
95 |
LogTo(Bonjour,@"Registered %@", self);
|
jens@31
|
96 |
self.registered = YES;
|
jens@31
|
97 |
[[self class] priv_addRegistration: self];
|
jens@31
|
98 |
}
|
jens@31
|
99 |
|
jens@31
|
100 |
|
jens@31
|
101 |
static void regCallback(DNSServiceRef sdRef,
|
jens@31
|
102 |
DNSServiceFlags flags,
|
jens@31
|
103 |
DNSServiceErrorType errorCode,
|
jens@31
|
104 |
const char *name,
|
jens@31
|
105 |
const char *regtype,
|
jens@31
|
106 |
const char *domain,
|
jens@31
|
107 |
void *context)
|
jens@31
|
108 |
{
|
jens@31
|
109 |
MYBonjourRegistration *reg = context;
|
jens@31
|
110 |
@try{
|
jens@31
|
111 |
if (!errorCode)
|
jens@31
|
112 |
[reg priv_registeredAsName: [NSString stringWithUTF8String: name]
|
jens@31
|
113 |
type: [NSString stringWithUTF8String: regtype]
|
jens@31
|
114 |
domain: [NSString stringWithUTF8String: domain]];
|
jens@31
|
115 |
}catchAndReport(@"MYBonjourRegistration callback");
|
jens@31
|
116 |
[reg gotResponse: errorCode];
|
jens@31
|
117 |
}
|
jens@31
|
118 |
|
jens@31
|
119 |
|
jens@31
|
120 |
- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
|
jens@31
|
121 |
DNSServiceFlags flags = 0;
|
jens@31
|
122 |
if (!_autoRename)
|
jens@31
|
123 |
flags |= kDNSServiceFlagsNoAutoRename;
|
jens@31
|
124 |
NSData *txtData = nil;
|
jens@31
|
125 |
if (_txtRecord)
|
jens@31
|
126 |
txtData = [NSNetService dataFromTXTRecordDictionary: _txtRecord];
|
jens@31
|
127 |
return DNSServiceRegister(sdRefPtr,
|
jens@31
|
128 |
flags,
|
jens@31
|
129 |
0,
|
jens@31
|
130 |
_name.UTF8String, // _name is likely to be nil
|
jens@31
|
131 |
_type.UTF8String,
|
jens@31
|
132 |
_domain.UTF8String, // _domain is most likely nil
|
jens@31
|
133 |
NULL,
|
jens@31
|
134 |
htons(_port),
|
jens@31
|
135 |
txtData.length,
|
jens@31
|
136 |
txtData.bytes,
|
jens@31
|
137 |
®Callback,
|
jens@31
|
138 |
self);
|
jens@31
|
139 |
}
|
jens@31
|
140 |
|
jens@31
|
141 |
|
jens@31
|
142 |
- (void) cancel {
|
jens@31
|
143 |
[super cancel];
|
jens@31
|
144 |
if (_registered) {
|
jens@31
|
145 |
[[self class] priv_removeRegistration: self];
|
jens@31
|
146 |
self.registered = NO;
|
jens@31
|
147 |
}
|
jens@31
|
148 |
}
|
jens@31
|
149 |
|
jens@31
|
150 |
|
jens@47
|
151 |
+ (NSData*) dataFromTXTRecordDictionary: (NSDictionary*)txtDict {
|
jens@47
|
152 |
// First translate any non-NSData values into UTF-8 formatted description data:
|
jens@47
|
153 |
NSMutableDictionary *encodedDict = $mdict();
|
jens@47
|
154 |
for (NSString *key in txtDict) {
|
jens@47
|
155 |
id value = [txtDict objectForKey: key];
|
jens@47
|
156 |
if (![value isKindOfClass: [NSData class]]) {
|
jens@47
|
157 |
value = [[value description] dataUsingEncoding: NSUTF8StringEncoding];
|
jens@47
|
158 |
}
|
jens@47
|
159 |
[encodedDict setObject: value forKey: key];
|
jens@47
|
160 |
}
|
jens@47
|
161 |
return [NSNetService dataFromTXTRecordDictionary: encodedDict];
|
jens@47
|
162 |
}
|
jens@47
|
163 |
|
jens@47
|
164 |
|
jens@31
|
165 |
- (void) updateTxtRecord {
|
jens@31
|
166 |
[NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
|
jens@31
|
167 |
if (self.serviceRef) {
|
jens@47
|
168 |
NSData *data = [[self class] dataFromTXTRecordDictionary: _txtRecord];
|
jens@31
|
169 |
Assert(data!=nil, @"Can't convert dictionary to TXT record");
|
jens@31
|
170 |
DNSServiceErrorType err = DNSServiceUpdateRecord(self.serviceRef,
|
jens@31
|
171 |
NULL,
|
jens@31
|
172 |
0,
|
jens@31
|
173 |
data.length,
|
jens@31
|
174 |
data.bytes,
|
jens@31
|
175 |
kTXTTTL);
|
jens@31
|
176 |
if (err)
|
jens@31
|
177 |
Warn(@"%@ failed to update TXT (err=%i)", self,err);
|
jens@31
|
178 |
else
|
jens@31
|
179 |
LogTo(Bonjour,@"%@ updated TXT to %@", self,data);
|
jens@31
|
180 |
}
|
jens@31
|
181 |
}
|
jens@31
|
182 |
|
jens@31
|
183 |
|
jens@31
|
184 |
- (NSDictionary*) txtRecord {
|
jens@31
|
185 |
return _txtRecord;
|
jens@31
|
186 |
}
|
jens@31
|
187 |
|
jens@31
|
188 |
- (void) setTxtRecord: (NSDictionary*)txtDict {
|
jens@31
|
189 |
if (!$equal(_txtRecord,txtDict)) {
|
jens@31
|
190 |
if (txtDict)
|
jens@31
|
191 |
[_txtRecord setDictionary: txtDict];
|
jens@31
|
192 |
else
|
jens@31
|
193 |
setObj(&_txtRecord,nil);
|
jens@31
|
194 |
[NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
|
jens@31
|
195 |
[self performSelector: @selector(updateTxtRecord) withObject: nil afterDelay: 0.1];
|
jens@31
|
196 |
}
|
jens@31
|
197 |
}
|
jens@31
|
198 |
|
jens@31
|
199 |
- (void) setString: (NSString*)value forTxtKey: (NSString*)key
|
jens@31
|
200 |
{
|
jens@31
|
201 |
NSData *data = [value dataUsingEncoding: NSUTF8StringEncoding];
|
jens@31
|
202 |
if (!$equal(data, [_txtRecord objectForKey: key])) {
|
jens@31
|
203 |
if (data) {
|
jens@31
|
204 |
if (!_txtRecord) _txtRecord = [[NSMutableDictionary alloc] init];
|
jens@31
|
205 |
[_txtRecord setObject: data forKey: key];
|
jens@31
|
206 |
} else
|
jens@31
|
207 |
[_txtRecord removeObjectForKey: key];
|
jens@31
|
208 |
[NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
|
jens@31
|
209 |
[self performSelector: @selector(updateTxtRecord) withObject: nil afterDelay: 0.1];
|
jens@31
|
210 |
}
|
jens@31
|
211 |
}
|
jens@31
|
212 |
|
jens@31
|
213 |
@end
|
jens@31
|
214 |
|
jens@31
|
215 |
|
jens@31
|
216 |
|
jens@31
|
217 |
|
jens@31
|
218 |
#pragma mark -
|
jens@31
|
219 |
#pragma mark TESTING:
|
jens@31
|
220 |
|
jens@31
|
221 |
#if DEBUG
|
jens@31
|
222 |
|
jens@31
|
223 |
#import "MYBonjourQuery.h"
|
jens@31
|
224 |
#import "MYAddressLookup.h"
|
jens@31
|
225 |
|
jens@31
|
226 |
@interface BonjourRegTester : NSObject
|
jens@31
|
227 |
{
|
jens@31
|
228 |
MYBonjourRegistration *_reg;
|
jens@31
|
229 |
BOOL _updating;
|
jens@31
|
230 |
}
|
jens@31
|
231 |
@end
|
jens@31
|
232 |
|
jens@31
|
233 |
@implementation BonjourRegTester
|
jens@31
|
234 |
|
jens@31
|
235 |
- (void) updateTXT {
|
jens@31
|
236 |
NSDictionary *txt = $dict({@"time", $sprintf(@"%.3lf", CFAbsoluteTimeGetCurrent())});
|
jens@31
|
237 |
_reg.txtRecord = txt;
|
jens@31
|
238 |
[self performSelector: @selector(updateTXT) withObject: nil afterDelay: 3.0];
|
jens@31
|
239 |
}
|
jens@31
|
240 |
|
jens@31
|
241 |
- (id) init
|
jens@31
|
242 |
{
|
jens@31
|
243 |
self = [super init];
|
jens@31
|
244 |
if (self != nil) {
|
jens@31
|
245 |
_reg = [[MYBonjourRegistration alloc] initWithServiceType: @"_foo._tcp" port: 12345];
|
jens@31
|
246 |
[_reg addObserver: self forKeyPath: @"registered" options: NSKeyValueObservingOptionNew context: NULL];
|
jens@31
|
247 |
[_reg addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: NULL];
|
jens@31
|
248 |
|
jens@31
|
249 |
[self updateTXT];
|
jens@31
|
250 |
[_reg start];
|
jens@31
|
251 |
}
|
jens@31
|
252 |
return self;
|
jens@31
|
253 |
}
|
jens@31
|
254 |
|
jens@31
|
255 |
- (void) dealloc
|
jens@31
|
256 |
{
|
jens@31
|
257 |
[_reg stop];
|
jens@31
|
258 |
[_reg removeObserver: self forKeyPath: @"registered"];
|
jens@31
|
259 |
[_reg removeObserver: self forKeyPath: @"name"];
|
jens@31
|
260 |
[_reg release];
|
jens@31
|
261 |
[super dealloc];
|
jens@31
|
262 |
}
|
jens@31
|
263 |
|
jens@31
|
264 |
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
jens@31
|
265 |
{
|
jens@31
|
266 |
LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change);
|
jens@31
|
267 |
}
|
jens@31
|
268 |
|
jens@31
|
269 |
@end
|
jens@31
|
270 |
|
jens@31
|
271 |
TestCase(BonjourReg) {
|
jens@31
|
272 |
EnableLogTo(Bonjour,YES);
|
jens@31
|
273 |
EnableLogTo(DNS,YES);
|
jens@31
|
274 |
[NSRunLoop currentRunLoop]; // create runloop
|
jens@31
|
275 |
BonjourRegTester *tester = [[BonjourRegTester alloc] init];
|
jens@31
|
276 |
[[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 15]];
|
jens@31
|
277 |
[tester release];
|
jens@31
|
278 |
}
|
jens@31
|
279 |
|
jens@31
|
280 |
#endif
|
jens@31
|
281 |
|
jens@31
|
282 |
|
jens@31
|
283 |
/*
|
jens@31
|
284 |
Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
|
jens@31
|
285 |
|
jens@31
|
286 |
Redistribution and use in source and binary forms, with or without modification, are permitted
|
jens@31
|
287 |
provided that the following conditions are met:
|
jens@31
|
288 |
|
jens@31
|
289 |
* Redistributions of source code must retain the above copyright notice, this list of conditions
|
jens@31
|
290 |
and the following disclaimer.
|
jens@31
|
291 |
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions
|
jens@31
|
292 |
and the following disclaimer in the documentation and/or other materials provided with the
|
jens@31
|
293 |
distribution.
|
jens@31
|
294 |
|
jens@31
|
295 |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
jens@31
|
296 |
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
jens@31
|
297 |
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
|
jens@31
|
298 |
BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
jens@31
|
299 |
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
jens@31
|
300 |
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
jens@31
|
301 |
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
jens@31
|
302 |
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
jens@31
|
303 |
*/
|