| jens@26 |      1 | //
 | 
| jens@26 |      2 | //  MYBonjourBrowser.m
 | 
| jens@26 |      3 | //  MYNetwork
 | 
| jens@26 |      4 | //
 | 
| jens@26 |      5 | //  Created by Jens Alfke on 1/22/08.
 | 
| jens@26 |      6 | //  Copyright 2008 Jens Alfke. All rights reserved.
 | 
| jens@26 |      7 | //
 | 
| jens@26 |      8 | 
 | 
| jens@26 |      9 | #import "MYBonjourBrowser.h"
 | 
| jens@26 |     10 | #import "MYBonjourService.h"
 | 
| jens@28 |     11 | #import "ExceptionUtils.h"
 | 
| jens@26 |     12 | #import "Test.h"
 | 
| jens@26 |     13 | #import "Logging.h"
 | 
| jens@28 |     14 | #import <dns_sd.h>
 | 
| jens@26 |     15 | 
 | 
| jens@26 |     16 | 
 | 
| jens@28 |     17 | static void browseCallback (DNSServiceRef                       sdRef,
 | 
| jens@28 |     18 |                             DNSServiceFlags                     flags,
 | 
| jens@28 |     19 |                             uint32_t                            interfaceIndex,
 | 
| jens@28 |     20 |                             DNSServiceErrorType                 errorCode,
 | 
| jens@28 |     21 |                             const char                          *serviceName,
 | 
| jens@28 |     22 |                             const char                          *regtype,
 | 
| jens@28 |     23 |                             const char                          *replyDomain,
 | 
| jens@28 |     24 |                             void                                *context);
 | 
| jens@28 |     25 | 
 | 
| jens@26 |     26 | @interface MYBonjourBrowser ()
 | 
| jens@26 |     27 | @property BOOL browsing;
 | 
| jens@28 |     28 | - (void) _updateServiceList;
 | 
| jens@26 |     29 | @end
 | 
| jens@26 |     30 | 
 | 
| jens@26 |     31 | 
 | 
| jens@26 |     32 | @implementation MYBonjourBrowser
 | 
| jens@26 |     33 | 
 | 
| jens@26 |     34 | 
 | 
| jens@26 |     35 | - (id) initWithServiceType: (NSString*)serviceType
 | 
| jens@26 |     36 | {
 | 
| jens@26 |     37 |     Assert(serviceType);
 | 
| jens@26 |     38 |     self = [super init];
 | 
| jens@26 |     39 |     if (self != nil) {
 | 
| jens@28 |     40 |         self.continuous = YES;
 | 
| jens@26 |     41 |         _serviceType = [serviceType copy];
 | 
| jens@26 |     42 |         _services = [[NSMutableSet alloc] init];
 | 
| jens@26 |     43 |         _addServices = [[NSMutableSet alloc] init];
 | 
| jens@26 |     44 |         _rmvServices = [[NSMutableSet alloc] init];
 | 
| jens@26 |     45 |         _serviceClass = [MYBonjourService class];
 | 
| jens@26 |     46 |     }
 | 
| jens@26 |     47 |     return self;
 | 
| jens@26 |     48 | }
 | 
| jens@26 |     49 | 
 | 
| jens@26 |     50 | 
 | 
| jens@26 |     51 | - (void) dealloc
 | 
| jens@26 |     52 | {
 | 
| jens@26 |     53 |     LogTo(Bonjour,@"DEALLOC BonjourBrowser");
 | 
| jens@26 |     54 |     [_serviceType release];
 | 
| jens@26 |     55 |     [_services release];
 | 
| jens@26 |     56 |     [_addServices release];
 | 
| jens@26 |     57 |     [_rmvServices release];
 | 
| jens@26 |     58 |     [super dealloc];
 | 
| jens@26 |     59 | }
 | 
| jens@26 |     60 | 
 | 
| jens@26 |     61 | 
 | 
| jens@28 |     62 | @synthesize browsing=_browsing, services=_services, serviceClass=_serviceClass;
 | 
| jens@26 |     63 | 
 | 
| jens@26 |     64 | 
 | 
| jens@28 |     65 | - (NSString*) description
 | 
| jens@26 |     66 | {
 | 
| jens@28 |     67 |     return $sprintf(@"%@[%@]", self.class,_serviceType);
 | 
| jens@26 |     68 | }
 | 
| jens@26 |     69 | 
 | 
