1/* 2** CWService.m 3** 4** Copyright (c) 2001-2007 Ludovic Marcotte 5** Copyright (C) 2018 Riccardo Mottola 6** 7** Author: Ludovic Marcotte <ludovic@Sophos.ca> 8** Riccardo Mottola <rm@gnu.org> 9** 10** This library is free software; you can redistribute it and/or 11** modify it under the terms of the GNU Lesser General Public 12** License as published by the Free Software Foundation; either 13** version 2.1 of the License, or (at your option) any later version. 14** 15** This library is distributed in the hope that it will be useful, 16** but WITHOUT ANY WARRANTY; without even the implied warranty of 17** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18** Lesser General Public License for more details. 19** 20** You should have received a copy of the GNU General Public License 21** along with this program. If not, see <http://www.gnu.org/licenses/>. 22*/ 23 24#import <Pantomime/CWService.h> 25 26#import <Pantomime/CWConstants.h> 27#import <Pantomime/CWTCPConnection.h> 28#import <Pantomime/NSData+Extensions.h> 29 30#import <Foundation/NSBundle.h> 31#import <Foundation/NSDictionary.h> 32#import <Foundation/NSNotification.h> 33#import <Foundation/NSPathUtilities.h> 34 35#include <stdlib.h> 36#include <string.h> 37 38// 39// It's important that the read buffer be bigger than the PMTU. Since almost all networks 40// permit 1500-byte packets and few permit more, the PMTU will generally be around 1500. 41// 2k is fine, 4k accomodates FDDI (and HIPPI?) networks too. 42// 43#define NET_BUF_SIZE 4096 44 45// 46// We set the size increment of blocks we will write. Under Mac OS X, we use 1024 bytes 47// in order to avoid a strange bug in SSL_write. This prevents us from no longer beeing 48// notified after a couple of writes that we can actually write data! 49// 50#define WRITE_BLOCK_SIZE 1024 51 52 53// 54// Default timeout used when waiting for something to complete. 55// 56#define DEFAULT_TIMEOUT 60 57 58// 59// Service's private interface. 60// 61@interface CWService (Private) 62 63- (int) _addWatchers; 64- (void) _removeWatchers; 65- (void) _connectionTick: (id) sender; 66- (void) _queueTick: (id) sender; 67 68@end 69 70 71// 72// OS X's implementation of the GNUstep RunLoop Extensions 73// 74#ifdef MACOSX 75static NSMapTable *fd_to_cfsocket; 76 77void socket_callback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void* data, void* info) 78{ 79 if (type&kCFSocketWriteCallBack) 80 { 81 [(CWService *)info receivedEvent: (void*)CFSocketGetNative(s) 82 type: ET_WDESC 83 extra: 0 84 forMode: nil]; 85 } 86 if (type&kCFSocketReadCallBack) 87 { 88 [(CWService *)info receivedEvent: (void*)CFSocketGetNative(s) 89 type: ET_RDESC 90 extra: 0 91 forMode: nil]; 92 } 93} 94 95@interface NSRunLoop (PantomimeRunLoopExtensions) 96- (void) addEvent: (void *) data 97 type: (RunLoopEventType) type 98 watcher: (id) watcher 99 forMode: (NSString *) mode; 100- (void) removeEvent: (void *) data 101 type: (RunLoopEventType) type 102 forMode: (NSString *) mode 103 all: (BOOL) removeAll; 104@end 105 106@implementation NSRunLoop (PantomimeRunLoopExtensions) 107 108- (void) addEvent: (void *) data 109 type: (RunLoopEventType) type 110 watcher: (id) watcher 111 forMode: (NSString *) mode 112{ 113 CFSocketRef socket; 114 115 socket = (CFSocketRef)NSMapGet(fd_to_cfsocket, data); 116 117 // We prevent dealing with callbacks when the socket is NOT 118 // in a connected state. This can happen, under OS X, if 119 // we call -addEvent: type: watcher: forMode: but the 120 // connection hasn't yet been established. If it hasn't been 121 // established, -_addWatchers: was not called so the fd is 122 // NOT in our map table. 123 if (!socket) 124 { 125 return; 126 } 127 128 switch (type) 129 { 130 case ET_RDESC: 131 CFSocketEnableCallBacks(socket, kCFSocketReadCallBack); 132 break; 133 case ET_WDESC: 134 CFSocketEnableCallBacks(socket, kCFSocketWriteCallBack); 135 break; 136 default: 137 break; 138 } 139} 140 141- (void) removeEvent: (void *) data 142 type: (RunLoopEventType) type 143 forMode: (NSString *) mode 144 all: (BOOL) removeAll 145{ 146 CFSocketRef socket; 147 148 socket = (CFSocketRef)NSMapGet(fd_to_cfsocket, data); 149 150 // See the description in -addEvent: type: watcher: forMode:. 151 if (!socket) 152 { 153 return; 154 } 155 156 switch (type) 157 { 158 case ET_RDESC: 159 CFSocketDisableCallBacks(socket, kCFSocketReadCallBack); 160 break; 161 case ET_WDESC: 162 CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack); 163 break; 164 default: 165 break; 166 } 167} 168 169@end 170#endif // MACOSX 171 172 173// 174// 175// 176@implementation CWService 177 178#ifdef MACOSX 179+ (void) initialize 180{ 181 fd_to_cfsocket = NSCreateMapTable(NSIntMapKeyCallBacks, NSIntMapValueCallBacks, 16); 182} 183#endif 184 185 186// 187// 188// 189- (id) init 190{ 191 self = [super init]; 192 if (self) 193 { 194 _supportedMechanisms = [[NSMutableArray alloc] init]; 195 _responsesFromServer = [[NSMutableArray alloc] init]; 196 _capabilities = [[NSMutableArray alloc] init]; 197 _queue = [[NSMutableArray alloc] init]; 198 _username = nil; 199 _password = nil; 200 201 202 _rbuf = [[NSMutableData alloc] init]; 203 _wbuf = [[NSMutableData alloc] init]; 204 205 _runLoopModes = [[NSMutableArray alloc] initWithObjects: NSDefaultRunLoopMode, nil]; 206 _connectionTimeout = _readTimeout = _writeTimeout = DEFAULT_TIMEOUT; 207 _counter = _lastCommand = 0; 208 _connection = nil; 209 210 _connection_state.previous_queue = [[NSMutableArray alloc] init]; 211 _connection_state.reconnecting = _connection_state.opening_mailbox = NO; 212 } 213 214 return self; 215} 216 217 218// 219// 220// 221- (id) initWithName: (NSString *) theName 222 port: (unsigned int) thePort 223{ 224 self = [self init]; 225 if (self) 226 { 227 [self setName: theName]; 228 [self setPort: thePort]; 229 } 230 return self; 231} 232 233 234// 235// 236// 237- (void) dealloc 238{ 239 //NSLog(@"Service: -dealloc"); 240 [self setDelegate: nil]; 241 242 RELEASE(_supportedMechanisms); 243 RELEASE(_responsesFromServer); 244 RELEASE(_capabilities); 245 246 RELEASE(_queue); 247 248 RELEASE(_rbuf); 249 RELEASE(_wbuf); 250 251 TEST_RELEASE(_mechanism); 252 TEST_RELEASE(_username); 253 TEST_RELEASE(_password); 254 RELEASE(_name); 255 256 TEST_RELEASE((id<NSObject>)_connection); 257 RELEASE(_runLoopModes); 258 259 RELEASE(_connection_state.previous_queue); 260 261 [super dealloc]; 262} 263 264 265// 266// access / mutation methods 267// 268- (void) setDelegate: (id) theDelegate 269{ 270 _delegate = theDelegate; 271} 272 273- (id) delegate 274{ 275 return _delegate; 276} 277 278 279// 280// 281// 282- (NSString *) name 283{ 284 return _name; 285} 286 287- (void) setName: (NSString *) theName 288{ 289 ASSIGN(_name, theName); 290} 291 292 293// 294// 295// 296- (unsigned int) port 297{ 298 return _port; 299} 300 301- (void) setPort: (unsigned int) thePort 302{ 303 _port = thePort; 304} 305 306 307// 308// 309// 310- (id<CWConnection>) connection 311{ 312 return _connection; 313} 314 315 316// 317// 318// 319- (NSArray *) supportedMechanisms 320{ 321 return [NSArray arrayWithArray: _supportedMechanisms]; 322} 323 324 325// 326// 327// 328- (NSString *) username 329{ 330 return _username; 331} 332 333- (void) setUsername: (NSString *) theUsername 334{ 335 ASSIGN(_username, theUsername); 336} 337 338 339// 340// 341// 342- (BOOL) isConnected 343{ 344 return _connected; 345} 346 347 348// 349// Other methods 350// 351- (void) authenticate: (NSString *) theUsername 352 password: (NSString *) thePassword 353 mechanism: (NSString *) theMechanism 354{ 355 [self subclassResponsibility: _cmd]; 356} 357 358 359// 360// 361// 362- (void) cancelRequest 363{ 364 // If we were in the process of establishing 365 // a connection, let's stop our internal timer. 366 [_timer invalidate]; 367 DESTROY(_timer); 368 369 [self _removeWatchers]; 370 [_connection close]; 371 DESTROY(_connection); 372 [_queue removeAllObjects]; 373 374 POST_NOTIFICATION(PantomimeRequestCancelled, self, nil); 375 PERFORM_SELECTOR_1(_delegate, @selector(requestCancelled:), PantomimeRequestCancelled); 376} 377 378 379// 380// 381// 382- (void) close 383{ 384 // 385 // If we are reconnecting, no matter what, we close and release our current connection immediately. 386 // We do that since we'll create a new on in -connect/-connectInBackgroundAndNotify. No need 387 // to return immediately since _connected will be set to NO in _removeWatchers. 388 // 389 if (_connection_state.reconnecting) 390 { 391 [self _removeWatchers]; 392 [_connection close]; 393 DESTROY(_connection); 394 } 395 396 if (_connected) 397 { 398 [self _removeWatchers]; 399 [_connection close]; 400 401 POST_NOTIFICATION(PantomimeConnectionTerminated, self, nil); 402 PERFORM_SELECTOR_1(_delegate, @selector(connectionTerminated:), PantomimeConnectionTerminated); 403 } 404} 405 406// 407// If the connection or binding succeeds, zero is returned. 408// On error, -1 is returned, and errno is set appropriately 409// 410- (int) connect 411{ 412 _connection = [[CWTCPConnection alloc] initWithName: _name 413 port: _port 414 background: NO]; 415 if (!_connection) 416 { 417 return -1; 418 } 419 return [self _addWatchers]; 420} 421 422 423// 424// 425// 426- (void) connectInBackgroundAndNotify 427{ 428 NSUInteger i; 429 430 _connection = [[CWTCPConnection alloc] initWithName: _name 431 port: _port 432 background: YES]; 433 434 if (!_connection) 435 { 436 POST_NOTIFICATION(PantomimeConnectionTimedOut, self, nil); 437 PERFORM_SELECTOR_1(_delegate, @selector(connectionTimedOut:), PantomimeConnectionTimedOut); 438 return; 439 } 440 441 _timer = [NSTimer timerWithTimeInterval: 0.1 442 target: self 443 selector: @selector(_connectionTick:) 444 userInfo: nil 445 repeats: YES]; 446 RETAIN(_timer); 447 448 for (i = 0; i < [_runLoopModes count]; i++) 449 { 450 [[NSRunLoop currentRunLoop] addTimer: _timer forMode: [_runLoopModes objectAtIndex: i]]; 451 } 452 453 [_timer fire]; 454} 455 456 457// 458// 459// 460- (void) noop 461{ 462 [self subclassResponsibility: _cmd]; 463} 464 465 466// 467// 468// 469- (void) updateRead 470{ 471 char buf[NET_BUF_SIZE]; 472 ssize_t count; 473 474 while ((count = [_connection read: buf length: NET_BUF_SIZE]) > 0) 475 { 476 NSData *aData; 477 478 aData = [[NSData alloc] initWithBytes: buf length: (NSUInteger)count]; 479 480 if (_delegate && [_delegate respondsToSelector: @selector(service:receivedData:)]) 481 { 482 [_delegate performSelector: @selector(service:receivedData:) 483 withObject: self 484 withObject: aData]; 485 } 486 487 [_rbuf appendData: aData]; 488 RELEASE(aData); 489 } 490 491 if (count == 0) 492 { 493 // 494 // We check to see if we got disconnected. 495 // 496 // The data that causes select to return is the EOF because the other side 497 // has closed the connection. This causes read to return zero. 498 // 499 if (!((CWTCPConnection *)_connection)->ssl_handshaking && _connected) 500 { 501 [self _removeWatchers]; 502 [_connection close]; 503 POST_NOTIFICATION(PantomimeConnectionLost, self, nil); 504 PERFORM_SELECTOR_1(_delegate, @selector(connectionLost:), PantomimeConnectionLost); 505 } 506 } 507 else 508 { 509 // We reset our connection timeout counter. This could happen when we are performing operations 510 // that return a large amount of data. The queue might be non-empty but network I/O could be 511 // going on at the same time. This could also be problematic for lenghty IMAP search or 512 // mailbox preload. 513 _counter = 0; 514 } 515} 516 517 518// 519// 520// 521- (void) updateWrite 522{ 523 if ([_wbuf length] > 0) 524 { 525 char *bytes; 526 NSUInteger len; 527 NSInteger count; 528 NSUInteger i; 529 530 bytes = (char *)[_wbuf mutableBytes]; 531 len = [_wbuf length]; 532 533#ifdef MACOSX 534 count = [_connection write: bytes length: len > WRITE_BLOCK_SIZE ? WRITE_BLOCK_SIZE : len]; 535#else 536 count = [_connection write: bytes length: len]; 537#endif 538 // If nothing was written or if an error occured, we return. 539 if (count <= 0) 540 { 541 return; 542 } 543 // Otherwise, we inform our delegate that we wrote some data... 544 else if (_delegate && [_delegate respondsToSelector: @selector(service:sentData:)]) 545 { 546 [_delegate performSelector: @selector(service:sentData:) 547 withObject: self 548 withObject: [_wbuf subdataToIndex: (NSUInteger)count]]; 549 } 550 551 //NSLog(@"count = %d, len = %d", count, len); 552 553 // If we have been able to write everything... 554 if (count == len) 555 { 556 [_wbuf setLength: 0]; 557#ifndef __MINGW32__ 558 // If we are done writing, let's remove the watcher on our fd. 559 for (i = 0; i < [_runLoopModes count]; i++) 560 { 561 [[NSRunLoop currentRunLoop] removeEvent: (void *)[_connection fd] 562 type: ET_WDESC 563 forMode: [_runLoopModes objectAtIndex: i] 564 all: YES]; 565 } 566#endif 567 } 568 else 569 { 570 memmove(bytes, bytes+count, len-count); 571 [_wbuf setLength: len-count]; 572 573 // We enable the write callback under OS X. 574 // See the rationale in -writeData: 575#ifdef MACOSX 576 for (i = 0; i < [_runLoopModes count]; i++) 577 { 578 [[NSRunLoop currentRunLoop] addEvent: (void *)[_connection fd] 579 type: ET_WDESC 580 watcher: self 581 forMode: [_runLoopModes objectAtIndex: i]]; 582 } 583#endif 584 } 585 } 586} 587 588 589// 590// 591// 592- (void) writeData: (NSData *) theData 593{ 594 if (theData && [theData length]) 595 { 596 NSUInteger i; 597 598 [_wbuf appendData: theData]; 599 600 // 601 // Let's not try to enable the write callback if we are not connected 602 // There's no reason to try to enable the write callback if we 603 // are not connected. 604 // 605 if (!_connected) 606 { 607 return; 608 } 609 610 // 611 // We re-enable the write callback. 612 // 613 // Rationale from OS X's CoreFoundation: 614 // 615 // By default kCFSocketReadCallBack, kCFSocketAcceptCallBack, and kCFSocketDataCallBack callbacks are 616 // automatically reenabled, whereas kCFSocketWriteCallBack callbacks are not; kCFSocketConnectCallBack 617 // callbacks can only occur once, so they cannot be reenabled. Be careful about automatically reenabling 618 // read and write callbacks, because this implies that the callbacks will be sent repeatedly if the socket 619 // remains readable or writable respectively. Be sure to set these flags only for callback types that your 620 // CFSocket actually possesses; the result of setting them for other callback types is undefined. 621 // 622#ifndef __MINGW32__ 623 for (i = 0; i < [_runLoopModes count]; i++) 624 { 625 [[NSRunLoop currentRunLoop] addEvent: (void *)[_connection fd] 626 type: ET_WDESC 627 watcher: self 628 forMode: [_runLoopModes objectAtIndex: i]]; 629 } 630#endif 631 } 632} 633 634 635// 636// RunLoopEvents protocol's implementations. 637// 638- (void) receivedEvent: (void *) theData 639 type: (RunLoopEventType) theType 640 extra: (void *) theExtra 641 forMode: (NSString *) theMode 642{ 643 AUTORELEASE(RETAIN(self)); // Don't be deallocated while handling event 644 switch (theType) 645 { 646#ifdef __MINGW32__ 647 case ET_HANDLE: 648 case ET_TRIGGER: 649 [self updateRead]; 650 [self updateWrite]; 651 break; 652#else 653 case ET_RDESC: 654 [self updateRead]; 655 break; 656 657 case ET_WDESC: 658 [self updateWrite]; 659 break; 660 661 case ET_EDESC: 662 //NSLog(@"GOT ET_EDESC! %d current fd = %d", theData, [_connection fd]); 663 break; 664#endif 665 666 default: 667 break; 668 } 669} 670 671 672// 673// 674// 675- (int) reconnect 676{ 677 [self subclassResponsibility: _cmd]; 678 return 0; 679} 680 681 682// 683// 684// 685- (NSDate *) timedOutEvent: (void *) theData 686 type: (RunLoopEventType) theType 687 forMode: (NSString *) theMode 688{ 689 //NSLog(@"timed out event!"); 690 return nil; 691} 692 693 694// 695// 696// 697- (void) addRunLoopMode: (NSString *) theMode 698{ 699#ifndef MACOSX 700 if (theMode && ![_runLoopModes containsObject: theMode]) 701 { 702 [_runLoopModes addObject: theMode]; 703 } 704#endif 705} 706 707 708// 709// 710// 711- (unsigned int) connectionTimeout 712{ 713 return _connectionTimeout; 714} 715 716- (void) setConnectionTimeout: (unsigned int) theConnectionTimeout 717{ 718 _connectionTimeout = (theConnectionTimeout > 0 ? theConnectionTimeout : DEFAULT_TIMEOUT); 719} 720 721- (unsigned int) readTimeout 722{ 723 return _readTimeout; 724} 725 726- (void) setReadTimeout: (unsigned int) theReadTimeout 727{ 728 _readTimeout = (theReadTimeout > 0 ? theReadTimeout: DEFAULT_TIMEOUT); 729} 730 731- (unsigned int) writeTimeout 732{ 733 return _writeTimeout; 734} 735 736- (void) setWriteTimeout: (unsigned int) theWriteTimeout 737{ 738 _writeTimeout = (theWriteTimeout > 0 ? theWriteTimeout : DEFAULT_TIMEOUT); 739} 740 741- (void) startTLS 742{ 743 [self subclassResponsibility: _cmd]; 744} 745 746- (unsigned int) lastCommand 747{ 748 return _lastCommand; 749} 750 751- (NSArray *) capabilities 752{ 753 return _capabilities; 754} 755 756@end 757 758// 759// 760// 761@implementation CWService (Private) 762 763 764// 765// This methods adds watchers on a file descriptor. 766// It returns 0 if it has completed successfully. 767// 768- (int) _addWatchers 769{ 770 NSUInteger i; 771 772 // 773 // Under Mac OS X, we must also create a CFSocket and a runloop source in order 774 // to enabled callbacks write read/write availability. 775 // 776#ifdef MACOSX 777 _context = (CFSocketContext *)malloc(sizeof(CFSocketContext)); 778 memset(_context, 0, sizeof(CFSocketContext)); 779 _context->info = self; 780 781 _socket = CFSocketCreateWithNative(NULL, [_connection fd], kCFSocketReadCallBack|kCFSocketWriteCallBack, socket_callback, _context); 782 CFSocketDisableCallBacks(_socket, kCFSocketReadCallBack|kCFSocketWriteCallBack); 783 784 if (!_socket) 785 { 786 //NSLog(@"Failed to create CFSocket from native."); 787 return -1; 788 } 789 790 _runLoopSource = CFSocketCreateRunLoopSource(NULL, _socket, 1); 791 792 if (!_runLoopSource) 793 { 794 //NSLog(@"Failed to create the runloop source."); 795 return -1; 796 } 797 798 CFRunLoopAddSource(CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes); 799 NSMapInsert(fd_to_cfsocket, (void *)[_connection fd], (void *)_socket); 800#endif 801 802 // We get ready to monitor for read/write timeouts 803 _timer = [NSTimer timerWithTimeInterval: 1 804 target: self 805 selector: @selector(_queueTick:) 806 userInfo: nil 807 repeats: YES]; 808 RETAIN(_timer); 809 _counter = 0; 810 811 //NSLog(@"Adding watchers on %d", [_connection fd]); 812 813 for (i = 0; i < [_runLoopModes count]; i++) 814 { 815 [[NSRunLoop currentRunLoop] addEvent: (void *)[_connection fd] 816#ifdef __MINGW32__ 817 type: ET_HANDLE 818#else 819 type: ET_RDESC 820#endif 821 watcher: self 822 forMode: [_runLoopModes objectAtIndex: i]]; 823 824 [[NSRunLoop currentRunLoop] addEvent: (void *)[_connection fd] 825#ifdef __MINGW32__ 826 type: ET_TRIGGER 827#else 828 type: ET_EDESC 829#endif 830 watcher: self 831 forMode: [_runLoopModes objectAtIndex: i]]; 832 833 [[NSRunLoop currentRunLoop] addTimer: _timer forMode: [_runLoopModes objectAtIndex: i]]; 834 } 835 836 _connected = YES; 837 POST_NOTIFICATION(PantomimeConnectionEstablished, self, nil); 838 PERFORM_SELECTOR_1(_delegate, @selector(connectionEstablished:), PantomimeConnectionEstablished); 839 840 [_timer fire]; 841 return 0; 842} 843 844 845// 846// 847// 848- (void) _removeWatchers 849{ 850 NSUInteger i; 851 852 // 853 // If we are not connected, no need to remove the watchers on our file descriptor. 854 // This could also generate a crash under OS X as the _runLoopSource, _socket etc. 855 // ivars aren't initialized. 856 // 857 if (!_connected) 858 { 859 return; 860 } 861 862 [_timer invalidate]; 863 DESTROY(_timer); 864 _connected = NO; 865 866 //NSLog(@"Removing all watchers on %d...", [_connection fd]); 867 868 for (i = 0; i < [_runLoopModes count]; i++) 869 { 870#ifdef __MINGW32__ 871 [[NSRunLoop currentRunLoop] removeEvent: (void *)[_connection fd] 872 type: ET_HANDLE 873 forMode: [_runLoopModes objectAtIndex: i] 874 all: YES]; 875 876 [[NSRunLoop currentRunLoop] removeEvent: (void *)[_connection fd] 877 type: ET_TRIGGER 878 forMode: [_runLoopModes objectAtIndex: i] 879 all: YES]; 880#else 881 [[NSRunLoop currentRunLoop] removeEvent: (void *)[_connection fd] 882 type: ET_RDESC 883 forMode: [_runLoopModes objectAtIndex: i] 884 all: YES]; 885 886 [[NSRunLoop currentRunLoop] removeEvent: (void *)[_connection fd] 887 type: ET_WDESC 888 forMode: [_runLoopModes objectAtIndex: i] 889 all: YES]; 890 891 [[NSRunLoop currentRunLoop] removeEvent: (void *)[_connection fd] 892 type: ET_EDESC 893 forMode: [_runLoopModes objectAtIndex: i] 894 all: YES]; 895#endif 896 } 897 898#ifdef MACOSX 899 if (CFRunLoopSourceIsValid(_runLoopSource)) 900 { 901 CFRunLoopSourceInvalidate(_runLoopSource); 902 CFRelease(_runLoopSource); 903 } 904 905 if (CFSocketIsValid(_socket)) 906 { 907 CFSocketInvalidate(_socket); 908 } 909 910 NSMapRemove(fd_to_cfsocket, (void *)[_connection fd]); 911 CFRelease(_socket); 912 free(_context); 913#endif 914} 915 916// 917// 918// 919- (void) _connectionTick: (id) sender 920{ 921 if ((_counter/10) == _connectionTimeout) 922 { 923 [_timer invalidate]; 924 DESTROY(_timer); 925 926 POST_NOTIFICATION(PantomimeConnectionTimedOut, self, nil); 927 PERFORM_SELECTOR_1(_delegate, @selector(connectionTimedOut:), PantomimeConnectionTimedOut); 928 return; 929 } 930 931 if ([_connection isConnected]) 932 { 933 [_timer invalidate]; 934 DESTROY(_timer); 935 [self _addWatchers]; 936 return; 937 } 938 939 _counter++; 940} 941 942// 943// 944// 945- (void) _queueTick: (id) sender 946{ 947 if ([_queue count]) 948 { 949 if (_counter == _readTimeout) 950 { 951 NSLog(@"Waited %d secs, read/write timeout", _readTimeout); 952 [_timer invalidate]; 953 DESTROY(_timer); 954 POST_NOTIFICATION(PantomimeConnectionTimedOut, self, nil); 955 PERFORM_SELECTOR_1(_delegate, @selector(connectionTimedOut:), PantomimeConnectionTimedOut); 956 } 957 958 _counter++; 959 } 960 else 961 { 962 _counter = 0; 963 } 964} 965 966@end 967