1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/Bonjour/MYBonjourRegistration.m Mon May 04 23:10:51 2009 -0700
1.3 @@ -0,0 +1,289 @@
1.4 +//
1.5 +// MYBonjourRegistration.m
1.6 +// MYNetwork
1.7 +//
1.8 +// Created by Jens Alfke on 4/27/09.
1.9 +// Copyright 2009 Jens Alfke. All rights reserved.
1.10 +//
1.11 +
1.12 +#import "MYBonjourRegistration.h"
1.13 +#import "MYBonjourService.h"
1.14 +#import "ExceptionUtils.h"
1.15 +#import "Test.h"
1.16 +#import "Logging.h"
1.17 +#import <dns_sd.h>
1.18 +
1.19 +
1.20 +#define kTXTTTL 60 // TTL in seconds for TXT records I register
1.21 +
1.22 +
1.23 +@interface MYBonjourRegistration ()
1.24 +@property BOOL registered;
1.25 +@end
1.26 +
1.27 +
1.28 +@implementation MYBonjourRegistration
1.29 +
1.30 +
1.31 +static NSMutableDictionary *sAllRegistrations;
1.32 +
1.33 +
1.34 ++ (void) priv_addRegistration: (MYBonjourRegistration*)reg {
1.35 + if (!sAllRegistrations)
1.36 + sAllRegistrations = [[NSMutableDictionary alloc] init];
1.37 + [sAllRegistrations setObject: reg forKey: reg.fullName];
1.38 +}
1.39 +
1.40 ++ (void) priv_removeRegistration: (MYBonjourRegistration*)reg {
1.41 + [sAllRegistrations removeObjectForKey: reg.fullName];
1.42 +}
1.43 +
1.44 ++ (MYBonjourRegistration*) registrationWithFullName: (NSString*)fullName {
1.45 + return [sAllRegistrations objectForKey: fullName];
1.46 +}
1.47 +
1.48 +
1.49 +- (id) initWithServiceType: (NSString*)serviceType port: (UInt16)port
1.50 +{
1.51 + self = [super init];
1.52 + if (self != nil) {
1.53 + self.continuous = YES;
1.54 + self.usePrivateConnection = YES; // DNSServiceUpdateRecord doesn't work with shared conn :(
1.55 + _type = [serviceType copy];
1.56 + _port = port;
1.57 + _autoRename = YES;
1.58 + }
1.59 + return self;
1.60 +}
1.61 +
1.62 +- (void) dealloc {
1.63 + [_name release];
1.64 + [_type release];
1.65 + [_domain release];
1.66 + [super dealloc];
1.67 +}
1.68 +
1.69 +
1.70 +@synthesize name=_name, type=_type, domain=_domain, port=_port, autoRename=_autoRename;
1.71 +@synthesize registered=_registered;
1.72 +
1.73 +
1.74 +- (NSString*) fullName {
1.75 + return [[self class] fullNameOfService: _name ofType: _type inDomain: _domain];
1.76 +}
1.77 +
1.78 +
1.79 +- (BOOL) isSameAsService: (MYBonjourService*)service {
1.80 + return _name && _domain && [self.fullName isEqualToString: service.fullName];
1.81 +}
1.82 +
1.83 +
1.84 +- (NSString*) description
1.85 +{
1.86 + return $sprintf(@"%@['%@'.%@%@]", self.class,_name,_type,_domain);
1.87 +}
1.88 +
1.89 +
1.90 +- (void) priv_registeredAsName: (NSString*)name
1.91 + type: (NSString*)regtype
1.92 + domain: (NSString*)domain
1.93 +{
1.94 + if (!$equal(name,_name))
1.95 + self.name = name;
1.96 + if (!$equal(domain,_domain))
1.97 + self.domain = domain;
1.98 + LogTo(Bonjour,@"Registered %@", self);
1.99 + self.registered = YES;
1.100 + [[self class] priv_addRegistration: self];
1.101 +}
1.102 +
1.103 +
1.104 +static void regCallback(DNSServiceRef sdRef,
1.105 + DNSServiceFlags flags,
1.106 + DNSServiceErrorType errorCode,
1.107 + const char *name,
1.108 + const char *regtype,
1.109 + const char *domain,
1.110 + void *context)
1.111 +{
1.112 + MYBonjourRegistration *reg = context;
1.113 + @try{
1.114 + if (!errorCode)
1.115 + [reg priv_registeredAsName: [NSString stringWithUTF8String: name]
1.116 + type: [NSString stringWithUTF8String: regtype]
1.117 + domain: [NSString stringWithUTF8String: domain]];
1.118 + }catchAndReport(@"MYBonjourRegistration callback");
1.119 + [reg gotResponse: errorCode];
1.120 +}
1.121 +
1.122 +
1.123 +- (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
1.124 + DNSServiceFlags flags = 0;
1.125 + if (!_autoRename)
1.126 + flags |= kDNSServiceFlagsNoAutoRename;
1.127 + NSData *txtData = nil;
1.128 + if (_txtRecord)
1.129 + txtData = [NSNetService dataFromTXTRecordDictionary: _txtRecord];
1.130 + return DNSServiceRegister(sdRefPtr,
1.131 + flags,
1.132 + 0,
1.133 + _name.UTF8String, // _name is likely to be nil
1.134 + _type.UTF8String,
1.135 + _domain.UTF8String, // _domain is most likely nil
1.136 + NULL,
1.137 + htons(_port),
1.138 + txtData.length,
1.139 + txtData.bytes,
1.140 + ®Callback,
1.141 + self);
1.142 +}
1.143 +
1.144 +
1.145 +- (void) cancel {
1.146 + [super cancel];
1.147 + if (_registered) {
1.148 + [[self class] priv_removeRegistration: self];
1.149 + self.registered = NO;
1.150 + }
1.151 +}
1.152 +
1.153 +
1.154 +- (void) updateTxtRecord {
1.155 + [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
1.156 + if (self.serviceRef) {
1.157 + NSData *data = [NSNetService dataFromTXTRecordDictionary: _txtRecord];
1.158 + Assert(data!=nil, @"Can't convert dictionary to TXT record");
1.159 + DNSServiceErrorType err = DNSServiceUpdateRecord(self.serviceRef,
1.160 + NULL,
1.161 + 0,
1.162 + data.length,
1.163 + data.bytes,
1.164 + kTXTTTL);
1.165 + if (err)
1.166 + Warn(@"%@ failed to update TXT (err=%i)", self,err);
1.167 + else
1.168 + LogTo(Bonjour,@"%@ updated TXT to %@", self,data);
1.169 + }
1.170 +}
1.171 +
1.172 +
1.173 +- (NSDictionary*) txtRecord {
1.174 + return _txtRecord;
1.175 +}
1.176 +
1.177 +- (void) setTxtRecord: (NSDictionary*)txtDict {
1.178 + if (!$equal(_txtRecord,txtDict)) {
1.179 + if (txtDict)
1.180 + [_txtRecord setDictionary: txtDict];
1.181 + else
1.182 + setObj(&_txtRecord,nil);
1.183 + [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
1.184 + [self performSelector: @selector(updateTxtRecord) withObject: nil afterDelay: 0.1];
1.185 + }
1.186 +}
1.187 +
1.188 +- (void) setString: (NSString*)value forTxtKey: (NSString*)key
1.189 +{
1.190 + NSData *data = [value dataUsingEncoding: NSUTF8StringEncoding];
1.191 + if (!$equal(data, [_txtRecord objectForKey: key])) {
1.192 + if (data) {
1.193 + if (!_txtRecord) _txtRecord = [[NSMutableDictionary alloc] init];
1.194 + [_txtRecord setObject: data forKey: key];
1.195 + } else
1.196 + [_txtRecord removeObjectForKey: key];
1.197 + [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
1.198 + [self performSelector: @selector(updateTxtRecord) withObject: nil afterDelay: 0.1];
1.199 + }
1.200 +}
1.201 +
1.202 +@end
1.203 +
1.204 +
1.205 +
1.206 +
1.207 +#pragma mark -
1.208 +#pragma mark TESTING:
1.209 +
1.210 +#if DEBUG
1.211 +
1.212 +#import "MYBonjourQuery.h"
1.213 +#import "MYAddressLookup.h"
1.214 +
1.215 +@interface BonjourRegTester : NSObject
1.216 +{
1.217 + MYBonjourRegistration *_reg;
1.218 + BOOL _updating;
1.219 +}
1.220 +@end
1.221 +
1.222 +@implementation BonjourRegTester
1.223 +
1.224 +- (void) updateTXT {
1.225 + NSDictionary *txt = $dict({@"time", $sprintf(@"%.3lf", CFAbsoluteTimeGetCurrent())});
1.226 + _reg.txtRecord = txt;
1.227 + [self performSelector: @selector(updateTXT) withObject: nil afterDelay: 3.0];
1.228 +}
1.229 +
1.230 +- (id) init
1.231 +{
1.232 + self = [super init];
1.233 + if (self != nil) {
1.234 + _reg = [[MYBonjourRegistration alloc] initWithServiceType: @"_foo._tcp" port: 12345];
1.235 + [_reg addObserver: self forKeyPath: @"registered" options: NSKeyValueObservingOptionNew context: NULL];
1.236 + [_reg addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: NULL];
1.237 +
1.238 + [self updateTXT];
1.239 + [_reg start];
1.240 + }
1.241 + return self;
1.242 +}
1.243 +
1.244 +- (void) dealloc
1.245 +{
1.246 + [_reg stop];
1.247 + [_reg removeObserver: self forKeyPath: @"registered"];
1.248 + [_reg removeObserver: self forKeyPath: @"name"];
1.249 + [_reg release];
1.250 + [super dealloc];
1.251 +}
1.252 +
1.253 +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
1.254 +{
1.255 + LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change);
1.256 +}
1.257 +
1.258 +@end
1.259 +
1.260 +TestCase(BonjourReg) {
1.261 + EnableLogTo(Bonjour,YES);
1.262 + EnableLogTo(DNS,YES);
1.263 + [NSRunLoop currentRunLoop]; // create runloop
1.264 + BonjourRegTester *tester = [[BonjourRegTester alloc] init];
1.265 + [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 15]];
1.266 + [tester release];
1.267 +}
1.268 +
1.269 +#endif
1.270 +
1.271 +
1.272 +/*
1.273 + Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
1.274 +
1.275 + Redistribution and use in source and binary forms, with or without modification, are permitted
1.276 + provided that the following conditions are met:
1.277 +
1.278 + * Redistributions of source code must retain the above copyright notice, this list of conditions
1.279 + and the following disclaimer.
1.280 + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
1.281 + and the following disclaimer in the documentation and/or other materials provided with the
1.282 + distribution.
1.283 +
1.284 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
1.285 + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
1.286 + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
1.287 + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
1.288 + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
1.289 + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
1.290 + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
1.291 + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1.292 + */