Bonjour/MYBonjourRegistration.m
author morrowa
Thu Jul 02 19:58:11 2009 -0700 (2009-07-02)
changeset 56 6c3b5372a307
parent 31 1d6924779df7
child 50 63baa74c903f
permissions -rw-r--r--
Removed unnecessary files. Toned down logging. Added null logging handler to BLIP so client code doesn't have to use logging. Modified test drivers to work against Cocoa versions.
     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 + (NSData*) dataFromTXTRecordDictionary: (NSDictionary*)txtDict {
   152     // First translate any non-NSData values into UTF-8 formatted description data:
   153     NSMutableDictionary *encodedDict = $mdict();
   154     for (NSString *key in txtDict) {
   155         id value = [txtDict objectForKey: key];
   156         if (![value isKindOfClass: [NSData class]]) {
   157             value = [[value description] dataUsingEncoding: NSUTF8StringEncoding];
   158         }
   159         [encodedDict setObject: value forKey: key];
   160     }
   161     return [NSNetService dataFromTXTRecordDictionary: encodedDict];
   162 }
   163 
   164 
   165 - (void) updateTxtRecord {
   166     [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
   167     if (self.serviceRef) {
   168         NSData *data = [[self class] dataFromTXTRecordDictionary: _txtRecord];
   169         Assert(data!=nil, @"Can't convert dictionary to TXT record");
   170         DNSServiceErrorType err = DNSServiceUpdateRecord(self.serviceRef,
   171                                                          NULL,
   172                                                          0,
   173                                                          data.length,
   174                                                          data.bytes,
   175                                                          kTXTTTL);
   176         if (err)
   177             Warn(@"%@ failed to update TXT (err=%i)", self,err);
   178         else
   179             LogTo(Bonjour,@"%@ updated TXT to %@", self,data);
   180     }
   181 }
   182 
   183 
   184 - (NSDictionary*) txtRecord {
   185     return _txtRecord;
   186 }
   187 
   188 - (void) setTxtRecord: (NSDictionary*)txtDict {
   189     if (!$equal(_txtRecord,txtDict)) {
   190         if (txtDict)
   191             [_txtRecord setDictionary: txtDict];
   192         else
   193             setObj(&_txtRecord,nil);
   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     [self performSelector: @selector(updateTXT) withObject: nil afterDelay: 3.0];
   239 }
   240 
   241 - (id) init
   242 {
   243     self = [super init];
   244     if (self != nil) {
   245         _reg = [[MYBonjourRegistration alloc] initWithServiceType: @"_foo._tcp" port: 12345];
   246         [_reg addObserver: self forKeyPath: @"registered" options: NSKeyValueObservingOptionNew context: NULL];
   247         [_reg addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: NULL];
   248         
   249         [self updateTXT];
   250         [_reg start];
   251     }
   252     return self;
   253 }
   254 
   255 - (void) dealloc
   256 {
   257     [_reg stop];
   258     [_reg removeObserver: self forKeyPath: @"registered"];
   259     [_reg removeObserver: self forKeyPath: @"name"];
   260     [_reg release];
   261     [super dealloc];
   262 }
   263 
   264 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
   265 {
   266     LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change);
   267 }
   268 
   269 @end
   270 
   271 TestCase(BonjourReg) {
   272     EnableLogTo(Bonjour,YES);
   273     EnableLogTo(DNS,YES);
   274     [NSRunLoop currentRunLoop]; // create runloop
   275     BonjourRegTester *tester = [[BonjourRegTester alloc] init];
   276     [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 15]];
   277     [tester release];
   278 }
   279 
   280 #endif
   281 
   282 
   283 /*
   284  Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   285  
   286  Redistribution and use in source and binary forms, with or without modification, are permitted
   287  provided that the following conditions are met:
   288  
   289  * Redistributions of source code must retain the above copyright notice, this list of conditions
   290  and the following disclaimer.
   291  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   292  and the following disclaimer in the documentation and/or other materials provided with the
   293  distribution.
   294  
   295  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   296  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   297  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   298  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   299  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   300   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   301  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   302  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   303  */