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