| jens@28 |     70 | 
 | 
| jens@28 |     71 | - (DNSServiceRef) createServiceRef {
 | 
| jens@28 |     72 |     DNSServiceRef serviceRef = NULL;
 | 
| jens@28 |     73 |     self.error = DNSServiceBrowse(&serviceRef, 0, 0,
 | 
| jens@28 |     74 |                                   _serviceType.UTF8String, NULL,
 | 
| jens@28 |     75 |                                   &browseCallback, self);
 | 
| jens@28 |     76 |     return serviceRef;
 | 
| jens@26 |     77 | }
 | 
| jens@26 |     78 | 
 | 
| jens@26 |     79 | 
 | 
| jens@28 |     80 | - (void) priv_gotError: (DNSServiceErrorType)errorCode {
 | 
| jens@28 |     81 |     LogTo(Bonjour,@"%@ got error %i", self,errorCode);
 | 
| jens@28 |     82 |     self.error = errorCode;
 | 
| jens@26 |     83 | }
 | 
| jens@26 |     84 | 
 | 
| jens@28 |     85 | - (void) priv_gotServiceName: (NSString*)serviceName
 | 
| jens@28 |     86 |                         type: (NSString*)regtype
 | 
| jens@28 |     87 |                       domain: (NSString*)domain
 | 
| jens@28 |     88 |                    interface: (uint32_t)interfaceIndex
 | 
| jens@28 |     89 |                        flags: (DNSServiceFlags)flags
 | 
| jens@26 |     90 | {
 | 
| jens@28 |     91 |     // Create (or reuse existing) MYBonjourService object:
 | 
| jens@28 |     92 |     MYBonjourService *service = [[_serviceClass alloc] initWithName: serviceName
 | 
| jens@28 |     93 |                                                                type: regtype
 | 
| jens@28 |     94 |                                                              domain: domain
 | 
| jens@28 |     95 |                                                           interface: interfaceIndex];
 | 
| jens@28 |     96 |     MYBonjourService *existingService = [_services member: service];
 | 
| jens@28 |     97 |     if( existingService ) {
 | 
| jens@28 |     98 |         [service release];
 | 
| jens@28 |     99 |         service = [existingService retain];
 | 
| jens@28 |    100 |     }
 | 
| jens@28 |    101 |     
 | 
| jens@28 |    102 |     // Add it to the add/remove sets:
 | 
| jens@28 |    103 |     NSMutableSet *addTo, *removeFrom;
 | 
| jens@28 |    104 |     if (flags & kDNSServiceFlagsAdd) {
 | 
| jens@28 |    105 |         addTo = _addServices;
 | 
| jens@28 |    106 |         removeFrom = _rmvServices;
 | 
| jens@28 |    107 |     } else {
 | 
| jens@28 |    108 |         addTo = _rmvServices;
 | 
| jens@28 |    109 |         removeFrom = _addServices;
 | 
| jens@28 |    110 |     }
 | 
| jens@28 |    111 |     if( [removeFrom containsObject: service] )
 | 
| jens@28 |    112 |         [removeFrom removeObject: service];
 | 
| jens@28 |    113 |     else
 | 
| jens@28 |    114 |         [addTo addObject: service];
 | 
| jens@28 |    115 |     [service release];
 | 
| jens@28 |    116 |     
 | 
| jens@28 |    117 |     // After a round of updates is done, do the update:
 | 
| jens@28 |    118 |     if( ! (flags & kDNSServiceFlagsMoreComing) )
 | 
| jens@28 |    119 |         [self _updateServiceList];
 | 
| jens@26 |    120 | }
 | 
| jens@26 |    121 | 
 | 
| jens@26 |    122 | 
 | 
| jens@26 |    123 | - (void) _updateServiceList
 | 
