More work on Bonjour classes. They now support registering services.
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 BonjourBrowser");
55 [_myRegistration cancel];
56 [_myRegistration release];
57 [_serviceType release];
59 [_addServices release];
60 [_rmvServices release];
65 @synthesize 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] initWithName: serviceName
98 interface: interfaceIndex];
99 if ([_myRegistration isSameAsService: service]) {
100 // This is an echo of my own registration, so ignore it
101 LogTo(Bonjour,@"%@ ignoring echo %@", self,service);
105 MYBonjourService *existingService = [_services member: service];
106 if( existingService ) {
107 // Use existing service object instead of creating a new one
109 service = [existingService retain];
112 // Add it to the add/remove sets:
113 NSMutableSet *addTo, *removeFrom;
114 if (flags & kDNSServiceFlagsAdd) {
115 addTo = _addServices;
116 removeFrom = _rmvServices;
118 addTo = _rmvServices;
119 removeFrom = _addServices;
121 if( [removeFrom containsObject: service] )
122 [removeFrom removeObject: service];
124 [addTo addObject: service];
127 // Schedule a (single) call to _updateServiceList:
128 if (!_pendingUpdate) {
129 [self performSelector: @selector(_updateServiceList) withObject: nil afterDelay: 0];
130 _pendingUpdate = YES;
135 - (void) _updateServiceList
138 if( _rmvServices.count ) {
139 [self willChangeValueForKey: @"services"
140 withSetMutation: NSKeyValueMinusSetMutation
141 usingObjects: _rmvServices];
142 [_services minusSet: _rmvServices];
143 [self didChangeValueForKey: @"services"
144 withSetMutation: NSKeyValueMinusSetMutation
145 usingObjects: _rmvServices];
146 [_rmvServices makeObjectsPerformSelector: @selector(removed)];
147 [_rmvServices removeAllObjects];
149 if( _addServices.count ) {
150 [_addServices makeObjectsPerformSelector: @selector(added)];
151 [self willChangeValueForKey: @"services"
152 withSetMutation: NSKeyValueUnionSetMutation
153 usingObjects: _addServices];
154 [_services unionSet: _addServices];
155 [self didChangeValueForKey: @"services"
156 withSetMutation: NSKeyValueUnionSetMutation
157 usingObjects: _addServices];
158 [_addServices removeAllObjects];
163 static void browseCallback (DNSServiceRef sdRef,
164 DNSServiceFlags flags,
165 uint32_t interfaceIndex,
166 DNSServiceErrorType errorCode,
167 const char *serviceName,
169 const char *replyDomain,
172 MYBonjourBrowser *browser = context;
174 //LogTo(Bonjour,@"browseCallback (error=%i, name='%s')", errorCode,serviceName);
176 [browser priv_gotServiceName: [NSString stringWithUTF8String: serviceName]
177 type: [NSString stringWithUTF8String: regtype]
178 domain: [NSString stringWithUTF8String: replyDomain]
179 interface: interfaceIndex
181 }catchAndReport(@"Bonjour");
182 [browser gotResponse: errorCode];
187 [_myRegistration stop];
192 - (MYBonjourRegistration *) myRegistration {
193 if (!_myRegistration)
194 _myRegistration = [[MYBonjourRegistration alloc] initWithServiceType: _serviceType port: 0];
195 return _myRegistration;
204 #pragma mark TESTING:
208 #import "MYBonjourQuery.h"
209 #import "MYAddressLookup.h"
211 @interface BonjourTester : NSObject
213 MYBonjourBrowser *_browser;
217 @implementation BonjourTester
223 _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_presence._tcp"];
224 [_browser addObserver: self forKeyPath: @"services" options: NSKeyValueObservingOptionNew context: NULL];
225 [_browser addObserver: self forKeyPath: @"browsing" options: NSKeyValueObservingOptionNew context: NULL];
228 MYBonjourRegistration *myReg = _browser.myRegistration;
230 Assert([myReg start]);
238 [_browser removeObserver: self forKeyPath: @"services"];
239 [_browser removeObserver: self forKeyPath: @"browsing"];
244 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
246 LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change);
247 if( $equal(keyPath,@"services") ) {
248 if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) {
249 NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey];
250 for( MYBonjourService *service in newServices ) {
251 NSString *hostname = service.hostname; // block till it's resolved
252 Log(@"##### %@ : at %@:%hu, TXT=%@",
253 service, hostname, service.port, service.txtRecord);
254 service.addressLookup.continuous = YES;
255 [service queryForRecord: kDNSServiceType_NULL];
264 EnableLogTo(Bonjour,YES);
265 EnableLogTo(DNS,YES);
266 [NSRunLoop currentRunLoop]; // create runloop
267 BonjourTester *tester = [[BonjourTester alloc] init];
268 [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1500]];
276 Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
278 Redistribution and use in source and binary forms, with or without modification, are permitted
279 provided that the following conditions are met:
281 * Redistributions of source code must retain the above copyright notice, this list of conditions
282 and the following disclaimer.
283 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
284 and the following disclaimer in the documentation and/or other materials provided with the
287 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
288 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
289 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
290 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
291 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
292 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
293 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
294 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.