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