| jens@26 |    124 | {
 | 
| jens@26 |    125 |     if( _rmvServices.count ) {
 | 
| jens@26 |    126 |         [self willChangeValueForKey: @"services" 
 | 
| jens@26 |    127 |                     withSetMutation: NSKeyValueMinusSetMutation
 | 
| jens@26 |    128 |                        usingObjects: _rmvServices];
 | 
| jens@26 |    129 |         [_services minusSet: _rmvServices];
 | 
| jens@26 |    130 |         [self didChangeValueForKey: @"services" 
 | 
| jens@26 |    131 |                    withSetMutation: NSKeyValueMinusSetMutation
 | 
| jens@26 |    132 |                       usingObjects: _rmvServices];
 | 
| jens@26 |    133 |         [_rmvServices makeObjectsPerformSelector: @selector(removed)];
 | 
| jens@26 |    134 |         [_rmvServices removeAllObjects];
 | 
| jens@26 |    135 |     }
 | 
| jens@26 |    136 |     if( _addServices.count ) {
 | 
| jens@26 |    137 |         [_addServices makeObjectsPerformSelector: @selector(added)];
 | 
| jens@26 |    138 |         [self willChangeValueForKey: @"services" 
 | 
| jens@26 |    139 |                     withSetMutation: NSKeyValueUnionSetMutation
 | 
| jens@26 |    140 |                        usingObjects: _addServices];
 | 
| jens@26 |    141 |         [_services unionSet: _addServices];
 | 
| jens@26 |    142 |         [self didChangeValueForKey: @"services" 
 | 
| jens@26 |    143 |                    withSetMutation: NSKeyValueUnionSetMutation
 | 
| jens@26 |    144 |                       usingObjects: _addServices];
 | 
| jens@26 |    145 |         [_addServices removeAllObjects];
 | 
| jens@26 |    146 |     }
 | 
| jens@26 |    147 | }
 | 
| jens@26 |    148 | 
 | 
| jens@26 |    149 | 
 | 
| jens@28 |    150 | static void browseCallback (DNSServiceRef                       sdRef,
 | 
| jens@28 |    151 |                             DNSServiceFlags                     flags,
 | 
| jens@28 |    152 |                             uint32_t                            interfaceIndex,
 | 
| jens@28 |    153 |                             DNSServiceErrorType                 errorCode,
 | 
| jens@28 |    154 |                             const char                          *serviceName,
 | 
| jens@28 |    155 |                             const char                          *regtype,
 | 
| jens@28 |    156 |                             const char                          *replyDomain,
 | 
| jens@28 |    157 |                             void                                *context)
 | 
| jens@26 |    158 | {
 | 
| jens@28 |    159 |     @try{
 | 
| jens@28 |    160 |         //LogTo(Bonjour,@"browseCallback (error=%i, name='%s')", errorCode,serviceName);
 | 
| jens@28 |    161 |         if (errorCode)
 | 
| jens@28 |    162 |             [(MYBonjourBrowser*)context priv_gotError: errorCode];
 | 
| jens@28 |    163 |         else
 | 
| jens@28 |    164 |             [(MYBonjourBrowser*)context priv_gotServiceName: [NSString stringWithUTF8String: serviceName]
 | 
| jens@28 |    165 |                                                        type: [NSString stringWithUTF8String: regtype]
 | 
| jens@28 |    166 |                                                      domain: [NSString stringWithUTF8String: replyDomain]
 | 
| jens@28 |    167 |                                                   interface: interfaceIndex
 | 
| jens@28 |    168 |                                                       flags: flags];
 | 
| jens@28 |    169 |     }catchAndReport(@"Bonjour");
 | 
| jens@26 |    170 | }
 | 
| jens@26 |    171 | 
 | 
| jens@26 |    172 | 
 | 
| jens@26 |    173 | @end
 | 
| jens@26 |    174 | 
 | 
| jens@26 |    175 | 
 | 
| jens@26 |    176 | 
 | 
| jens@26 |    177 | #pragma mark -
 | 
| jens@26 |    178 | #pragma mark TESTING:
 | 
| jens@26 |    179 | 
 | 
| jens@28 |    180 | #if DEBUG
 | 
| jens@28 |    181 | 
 | 
| jens@28 |    182 | #import "MYBonjourQuery.h"
 | 
| jens@28 |    183 | #import "MYAddressLookup.h"
 | 
| jens@28 |    184 | 
 | 
| jens@26 |    185 | @interface BonjourTester : NSObject
 | 
| jens@26 |    186 | {
 | 
| jens@26 |    187 |     MYBonjourBrowser *_browser;
 | 
| jens@26 |    188 | }
 | 
| jens@26 |    189 | @end
 | 
| jens@26 |    190 | 
 | 
| jens@26 |    191 | @implementation BonjourTester
 | 
| jens@26 |    192 | 
 | 
| jens@26 |    193 | - (id) init
 | 
