If Bonjour disconnects but the user opens a contact window in the moment before the contact disappears from the list, it wass possible to attempt to open with a nil avInstanceName. Furthermore, if Bonjour disconnects, attempting to send a message to a still-open Bonjour chat could try to reference avInstanceName which would previously be nil after disconnect.
There's no reason to be so aggressive with having avInstanceName only be set while logged in; it's a single string, and it doesn't generally change. We'll create it in init and destroy it in dealloc for AWEzvContactManager.
Fixes #11799 fully.
3 * File: AWEzvContactManagerRendezvous.m
6 * Author: Andrew Wellington <proton[at]wiretapped.net>
9 * Copyright (C) 2004-2007 Andrew Wellington.
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions are met:
15 * 1. Redistributions of source code must retain the above copyright notice,
16 * this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright notice,
18 * this list of conditions and the following disclaimer in the documentation
19 * and/or other materials provided with the distribution.
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
22 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
23 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
24 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
27 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
29 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
30 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 #import "AWEzvContactManager.h"
34 #import "AWEzvContactManagerRendezvous.h"
36 #import "AWEzvPrivate.h"
37 #import "AWEzvContact.h"
38 #import "AWEzvContactPrivate.h"
39 #import "AWEzvRendezvousData.h"
41 #import "AWEzvSupportRoutines.h"
45 #import <openssl/sha.h>
47 /* One of the stupidest things I've ever met. Doing DNS lookups using the standard
48 * functions does not for mDNS records work unless you're in BIND 8 compatibility
49 * mode. And of course how do you get data from say a NULL record for iChat stuff?
50 * With the standard DNS functions. So we have to use BIND 8 mode. Which means we
51 * have to implement our own DNS packet parser. What were people thinking here?
53 #define BIND_8_COMPAT 1
55 #import <sys/socket.h>
56 #import <netinet/in.h>
57 #import <arpa/nameser.h>
66 #import <SystemConfiguration/SystemConfiguration.h>
67 // The ServiceController manages cleanup of DNSServiceRef & runloop info for an outstanding request
68 @interface ServiceController : NSObject
70 DNSServiceRef fServiceRef;
71 CFSocketRef fSocketRef;
72 CFRunLoopSourceRef fRunloopSrc;
73 AWEzvContactManager *contactManager;
76 - (id)initWithServiceRef:(DNSServiceRef)ref forContactManager:(AWEzvContactManager *)inContactManager;
77 - (boolean_t)addToCurrentRunLoop;
78 - (void)breakdownServiceController;
79 - (DNSServiceRef)serviceRef;
80 @property (readonly, nonatomic) AWEzvContactManager *contactManager;
82 @end // interface ServiceController
84 /* C-helper function prototypes */
87 DNSServiceFlags flags,
88 DNSServiceErrorType errorCode,
94 static void ProcessSockData (
96 CFSocketCallBackType callbackType,
102 void handle_av_browse_reply (
104 DNSServiceFlags flags,
105 uint32_t interfaceIndex,
106 DNSServiceErrorType errorCode,
107 const char *serviceName,
109 const char *replyDomain,
114 DNSServiceFlags flags,
115 uint32_t interfaceIndex,
116 DNSServiceErrorType errorCode,
117 const char *fullname,
118 const char *hosttarget,
121 const unsigned char *txtRecord,
124 void AddressQueryRecordReply( DNSServiceRef DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex,
125 DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass,
126 uint16_t rdlen, const void *rdata, uint32_t ttl, void *context );
128 void ImageQueryRecordReply( DNSServiceRef DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex,
129 DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass,
130 uint16_t rdlen, const void *rdata, uint32_t ttl, void *context );
132 void image_register_reply (
134 DNSRecordRef RecordRef,
135 DNSServiceFlags flags,
136 DNSServiceErrorType errorCode,
139 @implementation AWEzvContactManager (Rendezvous)
140 #pragma mark Announcing Functions
144 /* create data structure we'll advertise with */
145 userAnnounceData = [[AWEzvRendezvousData alloc] init];
146 /* set field contents of the data */
147 [userAnnounceData setField:@"1st" content:[client name]];
148 [userAnnounceData setField:@"email" content:@""];
149 [userAnnounceData setField:@"ext" content:@""];
150 [userAnnounceData setField:@"jid" content:@""];
151 [userAnnounceData setField:@"last" content:@""];
152 [userAnnounceData setField:@"msg" content:@""];
153 [userAnnounceData setField:@"nick" content:@""];
154 [userAnnounceData setField:@"node" content:@""];
155 [userAnnounceData setField:@"AIM" content:@""];
156 [userAnnounceData setField:@"email" content:@""];
157 [userAnnounceData setField:@"port.p2pj" content:[NSString stringWithFormat:@"%u", port]];
158 [userAnnounceData setField:@"txtvers" content:@"1"];
159 [userAnnounceData setField:@"version" content:@"1"];
161 [self setStatus:[client status] withMessage:nil];
163 /* register service with mDNSResponder */
165 DNSServiceRef servRef;
166 DNSServiceErrorType dnsError;
168 TXTRecordRef txtRecord;
169 txtRecord = [userAnnounceData dataAsTXTRecordRef];
171 dnsError = DNSServiceRegister(
172 /* uninitialized service discovery reference */ &servRef,
173 /* flags indicating how to handle name conflicts */ /* kDNSServiceFlagsNoAutoRename */ 0,
174 /* interface on which to register, 0 for all available */ 0,
175 /* service's name, may be null */ [avInstanceName UTF8String],
176 /* service registration type */ "_presence._tcp",
177 /* domain, may be NULL */ NULL,
178 /* SRV target host name, may be NULL */ NULL,
179 /* port number in network byte order */ htons(port),
180 /* length of txt record in bytes, 0 for NULL txt record */ TXTRecordGetLength(&txtRecord) ,
181 /* txt record properly formatted, may be NULL */ TXTRecordGetBytesPtr(&txtRecord) ,
182 /* call back function, may be NULL */ register_reply,
183 /* application context pointer, may be null */ self);
185 if (dnsError == kDNSServiceErr_NoError) {
186 fDomainBrowser = [[ServiceController alloc] initWithServiceRef:servRef forContactManager:self];
187 [fDomainBrowser addToCurrentRunLoop];
188 avDNSReference = servRef;
190 [[client client] reportError:@"Could not register DNS service: _presence._tcp" ofLevel:AWEzvConnectionError];
193 TXTRecordDeallocate(&txtRecord);
196 /* this is used for a clean logout */
201 /* this causes an actual disconnect */
202 - (void) disconnect {
203 AILogWithSignature(@"Disconnecting");
205 [fServiceBrowser release]; fServiceBrowser = nil;
207 /* Remove Resolvers, this also deallocates the DNSServiceReferences */
208 if (fDomainBrowser != nil) {
209 AILogWithSignature(@"Releasing %@",fDomainBrowser);
210 [fDomainBrowser release]; fDomainBrowser = nil;
212 avDNSReference = nil;
213 imageServiceRef = nil;
216 [self setConnected:NO];
219 - (void) setConnected:(BOOL)connected {
220 if (isConnected != connected) {
221 isConnected = connected;
223 [[client client] reportLoggedIn];
225 [[client client] reportLoggedOut];
229 - (void)setStatus:(AWEzvStatus)status withMessage:(NSString *)message {
230 NSString *statusString; /* string for use in Rendezous field */
231 /* work out the string for rendezvous */
234 statusString = @"away";
237 statusString = @"dnd";
240 statusString = @"avail";
243 /* if something weird, default to available */
244 statusString = @"avail";
247 /* add it to our data */
248 [userAnnounceData setField:@"status" content:statusString];
250 /* now set the message */
251 if ([message length]) {
252 [userAnnounceData setField:@"msg" content:message];
254 [userAnnounceData deleteField:@"msg"];
258 if ([client idleTime]) {
259 [userAnnounceData setField:@"away" content:[NSString stringWithFormat:@"%f", [[client idleTime] timeIntervalSinceReferenceDate]]];
261 [userAnnounceData deleteField:@"away"];
264 /* announce to network */
265 if (isConnected == YES)
266 [self updateAnnounceInfo];
269 /* udpates information announced over network for user */
270 - (void) updateAnnounceInfo {
271 DNSServiceErrorType updateError;
272 TXTRecordRef txtRecord;
277 if (avDNSReference == NULL) {
278 [[client client] reportError:@"avDNSReference is null when trying to update the TXT Record" ofLevel:AWEzvWarning];
282 txtRecord = [userAnnounceData dataAsTXTRecordRef];
283 AILogWithSignature(@"%@", [userAnnounceData dictionary]);
284 updateError = DNSServiceUpdateRecord (
285 /* serviceRef */ avDNSReference,
286 /* recordRef, may be NULL */ NULL,
287 /* Flags, currently ignored */ 0,
288 /* length */ TXTRecordGetLength(&txtRecord),
289 /* data */ TXTRecordGetBytesPtr(&txtRecord),
290 /* time to live */ 0 );
291 if (updateError != kDNSServiceErr_NoError) {
292 [[client client] reportError:@"Error updating TXT Record" ofLevel:AWEzvConnectionError];
296 TXTRecordDeallocate(&txtRecord);
299 - (void) updatedName {
300 [userAnnounceData setField:@"1st" content:[client name]];
301 [self updateAnnounceInfo];
304 - (void) updatedStatus {
305 [self setStatus:[client status] withMessage:[userAnnounceData getField:@"msg"]];
308 - (void)setImageData:(NSData *)JPEGData {
309 DNSServiceErrorType error;
311 unsigned char digest[20];
313 if (avDNSReference == NULL) {
314 [[client client] reportError:@"Error setting image data" ofLevel:AWEzvWarning];
318 if (JPEGData == nil) {
319 /* no image so remove record and update txt records*/
320 error = DNSServiceRemoveRecord (
321 /* service reference */ avDNSReference,
322 /* record reference */ imageRef,
323 /* flags, ignored */ 0);
324 if (error == kDNSServiceErr_NoError) {
326 [userAnnounceData deleteField:@"phsh"];
327 [self updateAnnounceInfo];
332 if (imageRef != nil && JPEGData != nil) {
333 /* Remove the old reference before updating the image.
334 * This works around a bug experienced when updating the record to use an image that occupied more space
336 error = DNSServiceRemoveRecord (
337 /* service reference */ /*imageServiceRef*/avDNSReference,
338 /* record reference */ imageRef,
339 /* flags, ignored */ 1);
340 if (error != kDNSServiceErr_NoError) {
341 [[client client] reportError:@"Error removing old image before setting new image" ofLevel:AWEzvWarning];
344 [userAnnounceData deleteField:@"phsh"];
349 error = DNSServiceAddRecord (/*service reference */ avDNSReference,
350 /*record reference */ &imageRef,
351 /* flags, ignored */ 0,
352 /* type */ kDNSServiceType_NULL,
353 /* length */ [JPEGData length],
354 /* data */ [JPEGData bytes],
355 /* time to live; 0=default */ 0);
357 if (error == kDNSServiceErr_NoError) {
358 /* Let's create the hash */
360 SHA1_Update(&ctx, [JPEGData bytes], (unsigned long)[JPEGData length]);
361 SHA1_Final(digest, &ctx);
362 imagehash = [[NSData dataWithBytes:digest length:20] retain];
363 AILogWithSignature(@"Will update with hash %@; length is %u", imagehash, [JPEGData length]);
366 [[client client] reportError:@"Error adding image record" ofLevel:AWEzvWarning];
370 - (void) updatePHSH {
371 if (imagehash != nil) {
372 [userAnnounceData setField:@"phsh" content:[imagehash autorelease]];
373 /* announce to network */
374 [self updateAnnounceInfo];
376 [userAnnounceData deleteField:@"phsh"];
380 #pragma mark Browsing Functions
381 /* start browsing the network for new rendezvous clients */
382 - (void) startBrowsing {
383 [fServiceBrowser release]; fServiceBrowser = nil;
385 /* destroy old contact dictionary if one exists */
388 /* allocate new contact dictionary */
389 contacts = [[NSMutableDictionary alloc] init];
391 /* create AV browser */
392 DNSServiceRef browsRef;
393 DNSServiceErrorType avBrowseError;
395 avBrowseError = DNSServiceBrowse (/* uninitialized DNSServiceRef */ &browsRef,
396 /* flags, currently unused */ 0,
397 /* interface index, 0 for all available */ 0,
398 /* registration type */ "_presence._tcp",
399 /* domain, may be null for default */ NULL,
400 /* callBack function */ handle_av_browse_reply,
401 /* context, may be null */ self);
403 if (avBrowseError == kDNSServiceErr_NoError) {
404 fServiceBrowser = [[ServiceController alloc] initWithServiceRef:browsRef forContactManager:self];
405 [fServiceBrowser addToCurrentRunLoop];
407 [[client client] reportError:@"Could not browse for _presence._tcp instances" ofLevel:AWEzvConnectionError];
412 /* stop looking for new rendezvous clients */
413 - (void)stopBrowsing{
414 AILogWithSignature(@"fServiceBrowser is %@ (retain count %i)", fServiceBrowser, [fServiceBrowser retainCount]);
415 [fServiceBrowser release]; fServiceBrowser = nil;
418 /* handle a message from our browser */
419 - (void)browseResultwithFlags:(DNSServiceFlags)flags
420 onInterface:(uint32_t) interfaceIndex
421 name:(const char *)replyName
422 type:(const char *)replyType
423 domain:(const char *)replyDomain
426 AWEzvContact *contact;
430 NSString *replyNameString = [NSString stringWithUTF8String:replyName];
431 if (!replyNameString)
433 if (flags == (kDNSServiceFlagsAdd) || flags == (kDNSServiceFlagsMoreComing | kDNSServiceFlagsAdd)) {
434 /* Add this contact */
435 /* initialise contact */
436 contact = [[AWEzvContact alloc] init];
437 contact.uniqueID = replyNameString;
438 contact.manager = self;
439 /* save contact in dictionary */
440 [contacts setObject:contact forKey:replyNameString];
441 [contact autorelease];
443 /* and resolve contact */
444 DNSServiceRef resolveRef;
445 DNSServiceErrorType resolveRefError;
447 resolveRefError = DNSServiceResolve (
448 /* serviceref uninitialized */ &resolveRef,
449 /* flags, currently ignored */ 0,
450 /* interfaceIndex */ 0/*interfaceIndex*/,
451 /* full name */ replyName,
452 /* registration type */ "_presence._tcp" /* replyType */,
453 /* domain */ replyDomain,
454 /* callback */ resolve_reply,
455 /* contxt, may be NULL */ contact);
457 if (resolveRefError == kDNSServiceErr_NoError) {
458 ServiceController *serviceResolver = [[ServiceController alloc] initWithServiceRef:resolveRef forContactManager:self];
459 [contact setResolveServiceController:serviceResolver];
460 [[contact resolveServiceController] addToCurrentRunLoop];
461 [serviceResolver release];
464 [[client client] reportError:@"Could not search for TXT records" ofLevel:AWEzvConnectionError];
468 /* delete the contact */
469 contact = [contacts objectForKey:replyNameString];
472 [[client client] userLoggedOut:contact];
473 /* remove the contact from our data structures */
474 [contacts removeObjectForKey:replyNameString];
478 - (void)findAddressForContact:(AWEzvContact *)contact
479 withHost:(NSString *)host
480 withInterface:(uint32_t)interface{
482 /* Now we need to query the record for the ip address */
484 DNSServiceErrorType err;
485 DNSServiceRef serviceRef;
487 err = DNSServiceQueryRecord( &serviceRef, (DNSServiceFlags) 0, interface, [host UTF8String],
488 kDNSServiceType_A, kDNSServiceClass_IN, AddressQueryRecordReply, contact);
489 if ( err == kDNSServiceErr_NoError) {
490 ServiceController *temp = [[ServiceController alloc] initWithServiceRef:serviceRef forContactManager:self];
491 [contact setAddressServiceController:temp];
492 [[contact addressServiceController] addToCurrentRunLoop];
495 [[client client] reportError:@"Error finding adress for contact" ofLevel:AWEzvError];
499 - (void)updateAddressForContact:(AWEzvContact *)contact
500 addr:(const void *)buff
501 addrLen:(uint16_t)addrLen
502 host:(const char*) host
503 interfaceIndex:(uint32_t)interface
504 more:(boolean_t)moreToCome{
506 /* check that contact exists in dictionary */
507 if ([contacts objectForKey:contact.uniqueID] == nil) {
508 NSString *uniqueID = contact.uniqueID;
509 /* So they haven't been seen before... not to worry we'll add them */
510 if (contact.uniqueID != nil) {
511 contact = [[AWEzvContact alloc] init];
512 contact.uniqueID = uniqueID;
513 contact.manager = self;
514 /* save contact in dictionary */
515 [contacts setObject:contact forKey:contact.uniqueID];
516 [contact autorelease];
519 [[client client] reportError:@"Contact to update not in dictionary and has bad identifier" ofLevel:AWEzvError];
523 NSString *ipAddr; /* ip address of contact */
524 NSRange range; /* just a range... */
528 inet_ntop( AF_INET, buff, addrBuff, sizeof addrBuff);
530 ipAddr = [NSString stringWithCString:addrBuff encoding:NSUTF8StringEncoding];
531 range = [ipAddr rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@":"]];
532 if (range.location == NSNotFound) {
533 contact.ipAddr = ipAddr;
536 if (!contact.ipAddr || !contact.ipAddr.length) {
537 [[client client] reportError:@"ip address not set" ofLevel:AWEzvError];
538 [contact setStatus: AWEzvUndefined];
542 [contact setAddressServiceController: nil];
546 - (void)updateImageForContact:(AWEzvContact *)contact
547 data:(const void *)data
548 dataLen:(uint16_t)dataLen
549 more:(boolean_t)moreToCome{
552 [contact setImageServiceController: nil];
554 AILogWithSignature(@"%@ -> %@ (%i)", [NSData dataWithBytes:data length:dataLen], [[[NSImage alloc] initWithData:[NSData dataWithBytes:data length:dataLen]] autorelease], dataLen);
556 /* We have an image */
558 [contact setContactImageData:[NSData dataWithBytes:data length:dataLen]];
559 [[client client] userChangedImage:contact];
562 [contact setImageHash: NULL];
563 [[client client] reportError:@"Error retrieving picture" ofLevel:AWEzvError];
566 - (void)updateContact:(AWEzvContact *)contact
567 withData:(AWEzvRendezvousData *)rendezvousData
568 withHost:(NSString *)host
569 withInterface:(uint32_t)interface
570 withPort:(uint16_t)recPort
573 NSString *nick = nil; /* nickname for contact */
574 NSMutableString *mutableNick = nil; /* nickname we can modify */
575 AWEzvRendezvousData *oldrendezvous; /* old rendezvous data for user */ /* XXX not used */
576 NSNumber *idleTime = nil; /* idle time */
578 /* check that contact exists in dictionary */
579 if ([contacts objectForKey:contact.uniqueID] == nil) {
580 NSString *uniqueID = contact.uniqueID;
581 /* So they haven't been seen before... not to worry we'll add them */
582 if (contact.uniqueID != nil) {
583 contact = [[AWEzvContact alloc] init];
584 contact.uniqueID = uniqueID;
585 contact.manager = self;
586 /* save contact in dictionary */
587 [contacts setObject:contact forKey:contact.uniqueID];
588 [contact autorelease];
590 [[client client] reportError:@"Contact to update not in dictionary and has bad identifier" ofLevel:AWEzvError];
594 if ([rendezvousData getField:@"slumming"] != nil)
595 // We don't want to live in a slum
598 if ([contact rendezvous] != nil) {
599 oldrendezvous = [contact rendezvous];
601 if ([contact serial] > [contact serial]) {
602 /* AWEzvLog(@"Rendezvous update for %@ with lower serial, updating anyway", contact.uniqueID); */
603 /* we'll update anyway, and hopefully we'll be back in sync with the network */
606 [contact setRendezvous:rendezvousData];
608 /* now we can update the contact */
609 /* get the nickname */
610 if ([rendezvousData getField:@"1st"] != nil)
611 nick = [rendezvousData getField:@"1st"];
612 if ([rendezvousData getField:@"last"] != nil)
614 nick = [rendezvousData getField:@"last"];
616 mutableNick = [[nick mutableCopy] autorelease];
617 [mutableNick appendString:@" "];
618 [mutableNick appendString:[rendezvousData getField:@"last"]];
619 nick = [[mutableNick copy] autorelease];
620 } else if (nick == nil)
621 nick = @"Unnamed contact";
623 [contact setName:nick];
625 /* now get the status */
626 if ([rendezvousData getField:@"status"] == nil) {
627 [contact setStatus: AWEzvOnline];
629 if ([[rendezvousData getField:@"status"] isEqualToString:@"avail"])
630 [contact setStatus: AWEzvOnline];
631 else if ([[rendezvousData getField:@"status"] isEqualToString:@"dnd"])
632 [contact setStatus: AWEzvAway];
633 else if ([[rendezvousData getField:@"status"] isEqualToString:@"away"])
634 [contact setStatus: AWEzvIdle];
636 [contact setStatus: AWEzvOnline];
640 if ([rendezvousData getField:@"away"])
641 idleTime = [NSNumber numberWithLong:strtol([[rendezvousData getField:@"away"] UTF8String], NULL, 0)];
643 [contact setIdleSinceDate: [NSDate dateWithTimeIntervalSinceReferenceDate:[idleTime doubleValue]]];
645 /* Update Buddy Icon */
646 if ([rendezvousData getField:@"phsh"] != nil) {
647 /* We should check to see if this is a new phsh */
648 NSString *hash = [contact imageHash];
649 NSString *newHash = [rendezvousData getField:@"phsh"];
650 AILogWithSignature(@"received image hash %@ for %@", newHash, contact);
651 if (hash == NULL || [newHash compare: hash] != NSOrderedSame) {
652 [contact setImageHash: newHash];
653 /* The two hashes are different or there was no image before so there is an image to be downloaded */
654 /* Download the image using DNSServiceQueryRecord */
655 DNSServiceErrorType err;
656 DNSServiceRef serviceRef;
658 NSString *dnsname = [NSString stringWithFormat:@"%@%s", contact.uniqueID,"._presence._tcp.local."];
659 err = DNSServiceQueryRecord( &serviceRef, (DNSServiceFlags) 0, interface, [dnsname UTF8String],
660 kDNSServiceType_NULL, kDNSServiceClass_IN, ImageQueryRecordReply, contact);
661 if ( err == kDNSServiceErr_NoError) {
662 ServiceController *temp = [[ServiceController alloc] initWithServiceRef:serviceRef forContactManager:self];
663 AILogWithSignature(@"requesting image with %@", temp);
664 [contact setImageServiceController:temp];
665 [[contact imageServiceController] addToCurrentRunLoop];
668 [contact setImageHash: NULL];
669 [[client client] reportError:@"Error finding image for contact" ofLevel:AWEzvError];
673 [contact setContactImageData:nil];
674 [[client client] userChangedImage:contact];
677 /* now set the port */
679 /* Couldn't find port from browse result so use port specified by txt records */
680 if ([rendezvousData getField:@"port.p2pj"] == nil) {
681 [[client client] reportError:@"Invalid rendezvous announcement for contact: no port specified" ofLevel:AWEzvError];
684 [contact setPort:[[rendezvousData getField:@"port.p2pj"] intValue]];
686 /* Correctly use port specified by SRV record */
687 [contact setPort:recPort];
689 /* and notify of new user */
690 [[client client] userChangedState:contact];
693 - (NSString *)myInstanceName{
694 return avInstanceName;
697 - (void)setInstanceName:(NSString *)newName{
698 if (avInstanceName != newName) {
699 [avInstanceName release];
700 avInstanceName = [newName retain];
704 - (void) regCallBack:(int)errorCode {
705 /* Recover if there was an error */
706 if (errorCode != kDNSServiceErr_NoError) {
708 #warning Localize and report through the connection error system
709 case kDNSServiceErr_Unknown:
710 [[[self client] client] reportError:@"Unknown error in Bonjour Registration"
711 ofLevel:AWEzvConnectionError];
713 case kDNSServiceErr_NameConflict:
714 [[[self client] client] reportError:@"A user with your Bonjour data is already online"
715 ofLevel:AWEzvConnectionError];
718 [[[self client] client] reportError:@"An internal error occurred"
719 ofLevel:AWEzvConnectionError];
720 AWEzvLog(@"Internal error: rendezvous code %d", errorCode);
723 /* kill connections */
726 [self setConnected:YES];
727 [self startBrowsing];
732 - (void)contactWillDeallocate:(AWEzvContact *)contact
734 [[client client] userLoggedOut:contact];
737 - (void)serviceControllerReceivedFatalError:(ServiceController *)serviceController
739 [[[self client] client] reportError:@"An unrecoverable connection error occurred"
740 ofLevel:AWEzvConnectionError];
746 #pragma mark mDNS callbacks
748 #pragma mark mDNS Register Callbacks
749 void register_reply (DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context ) {
750 AWEzvContactManager *self = context;
751 [self setInstanceName:[NSString stringWithUTF8String:name]];
752 [self regCallBack:errorCode];
755 void image_register_reply (
757 DNSRecordRef RecordRef,
758 DNSServiceFlags flags,
759 DNSServiceErrorType errorCode,
761 if (errorCode != kDNSServiceErr_NoError) {
762 AWEzvLog(@"error %d registering image record", errorCode);
764 AWEzvContactManager *self = context;
768 #pragma mark mDNS Browse Callback
771 * @brief DNSServiceBrowse callback
773 * This may be called multiple times for a single use of DNSServiceBrowse().
775 void handle_av_browse_reply (DNSServiceRef sdRef,
776 DNSServiceFlags flags,
777 uint32_t interfaceIndex,
778 DNSServiceErrorType errorCode,
779 const char *serviceName,
781 const char *replyDomain,
783 /* Received a browser reply from DNSServiceBrowse for av, now must handle processing the list of results */
784 if (errorCode == kDNSServiceErr_NoError) {
785 AWEzvContactManager *self = context;
786 if (![[self myInstanceName] isEqualToString:[NSString stringWithUTF8String:serviceName]])
787 [self browseResultwithFlags:flags onInterface:interfaceIndex name:serviceName type:regtype domain:replyDomain av:YES];
789 AWEzvLog(@"Error browsing");
793 #pragma mark mDNS Resolve Callback
795 * @brief DNSServiceResolve callback
797 * This may be called multiple times for a single use of DNSServiceResolve().
799 void resolve_reply (DNSServiceRef sdRef,
800 DNSServiceFlags flags,
801 uint32_t interfaceIndex,
802 DNSServiceErrorType errorCode,
803 const char *fullname,
804 const char *hosttarget,
807 const unsigned char *txtRecord,
810 if (errorCode == kDNSServiceErr_NoError) {
811 /* use TXTRecord methods to resolve this */
812 AWEzvContact *contact = context;
813 AWEzvContactManager *self = [contact manager];
814 //AWEzvLog(@"Would update contact");
815 AWEzvRendezvousData *data;
816 data = [[[AWEzvRendezvousData alloc] initWithTXTRecordRef:txtRecord length:txtLen] autorelease];
817 [self findAddressForContact:contact withHost:[NSString stringWithUTF8String:hosttarget] withInterface:interfaceIndex];
818 [self updateContact:contact withData:data withHost:[NSString stringWithUTF8String:hosttarget] withInterface:interfaceIndex withPort:ntohs(port) av:YES];
821 AWEzvLog(@"Error resolving records");
825 #pragma mark mDNS Address Callback
827 void AddressQueryRecordReply( DNSServiceRef DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex,
828 DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass,
829 uint16_t rdlen, const void *rdata, uint32_t ttl, void *context )
830 // DNSServiceQueryRecord callback used to look up IP addresses.
832 AWEzvContact *contact = context;
833 AWEzvContactManager *self = [contact manager];
835 [self updateAddressForContact:contact addr:rdata addrLen:rdlen host:fullname interfaceIndex:interfaceIndex
836 more:((flags & kDNSServiceFlagsMoreComing) != 0)];
840 #pragma mark mDNS Image Callback
841 void ImageQueryRecordReply( DNSServiceRef DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex,
842 DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass,
843 uint16_t rdlen, const void *rdata, uint32_t ttl, void *context )
844 // DNSServiceQueryRecord callback used to look up buddy icon.
846 AWEzvContact *contact = context;
847 AWEzvContactManager *self = [contact manager];
848 if (errorCode == kDNSServiceErr_NoError) {
849 if (flags & kDNSServiceFlagsAdd)
850 [self updateImageForContact:contact data:rdata dataLen:rdlen more:((flags & kDNSServiceFlagsMoreComing) != 0)];
854 #pragma mark Service Controller
856 /* ServiceController was taken from Apple's DNSServiceBrowser.m */
857 @implementation ServiceController : NSObject
859 #pragma mark CFSocket Callback
860 /* This code was taken from Apple's DNSServiceBrowser.m */
861 static void ProcessSockData( CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
862 // CFRunloop callback that notifies dns_sd when new data appears on a DNSServiceRef's socket.
864 ServiceController *self = (ServiceController *)info;
865 AILogWithSignature(@"Processing result for %@", self);
867 DNSServiceErrorType err = DNSServiceProcessResult([self serviceRef]);
868 if (err != kDNSServiceErr_NoError) {
869 if ((err == kDNSServiceErr_Unknown) && !data) {
870 //Try to accept(2) a connection. May be the cause of a hang on Tiger; see #7887.
871 int socketFD = CFSocketGetNative(s);
872 int childFD = accept(socketFD, /*addr*/ NULL, /*addrlen*/ NULL);
873 AILog(@"%@: Service ref %p received an unknown error with no data; perhaps mDNSResponder crashed? Result of calling accept(2) on fd %d is %d; will disconnect with error",
874 self, [self serviceRef], socketFD, childFD);
875 //We don't actually *want* a connection, so close the socket immediately.
876 if (childFD > -1) close(childFD);
879 [[self contactManager] serviceControllerReceivedFatalError:self];
880 [self breakdownServiceController];
884 AILog(@"DNSServiceProcessResult() for socket descriptor %d returned an error! %d with CFSocketCallBackType %d and data %s\n",
885 DNSServiceRefSockFD(info), err, type, data);
890 - (id) initWithServiceRef:(DNSServiceRef) ref forContactManager:(AWEzvContactManager *)inContactManager
894 contactManager = [inContactManager retain];
899 - (boolean_t) addToCurrentRunLoop
900 /* Add the service to the current runloop. Returns non-zero on success. */
902 CFSocketContext ctx = { 1, self, NULL, NULL, NULL };
904 fSocketRef = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(fServiceRef),
905 kCFSocketReadCallBack, ProcessSockData, &ctx);
906 if (fSocketRef != NULL)
907 fRunloopSrc = CFSocketCreateRunLoopSource(kCFAllocatorDefault, fSocketRef, 1);
908 if (fRunloopSrc != NULL) {
909 AILogWithSignature(@"Adding run loop source %p from run loop %p", fRunloopSrc, CFRunLoopGetCurrent());
910 CFRunLoopAddSource(CFRunLoopGetCurrent(), fRunloopSrc, kCFRunLoopDefaultMode);
912 AILog(@"%@: Could not listen to runloop socket", self);
914 return (fRunloopSrc != NULL);
917 - (DNSServiceRef) serviceRef
922 - (AWEzvContactManager *)contactManager
924 return contactManager;
928 /* Remove service from runloop, deallocate service and associated resources */
930 AILogWithSignature(@"%@", self);
932 [self breakdownServiceController];
937 - (void)breakdownServiceController
939 AILogWithSignature(@"%@", self);
941 if (fSocketRef != NULL) {
942 CFSocketInvalidate(fSocketRef); // Note: Also closes the underlying socket
943 CFRelease(fSocketRef);
947 if (fRunloopSrc != NULL) {
948 AILogWithSignature(@"Removing run loop source %p from run loop %p", fRunloopSrc, CFRunLoopGetCurrent());
949 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), fRunloopSrc, kCFRunLoopDefaultMode);
950 CFRelease(fRunloopSrc);
955 AILogWithSignature(@"Deallocating DNSServiceRef %p", fServiceRef);
957 DNSServiceRefDeallocate(fServiceRef);
961 [contactManager release]; contactManager = nil;
964 @end // implementation ServiceController