5 // Created by Jens Alfke on 1/22/08.
6 // Copyright 2008 Jens Alfke. All rights reserved.
9 #import "MYBonjourBrowser.h"
10 #import "MYBonjourService.h"
11 #import "MYBonjourRegistration.h"
12 #import "ExceptionUtils.h"
18 static void browseCallback (DNSServiceRef sdRef,
19 DNSServiceFlags flags,
20 uint32_t interfaceIndex,
21 DNSServiceErrorType errorCode,
22 const char *serviceName,
24 const char *replyDomain,
27 @interface MYBonjourBrowser ()
28 @property BOOL browsing;
29 - (void) _updateServiceList;
33 @implementation MYBonjourBrowser
36 - (id) initWithServiceType: (NSString*)serviceType
41 self.continuous = YES;
42 _serviceType = [serviceType copy];
43 _services = [[NSMutableSet alloc] init];
44 _addServices = [[NSMutableSet alloc] init];
45 _rmvServices = [[NSMutableSet alloc] init];
46 _serviceClass = [MYBonjourService class];
54 LogTo(Bonjour,@"DEALLOC MYBonjourBrowser");
55 [_myRegistration cancel];
56 [_myRegistration release];
57 [_serviceType release];
59 [_addServices release];
60 [_rmvServices release];
65 @synthesize delegate=_delegate, browsing=_browsing, services=_services, serviceClass=_serviceClass;
68 - (NSString*) description
70 return $sprintf(@"%@[%@]", self.class,_serviceType);
74 - (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
75 return DNSServiceBrowse(sdRefPtr,
76 kDNSServiceFlagsShareConnection,
78 _serviceType.UTF8String, NULL,
79 &browseCallback, self);
83 - (void) priv_gotError: (DNSServiceErrorType)errorCode {
84 LogTo(Bonjour,@"%@ got error %i", self,errorCode);
85 self.error = errorCode;
88 - (void) priv_gotServiceName: (NSString*)serviceName
89 type: (NSString*)regtype
90 domain: (NSString*)domain
91 interface: (uint32_t)interfaceIndex
92 flags: (DNSServiceFlags)flags
94 // Create (or reuse existing) MYBonjourService object:
95 MYBonjourService *service = [[_serviceClass alloc] initWithBrowser: self
99 interface: interfaceIndex];
100 if ([_myRegistration isSameAsService: service]) {
101 // This is an echo of my own registration, so ignore it
102 LogTo(Bonjour,@"%@ ignoring echo %@", self,service);
106 MYBonjourService *existingService = [_services member: service];
107 if( existingService ) {
108 // Use existing service object instead of creating a new one
110 service = [existingService retain];
113 // Add it to the add/remove sets:
114 NSMutableSet *addTo, *removeFrom;
115 if (flags & kDNSServiceFlagsAdd) {
116 addTo = _addServices;
117 removeFrom = _rmvServices;
119 addTo = _rmvServices;
120 removeFrom = _addServices;
122 if( [removeFrom containsObject: service] )
123 [removeFrom removeObject: service];
125 [addTo addObject: service];
128 // Schedule a (single) call to _updateServiceList:
129 if (!_pendingUpdate) {
130 [self performSelector: @selector(_updateServiceList) withObject: nil afterDelay: 0];
131 _pendingUpdate = YES;
136 - (void) _updateServiceList
139 if( _rmvServices.count ) {
140 [self willChangeValueForKey: @"services"
141 withSetMutation: NSKeyValueMinusSetMutation
142 usingObjects: _rmvServices];
143 [_services minusSet: _rmvServices];
144 [self didChangeValueForKey: @"services"
145 withSetMutation: NSKeyValueMinusSetMutation
146 usingObjects: _rmvServices];
147 [_rmvServices makeObjectsPerformSelector: @selector(removed)];
148 [_rmvServices removeAllObjects];
150 if( _addServices.count ) {
151 [_addServices makeObjectsPerformSelector: @selector(added)];
152 [self willChangeValueForKey: @"services"
153 withSetMutation: NSKeyValueUnionSetMutation
154 usingObjects: _addServices];
155 [_services unionSet: _addServices];
156 [self didChangeValueForKey: @"services"
157 withSetMutation: NSKeyValueUnionSetMutation
158 usingObjects: _addServices];
159 [_addServices removeAllObjects];
164 static void browseCallback (DNSServiceRef sdRef,
165 DNSServiceFlags flags,
166 uint32_t interfaceIndex,
167 DNSServiceErrorType errorCode,
168 const char *serviceName,
170 const char *replyDomain,
173 MYBonjourBrowser *browser = context;
175 LogTo(Bonjour,@"browseCallback (error=%i, name='%s', intf=%u)", errorCode,serviceName,interfaceIndex);
177 [browser priv_gotServiceName: [NSString stringWithUTF8String: serviceName]
178 type: [NSString stringWithUTF8String: regtype]
179 domain: [NSString stringWithUTF8String: replyDomain]
180 interface: interfaceIndex
182 }catchAndReport(@"Bonjour");
183 [browser gotResponse: errorCode];
188 [_myRegistration stop];
193 - (MYBonjourRegistration *) myRegistration {
194 if (!_myRegistration)
195 _myRegistration = [[MYBonjourRegistration alloc] initWithServiceType: _serviceType port: 0];
196 return _myRegistration;
206 #pragma mark TESTING:
210 #import "MYBonjourQuery.h"
211 #import "MYAddressLookup.h"
213 @interface BonjourTester : NSObject
215 MYBonjourBrowser *_browser;
219 @implementation BonjourTester
225 _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_presence._tcp"];
226 [_browser addObserver: self forKeyPath: @"services"
227 options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
229 [_browser addObserver: self forKeyPath: @"browsing"
230 options: NSKeyValueObservingOptionNew
234 MYBonjourRegistration *myReg = _browser.myRegistration;
236 Assert([myReg start]);
244 [_browser removeObserver: self forKeyPath: @"services"];
245 [_browser removeObserver: self forKeyPath: @"browsing"];
250 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
252 Log(@"Observed change in %@: %@",keyPath,change);
253 if( $equal(keyPath,@"services") ) {
254 if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) {
255 NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey];
256 for( MYBonjourService *service in newServices ) {
257 Log(@"##### %@ : at %@:%hu, TXT=%@",
258 service, service.hostname, service.port, service.txtRecord);
259 service.addressLookup.continuous = YES;
260 [service.addressLookup addObserver: self
261 forKeyPath: @"addresses"
262 options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
264 [service queryForRecord: kDNSServiceType_NULL];
266 } else if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeRemoval ) {
267 NSSet *oldServices = [change objectForKey: NSKeyValueChangeOldKey];
268 for( MYBonjourService *service in oldServices ) {
269 Log(@"##### REMOVED: %@", service);
270 [service.addressLookup removeObserver: self forKeyPath: @"addresses"];
279 EnableLogTo(Bonjour,YES);
280 EnableLogTo(DNS,YES);
281 [NSRunLoop currentRunLoop]; // create runloop
282 BonjourTester *tester = [[BonjourTester alloc] init];
283 [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1500]];
291 Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
293 Redistribution and use in source and binary forms, with or without modification, are permitted
294 provided that the following conditions are met:
296 * Redistributions of source code must retain the above copyright notice, this list of conditions
297 and the following disclaimer.
298 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
299 and the following disclaimer in the documentation and/or other materials provided with the
302 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
303 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
304 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
305 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
306 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
307 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
308 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
309 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.