| jens@26 |    194 | {
 | 
| jens@26 |    195 |     self = [super init];
 | 
| jens@26 |    196 |     if (self != nil) {
 | 
| jens@28 |    197 |         _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_presence._tcp"];
 | 
| jens@26 |    198 |         [_browser addObserver: self forKeyPath: @"services" options: NSKeyValueObservingOptionNew context: NULL];
 | 
| jens@26 |    199 |         [_browser addObserver: self forKeyPath: @"browsing" options: NSKeyValueObservingOptionNew context: NULL];
 | 
| jens@26 |    200 |         [_browser start];
 | 
| jens@26 |    201 |     }
 | 
| jens@26 |    202 |     return self;
 | 
| jens@26 |    203 | }
 | 
| jens@26 |    204 | 
 | 
| jens@26 |    205 | - (void) dealloc
 | 
| jens@26 |    206 | {
 | 
| jens@26 |    207 |     [_browser stop];
 | 
| jens@26 |    208 |     [_browser removeObserver: self forKeyPath: @"services"];
 | 
| jens@26 |    209 |     [_browser removeObserver: self forKeyPath: @"browsing"];
 | 
| jens@26 |    210 |     [_browser release];
 | 
| jens@26 |    211 |     [super dealloc];
 | 
| jens@26 |    212 | }
 | 
| jens@26 |    213 | 
 | 
| jens@26 |    214 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
 | 
| jens@26 |    215 | {
 | 
| jens@26 |    216 |     LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change);
 | 
| jens@26 |    217 |     if( $equal(keyPath,@"services") ) {
 | 
| jens@26 |    218 |         if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) {
 | 
| jens@26 |    219 |             NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey];
 | 
| jens@26 |    220 |             for( MYBonjourService *service in newServices ) {
 | 
| jens@28 |    221 |                 NSString *hostname = service.hostname;  // block till it's resolved
 | 
| jens@28 |    222 |                 Log(@"##### %@ : at %@:%hu, TXT=%@", 
 | 
| jens@28 |    223 |                       service, hostname, service.port, service.txtRecord);
 | 
| jens@28 |    224 |                 service.addressLookup.continuous = YES;
 | 
| jens@28 |    225 |                 [service queryForRecord: kDNSServiceType_NULL];
 | 
| jens@26 |    226 |             }
 | 
| jens@26 |    227 |         }
 | 
| jens@26 |    228 |     }
 | 
| jens@26 |    229 | }
 | 
| jens@26 |    230 | 
 | 
| jens@26 |    231 | @end
 | 
| jens@26 |    232 | 
 | 
| jens@26 |    233 | TestCase(Bonjour) {
 | 
| jens@28 |    234 |     EnableLogTo(Bonjour,YES);
 | 
| jens@28 |    235 |     EnableLogTo(DNS,YES);
 | 
| jens@26 |    236 |     [NSRunLoop currentRunLoop]; // create runloop
 | 
| jens@26 |    237 |     BonjourTester *tester = [[BonjourTester alloc] init];
 | 
| jens@28 |    238 |     [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1500]];
 | 
| jens@26 |    239 |     [tester release];
 | 
| jens@26 |    240 | }
 | 
| jens@26 |    241 | 
 | 
| jens@28 |    242 | #endif
 | 
| jens@26 |    243 | 
 | 
| jens@26 |    244 | 
 | 
| jens@26 |    245 | /*
 | 
| jens@26 |    246 |  Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
 | 
| jens@26 |    247 |  
 | 
| jens@26 |    248 |  Redistribution and use in source and binary forms, with or without modification, are permitted
 | 
| jens@26 |    249 |  provided that the following conditions are met:
 | 
| jens@26 |    250 |  
 | 
| jens@26 |    251 |  * Redistributions of source code must retain the above copyright notice, this list of conditions
 | 
| jens@26 |    252 |  and the following disclaimer.
 | 
| jens@26 |    253 |  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
 | 
| jens@26 |    254 |  and the following disclaimer in the documentation and/or other materials provided with the
 | 
| jens@26 |    255 |  distribution.
 | 
| jens@26 |    256 |  
 | 
| jens@26 |    257 |  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 | 
| jens@26 |    258 |  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
 | 
| jens@26 |    259 |  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
 | 
| jens@26 |    260 |  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 | 
| jens@26 |    261 |  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 | 
| jens@26 |    262 |   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 | 
| jens@26 |    263 |  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
 | 
| jens@26 |    264 |  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
| jens@26 |    265 |  */
 |