Bonjour/MYBonjourRegistration.m
author Jens Alfke <jens@mooseyard.com>
Sun May 10 19:00:50 2009 -0700 (2009-05-10)
changeset 45 8efb48eabd08
child 47 60f2b46d9a3b
permissions -rw-r--r--
Fixed MYAddressLookup to allocate an NSSet, and to send correct KV notifications. (Based on Jim Roepke's patch, but outsourcing the KV grunge to CollectionUtils.)
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
                              &regCallback,
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@31
   151
- (void) updateTxtRecord {
jens@31
   152
    [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
jens@31
   153
    if (self.serviceRef) {
jens@31
   154
        NSData *data = [NSNetService dataFromTXTRecordDictionary: _txtRecord];
jens@31
   155
        Assert(data!=nil, @"Can't convert dictionary to TXT record");
jens@31
   156
        DNSServiceErrorType err = DNSServiceUpdateRecord(self.serviceRef,
jens@31
   157
                                                         NULL,
jens@31
   158
                                                         0,
jens@31
   159
                                                         data.length,
jens@31
   160
                                                         data.bytes,
jens@31
   161
                                                         kTXTTTL);
jens@31
   162
        if (err)
jens@31
   163
            Warn(@"%@ failed to update TXT (err=%i)", self,err);
jens@31
   164
        else
jens@31
   165
            LogTo(Bonjour,@"%@ updated TXT to %@", self,data);
jens@31
   166
    }
jens@31
   167
}
jens@31
   168
jens@31
   169
jens@31
   170
- (NSDictionary*) txtRecord {
jens@31
   171
    return _txtRecord;
jens@31
   172
}
jens@31
   173
jens@31
   174
- (void) setTxtRecord: (NSDictionary*)txtDict {
jens@31
   175
    if (!$equal(_txtRecord,txtDict)) {
jens@31
   176
        if (txtDict)
jens@31
   177
            [_txtRecord setDictionary: txtDict];
jens@31
   178
        else
jens@31
   179
            setObj(&_txtRecord,nil);
jens@31
   180
        [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
jens@31
   181
        [self performSelector: @selector(updateTxtRecord) withObject: nil afterDelay: 0.1];
jens@31
   182
    }
jens@31
   183
}
jens@31
   184
jens@31
   185
- (void) setString: (NSString*)value forTxtKey: (NSString*)key
jens@31
   186
{
jens@31
   187
    NSData *data = [value dataUsingEncoding: NSUTF8StringEncoding];
jens@31
   188
    if (!$equal(data, [_txtRecord objectForKey: key])) {
jens@31
   189
        if (data) {
jens@31
   190
            if (!_txtRecord) _txtRecord = [[NSMutableDictionary alloc] init];
jens@31
   191
            [_txtRecord setObject: data forKey: key];
jens@31
   192
        } else
jens@31
   193
            [_txtRecord removeObjectForKey: key];
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
@end
jens@31
   200
jens@31
   201
jens@31
   202
jens@31
   203
jens@31
   204
#pragma mark -
jens@31
   205
#pragma mark TESTING:
jens@31
   206
jens@31
   207
#if DEBUG
jens@31
   208
jens@31
   209
#import "MYBonjourQuery.h"
jens@31
   210
#import "MYAddressLookup.h"
jens@31
   211
jens@31
   212
@interface BonjourRegTester : NSObject
jens@31
   213
{
jens@31
   214
    MYBonjourRegistration *_reg;
jens@31
   215
    BOOL _updating;
jens@31
   216
}
jens@31
   217
@end
jens@31
   218
jens@31
   219
@implementation BonjourRegTester
jens@31
   220
jens@31
   221
- (void) updateTXT {
jens@31
   222
    NSDictionary *txt = $dict({@"time", $sprintf(@"%.3lf", CFAbsoluteTimeGetCurrent())});
jens@31
   223
    _reg.txtRecord = txt;
jens@31
   224
    [self performSelector: @selector(updateTXT) withObject: nil afterDelay: 3.0];
jens@31
   225
}
jens@31
   226
jens@31
   227
- (id) init
jens@31
   228
{
jens@31
   229
    self = [super init];
jens@31
   230
    if (self != nil) {
jens@31
   231
        _reg = [[MYBonjourRegistration alloc] initWithServiceType: @"_foo._tcp" port: 12345];
jens@31
   232
        [_reg addObserver: self forKeyPath: @"registered" options: NSKeyValueObservingOptionNew context: NULL];
jens@31
   233
        [_reg addObserver: self forKeyPath: @"name" options: NSKeyValueObservingOptionNew context: NULL];
jens@31
   234
        
jens@31
   235
        [self updateTXT];
jens@31
   236
        [_reg start];
jens@31
   237
    }
jens@31
   238
    return self;
jens@31
   239
}
jens@31
   240
jens@31
   241
- (void) dealloc
jens@31
   242
{
jens@31
   243
    [_reg stop];
jens@31
   244
    [_reg removeObserver: self forKeyPath: @"registered"];
jens@31
   245
    [_reg removeObserver: self forKeyPath: @"name"];
jens@31
   246
    [_reg release];
jens@31
   247
    [super dealloc];
jens@31
   248
}
jens@31
   249
jens@31
   250
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
jens@31
   251
{
jens@31
   252
    LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change);
jens@31
   253
}
jens@31
   254
jens@31
   255
@end
jens@31
   256
jens@31
   257
TestCase(BonjourReg) {
jens@31
   258
    EnableLogTo(Bonjour,YES);
jens@31
   259
    EnableLogTo(DNS,YES);
jens@31
   260
    [NSRunLoop currentRunLoop]; // create runloop
jens@31
   261
    BonjourRegTester *tester = [[BonjourRegTester alloc] init];
jens@31
   262
    [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 15]];
jens@31
   263
    [tester release];
jens@31
   264
}
jens@31
   265
jens@31
   266
#endif
jens@31
   267
jens@31
   268
jens@31
   269
/*
jens@31
   270
 Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@31
   271
 
jens@31
   272
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@31
   273
 provided that the following conditions are met:
jens@31
   274
 
jens@31
   275
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@31
   276
 and the following disclaimer.
jens@31
   277
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@31
   278
 and the following disclaimer in the documentation and/or other materials provided with the
jens@31
   279
 distribution.
jens@31
   280
 
jens@31
   281
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@31
   282
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@31
   283
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@31
   284
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@31
   285
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@31
   286
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@31
   287
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@31
   288
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@31
   289
 */