Bonjour/MYBonjourRegistration.m
author morrowa
Thu Jul 02 20:45:11 2009 -0700 (2009-07-02)
changeset 57 998dcacd4983
parent 47 60f2b46d9a3b
child 59 46c7844cb592
permissions -rw-r--r--
Merged Jens' latest changes.
     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     [_txtRecord release];
    64     [super dealloc];
    65 }
    66 
    67 
    68 @synthesize name=_name, type=_type, domain=_domain, port=_port, autoRename=_autoRename;
    69 @synthesize registered=_registered;
    70 
    71 
    72 - (NSString*) fullName {
    73     return [[self class] fullNameOfService: _name ofType: _type inDomain: _domain];
    74 }
    75 
    76 
    77 - (BOOL) isSameAsService: (MYBonjourService*)service {
    78     return _name && _domain && [self.fullName isEqualToString: service.fullName];
    79 }
    80 
    81 
    82 - (NSString*) description
    83 {
    84     return $sprintf(@"%@['%@'.%@%@]", self.class,_name,_type,_domain);
    85 }
    86 
    87 
    88 - (void) priv_registeredAsName: (NSString*)name 
    89                           type: (NSString*)regtype
    90                         domain: (NSString*)domain
    91 {
    92     if (!$equal(name,_name))
    93         self.name = name;
    94     if (!$equal(domain,_domain))
    95         self.domain = domain;
    96     LogTo(Bonjour,@"Registered %@", self);
    97     self.registered = YES;
    98     [[self class] priv_addRegistration: self];
    99 }
   100 
   101 
   102 static void regCallback(DNSServiceRef                       sdRef,
   103                         DNSServiceFlags                     flags,
   104                         DNSServiceErrorType                 errorCode,
   105                         const char                          *name,
   106                         const char                          *regtype,
   107                         const char                          *domain,
   108                         void                                *context)
   109 {
   110     MYBonjourRegistration *reg = context;
   111     @try{
   112         if (!errorCode)
   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];
   118 }
   119 
   120 
   121 - (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
   122     DNSServiceFlags flags = 0;
   123     if (!_autoRename)
   124         flags |= kDNSServiceFlagsNoAutoRename;
   125     NSData *txtData = nil;
   126     if (_txtRecord)
   127         txtData = [NSNetService dataFromTXTRecordDictionary: _txtRecord];
   128     return DNSServiceRegister(sdRefPtr,
   129                               flags,
   130                               0,
   131                               _name.UTF8String,         // _name is likely to be nil
   132                               _type.UTF8String,
   133                               _domain.UTF8String,       // _domain is most likely nil
   134                               NULL,
   135                               htons(_port),
   136                               txtData.length,
   137                               txtData.bytes,
   138                               &regCallback,
   139                               self);
   140 }
   141 
   142 
   143 - (void) cancel {
   144     [super cancel];
   145     if (_registered) {
   146         [[self class] priv_removeRegistration: self];
   147         self.registered = NO;
   148     }
   149 }
   150 
   151 
   152 + (NSData*) dataFromTXTRecordDictionary: (NSDictionary*)txtDict {
   153     if (!txtDict)
   154         return nil;
   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];
   161         }
   162         [encodedDict setObject: value forKey: key];
   163     }
   164     return [NSNetService dataFromTXTRecordDictionary: encodedDict];
   165 }
   166 
   167 
   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,
   174                                                          NULL,
   175                                                          0,
   176                                                          data.length,
   177                                                          data.bytes,
   178                                                          kTXTTTL);
   179         if (err)
   180             Warn(@"%@ failed to update TXT (err=%i)", self,err);
   181         else
   182             LogTo(Bonjour,@"%@ updated TXT to %@", self,data);
   183     }
   184 }
   185 
   186 
   187 - (NSDictionary*) txtRecord {
   188     return _txtRecord;
   189 }
   190 
   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];
   196     }
   197 }
   198 
   199 - (void) setString: (NSString*)value forTxtKey: (NSString*)key
   200 {
   201     NSData *data = [value dataUsingEncoding: NSUTF8StringEncoding];
   202     if (!$equal(data, [_txtRecord objectForKey: key])) {
   203         if (data) {
   204             if (!_txtRecord) _txtRecord = [[NSMutableDictionary alloc] init];
   205             [_txtRecord setObject: data forKey: key];
   206         } else
   207             [_txtRecord removeObjectForKey: key];
   208         [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
   209         [self performSelector: @selector(updateTxtRecord) withObject: nil afterDelay: 0.1];
   210     }
   211 }
   212 
   213 @end
   214 
   215 
   216 
   217 
   218 #pragma mark -
   219 #pragma mark TESTING:
   220 
   221 #if DEBUG
   222 
   223 #import "MYBonjourQuery.h"
   224 #import "MYAddressLookup.h"
   225 
   226 @interface BonjourRegTester : NSObject
   227 {
   228     MYBonjourRegistration *_reg;
   229     BOOL _updating;
   230 }
   231 @end
   232 
   233 @implementation BonjourRegTester
   234 
   235 - (void) updateTXT {
   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];
   240 }
   241 
   242 - (id) init
   243 {
   244     self = [super init];
   245     if (self != nil) {
   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];
   249         
   250         [self updateTXT];
   251         [_reg start];
   252     }
   253     return self;
   254 }
   255 
   256 - (void) dealloc
   257 {
   258     [_reg stop];
   259     [_reg removeObserver: self forKeyPath: @"registered"];
   260     [_reg removeObserver: self forKeyPath: @"name"];
   261     [_reg release];
   262     [super dealloc];
   263 }
   264 
   265 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
   266 {
   267     LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change);
   268 }
   269 
   270 @end
   271 
   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]];
   278     [tester release];
   279 }
   280 
   281 #endif
   282 
   283 
   284 /*
   285  Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   286  
   287  Redistribution and use in source and binary forms, with or without modification, are permitted
   288  provided that the following conditions are met:
   289  
   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
   294  distribution.
   295  
   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.
   304  */