]> err.no Git - sope/blob - sope-core/NGStreams/NGActiveSocket.m
841cb9cc3906772d16557d71e930b1b80059cc4f
[sope] / sope-core / NGStreams / NGActiveSocket.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "config.h"
23
24 #if defined(HAVE_UNISTD_H) || defined(__APPLE__)
25 #  include <unistd.h>
26 #endif
27
28 #ifdef HAVE_SYS_SELECT_H
29 #  include <sys/select.h>
30 #endif
31 #ifdef HAVE_SYS_FILIO_H
32 #  include <sys/filio.h>
33 #endif
34 #if defined(HAVE_SYS_IOCTL_H)
35 #  include <sys/ioctl.h>
36 #endif
37 #if defined(HAVE_TIME_H) || defined(__APPLE__)
38 #  include <time.h>
39 #endif
40 #if defined(HAVE_SYS_TIME_H) || defined(__APPLE__)
41 #  include <sys/time.h>
42 #endif
43 #if defined(HAVE_FCNTL_H) || defined(__APPLE__)
44 #  include <fcntl.h>
45 #endif
46
47 #if defined(__APPLE__)
48 #  include <sys/types.h>
49 #  include <sys/socket.h>
50 #  include <sys/ioctl.h>
51 #endif
52
53 #if HAVE_WINDOWS_H && !defined(__CYGWIN32__)
54 #  include <windows.h>
55 #endif
56
57 #if defined(WIN32) && !defined(__CYGWIN32__)
58 #  include <winsock.h>
59 #  define ioctl ioctlsocket
60 #endif
61
62 #include "common.h"
63
64 #include <NGStreams/NGDescriptorFunctions.h>
65 #include "NGActiveSocket.h"
66 #include "NGSocketExceptions.h"
67 #include "NGSocket+private.h"
68 #include "common.h"
69
70 #if !defined(POLLRDNORM)
71 #  define POLLRDNORM POLLIN
72 #endif
73
74 @interface NGActiveSocket(PrivateMethods)
75
76 - (id)_initWithDescriptor:(int)_fd
77   localAddress:(id<NGSocketAddress>)_local
78   remoteAddress:(id<NGSocketAddress>)_remote;
79
80 @end
81
82 @implementation NGActiveSocket
83
84 #if !defined(WIN32) || defined(__CYGWIN32__)
85
86 + (BOOL)socketPair:(id<NGSocket>[2])_pair inDomain:(id<NGSocketDomain>)_domain {
87   int fds[2];
88
89   _pair[0] = nil;
90   _pair[1] = nil;
91
92   if (socketpair([_domain socketDomain], SOCK_STREAM, [_domain protocol],
93                  fds) == 0) {
94     NGActiveSocket *s1 = nil;
95     NGActiveSocket *s2 = nil;
96     
97     s1 = [[self alloc] _initWithDomain:_domain descriptor:fds[0]];
98     s2 = [[self alloc] _initWithDomain:_domain descriptor:fds[1]];
99     s1 = [s1 autorelease];
100     s2 = [s2 autorelease];
101
102     if ((s1 != nil) && (s2 != nil)) {
103       s1->mode           = NGStreamMode_readWrite;
104       s1->receiveTimeout = 0.0;
105       s1->sendTimeout    = 0.0;
106       s2->mode           = NGStreamMode_readWrite;
107       s2->receiveTimeout = 0.0;
108       s2->sendTimeout    = 0.0;
109
110       _pair[0] = s1;
111       _pair[1] = s2;
112
113       return YES;
114     }
115     else
116       return NO;
117   }
118   else {
119     int      e       = errno;
120     NSString *reason = nil;
121
122     switch (e) {
123       case EACCES:
124         reason = @"Not allowed to create socket of this type";
125         break;
126       case ENOMEM:
127         reason = @"Could not create socket: Insufficient user memory available";
128         break;
129       case EPROTONOSUPPORT:
130         reason = @"The protocol is not supported by the address family or "
131                  @"implementation";
132         break;
133       case EPROTOTYPE:
134         reason = @"The socket type is not supported by the protocol";
135         break;
136       case EMFILE:
137         reason = @"Could not create socket: descriptor table is full";
138         break;
139       case EOPNOTSUPP:
140         reason = @"The specified protocol does not permit creation of socket "
141                  @"pairs";
142         break;
143
144 #if DEBUG
145       case 0:
146         NSLog(@"WARNING(%s): socketpair() call failed, but errno=0",
147               __PRETTY_FUNCTION__);
148 #endif
149       default:
150         reason = [NSString stringWithFormat:@"Could not create socketpair: %s",
151                              strerror(e)];
152         break;
153     }
154     [[[NGCouldNotCreateSocketException alloc]
155               initWithReason:reason domain:_domain] raise];
156     return NO;
157   }
158 }
159
160 #endif
161
162 + (id)socketConnectedToAddress:(id<NGSocketAddress>)_address {
163   volatile id sock = [[self alloc] initWithDomain:[_address domain]];
164   
165   if (sock != nil) {
166     if (![sock connectToAddress:_address]) {
167       NSException *e;
168 #if 0
169       NSLog(@"WARNING(%s): Couldn't connect to address %@: %@",
170             __PRETTY_FUNCTION__, _address, [sock lastException]);
171 #endif
172       /*
173         this method needs to raise the exception, since no object is returned
174         in which we could check the -lastException ...
175       */
176       e = [[sock lastException] retain];
177       [self release];
178       e = [e autorelease];
179       [e raise];
180       return nil;
181     }
182     sock = [sock autorelease];
183   }
184   return sock;
185 }
186
187 - (id)initWithDomain:(id<NGSocketDomain>)_domain {
188   // designated initializer
189   if ((self = [super initWithDomain:_domain])) {
190     self->mode           = NGStreamMode_readWrite;
191     self->receiveTimeout = 0.0;
192     self->sendTimeout    = 0.0;
193   }
194   return self;
195 }
196
197 - (id)_initWithDescriptor:(int)_fd
198   localAddress:(id<NGSocketAddress>)_local
199   remoteAddress:(id<NGSocketAddress>)_remote 
200 {
201   if ((self = [self _initWithDomain:[_local domain] descriptor:_fd])) {
202     ASSIGN(self->localAddress,  _local);
203     ASSIGN(self->remoteAddress, _remote);
204     self->mode = NGStreamMode_readWrite;
205     
206 #if !defined(WIN32) || defined(__CYGWIN32__)
207     NGAddDescriptorFlag(self->fd, O_NONBLOCK);
208 #endif
209   }
210   return self;
211 }
212
213 - (void)dealloc {
214   [self->remoteAddress release];
215   [super dealloc];
216 }
217
218 /* operations */
219
220 - (NSException *)lastException {
221   return [super lastException];
222 }
223
224 - (void)raise:(NSString *)_name reason:(NSString *)_reason {
225   Class clazz;
226   NSException *e;
227   
228   clazz = NSClassFromString(_name);
229   NSAssert1(clazz, @"did not find exception class %@", _name);
230   
231   e = [clazz alloc];
232   if (_reason) {
233     if ([clazz instancesRespondToSelector:@selector(initWithReason:socket:)])
234       e = [(id)e initWithReason:_reason socket:self];
235     else if ([clazz instancesRespondToSelector:@selector(initWithStream:reason:)])
236       e = [(id)e initWithStream:self reason:_reason];
237     else if ([clazz instancesRespondToSelector:@selector(initWithSocket:)])
238       e = [(id)e initWithSocket:self];
239     else if ([clazz instancesRespondToSelector:@selector(initWithStream:)])
240       e = [(id)e initWithStream:self];
241     else
242       e = [e initWithReason:_reason];
243   }
244   else {
245     if ([clazz instancesRespondToSelector:@selector(initWithSocket:)])
246       e = [(id)e initWithSocket:self];
247     else if ([clazz instancesRespondToSelector:@selector(initWithStream:)])
248       e = [(id)e initWithStream:self];
249     else
250       e = [e init];
251   }
252   [self setLastException:e];
253   [e release];
254 }
255 - (void)raise:(NSString *)_name {
256   [self raise:_name reason:nil];
257 }
258
259 - (BOOL)markNonblockingAfterConnect {
260 #if !defined(WIN32) || defined(__CYGWIN32__)
261   // mark socket as non-blocking
262   return YES;
263 #else
264   // on Win we only support blocking sockets right now ...
265   return NO;
266 #endif
267 }
268
269 - (BOOL)primaryConnectToAddress:(id<NGSocketAddress>)_address {
270   // throws
271   //   NGCouldNotConnectException  if the the connect() call fails
272
273   [self resetLastException];
274   
275   if (connect(fd,
276               (struct sockaddr *)[_address internalAddressRepresentation],
277               [_address addressRepresentationSize]) != 0) {
278     NSString *reason   = nil;
279     int      errorCode = errno;
280     NSException *e;
281     
282     switch (errorCode) {
283       case EACCES:
284         reason = @"search permission denied for element in path";
285         break;
286 #if defined(WIN32) && !defined(__CYGWIN32__)
287       case WSAEADDRINUSE:
288         reason = @"address already in use";
289         break;
290       case WSAEADDRNOTAVAIL:
291         reason = @"address is not available on remote machine";
292         break;
293       case WSAEAFNOSUPPORT:
294         reason = @"addresses in the specified family cannot be used with the socket";
295         break;
296       case WSAEALREADY:
297         reason = @"a previous non-blocking attempt has not yet been completed";
298         break;
299       case WSAEBADF:
300         reason = @"descriptor is invalid";
301         break;
302       case WSAECONNREFUSED:
303         reason = @"connection refused";
304         break;
305       case WSAEINTR:
306         reason = @"connect was interrupted";
307         break;
308       case WSAEINVAL:
309         reason = @"the address length is invalid";
310         break;
311       case WSAEISCONN:
312         reason = @"socket is already connected";
313         break;
314       case WSAENETUNREACH:
315         reason = @"network is unreachable";
316         break;
317       case WSAETIMEDOUT:
318         reason = @"timeout occured";
319         break;
320 #else
321       case EADDRINUSE:
322         reason = @"address already in use";
323         break;
324       case EADDRNOTAVAIL:
325         reason = @"address is not available on remote machine";
326         break;
327       case EAFNOSUPPORT:
328         reason = @"addresses in the specified family cannot be used with the socket";
329         break;
330       case EALREADY:
331         reason = @"a previous non-blocking attempt has not yet been completed";
332         break;
333       case EBADF:
334         reason = @"descriptor is invalid";
335         break;
336       case ECONNREFUSED:
337         reason = @"connection refused";
338         break;
339       case EINTR:
340         reason = @"connect was interrupted";
341         break;
342       case EINVAL:
343         reason = @"the address length is invalid";
344         break;
345       case EIO:
346         reason = @"an IO error occured";
347         break;
348       case EISCONN:
349         reason = @"socket is already connected";
350         break;
351       case ENETUNREACH:
352         reason = @"network is unreachable";
353         break;
354       case ETIMEDOUT:
355         reason = @"timeout occured";
356         break;
357 #endif
358
359 #if DEBUG
360       case 0:
361         NSLog(@"WARNING(%s): connect() call failed, but errno=0",
362               __PRETTY_FUNCTION__);
363 #endif
364         
365       default:
366         reason = [NSString stringWithCString:strerror(errorCode)];
367         break;
368     }
369
370     reason = [NSString stringWithFormat:@"Could not connect to address %@: %@",
371                          _address, reason];
372     
373     e = [[NGCouldNotConnectException alloc]
374               initWithReason:reason socket:self address:_address];
375     [self setLastException:e];
376     [e release];
377     return NO;
378   }
379   
380   /* connect was successful */
381
382   ASSIGN(self->remoteAddress, _address);
383
384   if ([self markNonblockingAfterConnect]) {
385     /* mark socket as non-blocking */
386     NGAddDescriptorFlag(self->fd, O_NONBLOCK);
387     NSAssert((NGGetDescriptorFlags(self->fd) & O_NONBLOCK),
388              @"could not enable non-blocking mode ..");
389   }
390   return YES;
391 }
392
393 - (BOOL)connectToAddress:(id<NGSocketAddress>)_address {
394   // throws
395   //   NGSocketAlreadyConnectedException  if the socket is already connected
396   //   NGInvalidSocketDomainException     if the remote domain != local domain
397   //   NGCouldNotCreateSocketException    if the socket creation failed
398   
399   if ([self isConnected]) {
400     [[[NGSocketAlreadyConnectedException alloc]
401               initWithReason:@"Could not connected: socket is already connected"
402               socket:self address:self->remoteAddress] raise];
403     return NO;
404   }
405
406   // check whether the remote address is in the same domain like the bound one
407   if (flags.isBound) {
408     if (![[localAddress domain] isEqual:[_address domain]]) {
409       [[[NGInvalidSocketDomainException alloc]
410                 initWithReason:@"local and remote socket domains are different"
411                 socket:self domain:[_address domain]] raise];
412       return NO;
413     }
414   }
415   
416   // connect, remote-address is non-nil if this returns
417   if (![self primaryConnectToAddress:_address])
418     return NO;
419
420   // if the socket wasn't bound before (normal case), bind it now
421   if (!flags.isBound)
422     if (![self kernelBoundAddress]) return NO;
423   return YES;
424 }
425
426 - (void)_shutdownDuringOperation {
427   [self shutdown];
428 }
429
430 - (BOOL)shutdown {
431   if (self->fd != NGInvalidSocketDescriptor) {
432     if (self->mode != NGStreamMode_undefined) {
433       if (shutdown(self->fd, SHUT_RDWR) == 0)
434         self->mode = NGStreamMode_undefined;
435     }
436     
437 #if defined(WIN32) && !defined(__CYGWIN32__)
438     if (closesocket(self->fd) == 0) {
439 #else
440     if (close(self->fd) == 0) {
441 #endif
442       self->fd = NGInvalidSocketDescriptor;
443     }
444     else {
445       NSLog(@"ERROR(%s): close of socket %@ (fd=%i) alive=%s failed: %s",
446             __PRETTY_FUNCTION__,
447             self, self->fd, [self isAlive] ? "YES" : "NO", strerror(errno));
448     }
449     
450     ASSIGN(self->remoteAddress, (id)nil);
451   }
452   return YES;
453 }
454
455 - (BOOL)shutdownSendChannel {
456   if (NGCanWriteInStreamMode(self->mode)) {
457     shutdown(self->fd, SHUT_WR);
458     
459     if (self->mode == NGStreamMode_readWrite)
460       self->mode = NGStreamMode_readOnly;
461     else {
462       self->mode = NGStreamMode_undefined;
463 #if defined(WIN32) && !defined(__CYGWIN32__)
464       closesocket(self->fd);
465 #else
466       close(self->fd);
467 #endif
468       self->fd = NGInvalidSocketDescriptor;
469     }
470   }
471   return YES;
472 }
473 - (BOOL)shutdownReceiveChannel {
474   if (NGCanReadInStreamMode(self->mode)) {
475     shutdown(self->fd, SHUT_RD);
476     
477     if (self->mode == NGStreamMode_readWrite)
478       self->mode = NGStreamMode_writeOnly;
479     else {
480       self->mode = NGStreamMode_undefined;
481 #if defined(WIN32) && !defined(__CYGWIN32__)
482       closesocket(self->fd);
483 #else
484       close(self->fd);
485 #endif
486       self->fd = NGInvalidSocketDescriptor;
487     }
488   }
489   return YES;
490 }
491
492 // ******************** accessors ******************
493
494 - (id<NGSocketAddress>)remoteAddress {
495   return self->remoteAddress;
496 }
497
498 - (BOOL)isConnected {
499   return (self->remoteAddress != nil);
500 }
501 - (BOOL)isOpen {
502   return [self isConnected];
503 }
504
505 - (int)socketType {
506   return SOCK_STREAM;
507 }
508
509 - (void)setSendTimeout:(NSTimeInterval)_timeout {
510   self->sendTimeout = _timeout;
511 }
512 - (NSTimeInterval)sendTimeout {
513   return self->sendTimeout;
514 }
515
516 - (void)setReceiveTimeout:(NSTimeInterval)_timeout {
517   self->receiveTimeout = _timeout;
518 }
519 - (NSTimeInterval)receiveTimeout {
520   return self->receiveTimeout;
521 }
522
523 - (BOOL)wouldBlockInMode:(NGStreamMode)_mode {
524   short events = 0;
525
526   if ((![self isConnected]) || (fd == NGInvalidSocketDescriptor))
527     return NO;
528
529   if (NGCanReadInStreamMode(_mode))  events |= POLLRDNORM;
530   if (NGCanWriteInStreamMode(_mode)) events |= POLLWRNORM;
531
532   // timeout of 0 means return immediatly
533   return (NGPollDescriptor([self fileDescriptor], events, 0) == 1 ? NO : YES);
534 }
535
536 - (int)waitForMode:(NGStreamMode)_mode timeout:(NSTimeInterval)_timeout {
537   short events = 0;
538
539   if (NGCanReadInStreamMode(_mode))  events |= POLLRDNORM;
540   if (NGCanWriteInStreamMode(_mode)) events |= POLLWRNORM;
541
542   // timeout of 0 means return immediatly
543   return NGPollDescriptor([self fileDescriptor], events,
544                           (int)(_timeout * 1000.0));
545 }
546
547 - (unsigned)numberOfAvailableBytesForReading {
548   int len;
549
550   // need to check whether socket is connected
551   if (self->remoteAddress == nil) {
552     [self raise:@"NGSocketNotConnectedException"
553           reason:@"socket is not connected"];
554     return NGStreamError;
555   }
556   
557   if (!NGCanReadInStreamMode(self->mode)) {
558     [self raise:@"NGWriteOnlyStreamException"];
559     return NGStreamError;
560   }
561   
562 #if !defined(WIN32) && !defined(__CYGWIN32__)
563   while (ioctl(self->fd, FIONREAD, &len) == -1) {
564     if (errno == EINTR) continue;
565     
566     [self raise:@"NGSocketException"
567           reason:@"could not get number of available bytes"];
568     return NGStreamError;
569   }
570 #else
571   // PeekNamedPipe() on Win ...
572   len = 0;
573 #endif
574   return len;
575 }
576
577 - (BOOL)isAlive {
578   if (self->fd == NGInvalidSocketDescriptor)
579     return NO;
580   
581   /* poll socket for input */
582   {
583     struct timeval to;
584     fd_set readMask;
585
586     while (YES) {
587       FD_ZERO(&readMask);
588       FD_SET(self->fd, &readMask);
589       to.tv_sec = to.tv_usec = 0;
590       
591       if (select(self->fd + 1, &readMask, NULL, NULL, &to) >= 0)
592         break;
593
594       switch (errno) {
595         case EINTR:
596           continue;
597         case EBADF:
598           goto notAlive;
599         default:
600           NSLog(@"socket select() failed: %s", strerror(errno));
601           goto notAlive;
602       }
603     }
604
605     /* no input is pending, connection is alive */
606     if (!FD_ISSET(self->fd, &readMask)) 
607       return YES;
608   }
609
610   /*
611     input is pending: If select() indicates pending input, but ioctl()
612     indicates zero bytes of pending input, the connection is broken
613   */
614   {
615 #if defined(WIN32) && !defined(__CYGWIN32__)
616     u_long len;
617 #else
618     int len;
619 #endif
620     while (ioctl(self->fd, FIONREAD, &len) == -1) {
621       if (errno == EINTR) continue;
622       goto notAlive;
623     }
624     if (len > 0) return YES;
625   }
626   
627  notAlive:
628   /* valid descriptor, but not alive .. so we close the socket */
629 #if defined(WIN32) && !defined(__CYGWIN32__)
630   closesocket(self->fd);
631 #else
632   close(self->fd);
633 #endif
634   self->fd = NGInvalidSocketDescriptor;
635   RELEASE(self->remoteAddress); self->remoteAddress = nil;
636   return NO;
637 }
638  
639 // ******************** NGStream ********************
640
641 - (unsigned)readBytes:(void *)_buf count:(unsigned)_len {
642   // throws
643   //   NGStreamReadErrorException    when the read call failed
644   //   NGSocketNotConnectedException when the socket is not connected
645   //   NGEndOfStreamException        when the end of the stream is reached
646   //   NGWriteOnlyStreamException    when the receive channel was shutdown
647   NSException *e = nil;
648   
649   if (self->fd == NGInvalidSocketDescriptor) {
650     [self raise:@"NGSocketException" reason:@"NGActiveSocket is not open"];
651     return NGStreamError;
652   }
653   
654   // need to check whether socket is connected
655   if (self->remoteAddress == nil) {
656     [self raise:@"NGSocketNotConnectedException"
657           reason:@"socket is not connected"];
658     return NGStreamError;
659   }
660   
661   if (!NGCanReadInStreamMode(self->mode)) {
662     [self raise:@"NGWriteOnlyStreamException"];
663     return NGStreamError;
664   }
665   
666   if (_len == 0) return 0;
667
668   {
669 #if defined(WIN32) && !defined(__CYGWIN32__)
670     int readResult;
671
672     readResult = recv(self->fd, _buf, _len, 0);
673     if (readResult == 0) {
674       [self _shutdownDuringOperation];
675       [self raise:@"NGSocketShutdownDuringReadException"];
676       return NGStreamError;
677     }
678     else if (readResult < 0) {
679       int errorCode = WSAGetLastError();
680       
681       switch (errorCode) {
682         case WSAECONNRESET:
683           e = [[NGSocketConnectionResetException alloc] initWithStream:self];
684           break;
685         case WSAETIMEDOUT:
686           e = [[NGSocketTimedOutException alloc] initWithStream:self];
687           break;
688
689         case WSAEWOULDBLOCK:
690           NSLog(@"WARNING: descriptor would block ..");
691           
692         default:
693           e = [[NGStreamReadErrorException alloc]
694                     initWithStream:self errorCode:errorCode];
695           break;
696       }
697       if (e) {
698         [self setLastException:e];
699         [e release];
700         return NGStreamError;
701       }
702     }
703 #else /* !WIN32 */
704     int readResult;
705
706     NSAssert(_buf,     @"invalid buffer");
707     NSAssert1(_len > 0, @"invalid length: %i", _len);
708    retry: 
709     readResult = NGDescriptorRecv(self->fd, _buf, _len, 0,
710                                   (self->receiveTimeout == 0.0)
711                                   ? -1 // block until data
712                                   : (int)(self->receiveTimeout * 1000.0));
713 #if DEBUG
714     if ((readResult < 0) && (errno == EINVAL)) {
715       NSLog(@"%s: invalid argument in NGDescriptorRecv(%i, 0x%p, %i, %i)",
716             __PRETTY_FUNCTION__,
717             self->fd, _buf, _len, 0,
718             (self->receiveTimeout == 0.0)
719             ? -1 // block until data
720             : (int)(self->receiveTimeout * 1000.0));
721     }
722 #endif
723     
724     if (readResult == 0) {
725       [self _shutdownDuringOperation];
726       [self raise:@"NGSocketShutdownDuringReadException"];
727       return NGStreamError;
728     }
729     else if (readResult == -2) {
730       [self raise:@"NGSocketTimedOutException"];
731       return NGStreamError;
732     }
733     else if (readResult < 0) {
734       int errorCode = errno;
735
736       e = nil;
737       switch (errorCode) {
738         case 0:
739 #if DEBUG
740           /* this happens with the Oracle7 adaptor !!! */
741           NSLog(@"WARNING(%s): readResult<0 (%i), but errno=0 - retry",
742                 __PRETTY_FUNCTION__, readResult);
743 #endif
744           goto retry;
745           break;
746           
747         case ECONNRESET:
748           e = [[NGSocketConnectionResetException alloc] initWithStream:self];
749           break;
750         case ETIMEDOUT:
751           e = [[NGSocketTimedOutException alloc] initWithStream:self];
752           break;
753
754         case EWOULDBLOCK:
755           NSLog(@"WARNING: descriptor would block ..");
756           
757         default:
758           e = [[NGStreamReadErrorException alloc]
759                     initWithStream:self errorCode:errorCode];
760           break;
761       }
762       if (e) {
763         [self setLastException:e];
764         [e release];
765         return NGStreamError;
766       }
767     }
768 #endif /* !WIN32 */
769     return readResult;
770   }
771 }
772
773 #if defined(WIN32) && !defined(__CYGWIN32__)
774 #warning fix exception handling
775
776 - (unsigned)_winWriteBytes:(const void *)_buf count:(unsigned)_len {
777   NSException *e = nil;
778     int writeResult;
779
780     writeResult = send(self->fd, _buf, _len, 0);
781     
782     if (writeResult == 0) {
783       [self _shutdownDuringOperation];
784       [self raise:@"NGSocketShutdownDuringWriteException"];
785       return NGStreamError;
786     }
787     else if (writeResult < 0) {
788       int errorCode = WSAGetLastError();
789       
790       switch (errorCode) {
791         case WSAECONNRESET:
792           e = [[NGSocketConnectionResetException alloc] initWithStream:self];
793           break;
794         case WSAETIMEDOUT:
795           e = [[NGSocketTimedOutException alloc] initWithStream:self];
796           break;
797
798         case WSAEWOULDBLOCK:
799           NSLog(@"WARNING: descriptor would block ..");
800
801         default:
802           e = [[NGStreamWriteErrorException alloc]
803                     initWithStream:self errorCode:errno];
804           break;
805       }
806       if (e) {
807         [self setLastException:e];
808         [e release];
809         return NGStreamError;
810       }
811     }
812     return writeResult;
813 }
814
815 #else 
816
817 - (unsigned)_unixWriteBytes:(const void *)_buf count:(unsigned)_len {
818    int writeResult;
819    int timeOut;
820    int retryCount;
821
822    retryCount = 0;
823    timeOut = (self->sendTimeout == 0.0)
824      ? -1 // block until data
825      : (int)(self->sendTimeout * 1000.0);
826    
827  wretry: 
828    writeResult = NGDescriptorSend(self->fd, _buf, _len, 0, timeOut);
829    
830    if (writeResult == 0) {
831      [self _shutdownDuringOperation];
832      [self raise:@"NGSocketShutdownDuringWriteException"];
833      return NGStreamError;
834    }
835    else if (writeResult == -2) {
836      [self raise:@"NGSocketTimedOutException"];
837      return NGStreamError;
838    }
839    else if (writeResult < 0) {
840      int errorCode = errno;
841      
842      switch (errorCode) {
843        case 0:
844 #if DEBUG
845          /* this happens with the Oracle7 (on SuSE < 7.1??) adaptor !!! */
846          NSLog(@"WARNING(%s): writeResult<0 (%i), but errno=0 - retry",
847                __PRETTY_FUNCTION__, writeResult);
848 #endif
849          retryCount++;
850          if (retryCount > 200000) {
851            NSLog(@"WARNING(%s): writeResult<0 (%i), but errno=0 - cancel retry "
852                  @"(already tried %i times !!!)",
853                  __PRETTY_FUNCTION__, writeResult, retryCount);
854            [self _shutdownDuringOperation];
855            [self raise:@"NGSocketShutdownDuringWriteException"];
856            return NGStreamError;
857            break;
858          }
859          sleep(retryCount);
860          goto wretry;
861          break;
862          
863        case ECONNRESET:
864          [self raise:@"NGSocketConnectionResetException"];
865          return NGStreamError;
866        case ETIMEDOUT:
867          [self raise:@"NGSocketTimedOutException"];
868          return NGStreamError;
869          
870        case EPIPE:
871          [self _shutdownDuringOperation];
872          [self raise:@"NGSocketShutdownDuringWriteException"];
873          return NGStreamError;
874          
875        case EWOULDBLOCK:
876          NSLog(@"WARNING: descriptor would block ..");
877          
878        default: {
879          NSException *e;
880          e = [[NGStreamWriteErrorException alloc]
881                initWithStream:self errorCode:errno];
882          [self setLastException:e];
883          [e release];
884          return NGStreamError;
885        }
886      }
887    }
888    return writeResult;
889 }
890
891 #endif 
892  
893 - (unsigned)writeBytes:(const void *)_buf count:(unsigned)_len {
894   // throws
895   //   NGStreamWriteErrorException   when the write call failed
896   //   NGSocketNotConnectedException when the socket is not connected
897   //   NGReadOnlyStreamException     when the send channel was shutdown
898   
899   if (_len == NGStreamError) {
900     NSLog(@"ERROR(%s): got NGStreamError passed in as length ...",
901           __PRETTY_FUNCTION__);
902     return NGStreamError;
903   }
904 #if DEBUG
905   if (_len > (1024 * 1024 * 100 /* 100MB */)) {
906     NSLog(@"WARNING(%s): got passed in length %uMB (%u bytes, errcode=%u) ...",
907           __PRETTY_FUNCTION__, (_len / 1024 / 1024), _len, NGStreamError);
908   }
909 #endif
910   
911   if (self->fd == NGInvalidSocketDescriptor) {
912     [self raise:@"NGSocketException" reason:@"NGActiveSocket is not open"];
913     return NGStreamError;
914   }
915   
916   // need to check whether socket is connected
917   if (self->remoteAddress == nil) {
918     [self raise:@"NGSocketNotConnectedException"
919           reason:@"socket is not connected"];
920     return NGStreamError;
921   }
922   
923   if (!NGCanWriteInStreamMode(self->mode)) {
924     [self raise:@"NGReadOnlyStreamException"];
925     return NGStreamError;
926   }
927   
928   //NSLog(@"writeBytes: count:%u", _len);
929   
930 #if defined(WIN32) && !defined(__CYGWIN32__)
931   return [self _winWriteBytes:_buf count:_len];
932 #else
933   return [self _unixWriteBytes:_buf count:_len];
934 #endif
935 }
936
937 - (BOOL)flush {
938   return YES;
939 }
940 #if 0 
941 - (BOOL)close {
942   return [self shutdown];
943 }
944 #endif 
945
946 - (NGStreamMode)mode {
947   return self->mode;
948 }
949
950 /* methods method which write exactly _len bytes or fail */
951
952 - (BOOL)safeReadBytes:(void *)_buf count:(unsigned)_len {
953   volatile int toBeRead;
954   int  readResult;
955   void *pos;
956   unsigned (*readBytes)(id, SEL, void *, unsigned);
957
958   *(&readBytes)  = (void *)[self methodForSelector:@selector(readBytes:count:)];
959   *(&toBeRead)   = _len;
960   *(&readResult) = 0;
961   *(&pos)        = _buf;
962   
963   while (YES) {
964     *(&readResult) =
965       readBytes(self, @selector(readBytes:count:), pos, toBeRead);
966     
967     if (readResult == NGStreamError) {
968       NSException *localException = [self lastException];
969       NSData *data;
970       
971       data = [NSData dataWithBytes:_buf length:(_len - toBeRead)];
972       
973       localException = [[[localException class] alloc]
974                           initWithStream:self
975                           readCount:(_len - toBeRead)
976                           safeCount:_len
977                           data:data];
978       [self setLastException:localException];
979       RELEASE(localException);
980     }
981     
982     NSAssert(readResult != 0, @"ERROR: readBytes may not return '0' ..");
983
984     if (readResult == toBeRead) {
985       // all bytes were read successfully, return
986       break;
987     }
988     
989     if (readResult < 1) {
990       [NSException raise:NSInternalInconsistencyException
991                    format:@"readBytes:count: returned a value < 1"];
992     }
993
994     toBeRead -= readResult;
995     pos      += readResult;
996   }
997   
998   return YES;
999 }
1000
1001 - (BOOL)safeWriteBytes:(const void *)_buf count:(unsigned)_len {
1002   int  toBeWritten = _len;
1003   int  writeResult;
1004   void *pos = (void *)_buf;
1005
1006   /* method cache (THREAD, reentrant) */
1007   static Class lastClass = Nil;
1008   static int  (*writeBytes)(id,SEL,const void*,unsigned) = NULL;
1009
1010   if (lastClass == *(Class *)self) {
1011     if (writeBytes == NULL)
1012       writeBytes =
1013         (void *)[self methodForSelector:@selector(writeBytes:count:)];
1014   }
1015   else {
1016     lastClass = *(Class *)self;
1017     writeBytes = (void *)[self methodForSelector:@selector(writeBytes:count:)];
1018   }
1019   
1020   while (YES) {
1021     writeResult =
1022       (int)writeBytes(self, @selector(writeBytes:count:), pos, toBeWritten);
1023     
1024     if (writeResult == NGStreamError) {
1025       /* remember number of written bytes ??? */
1026       return NO;
1027     }
1028     else if (writeResult == toBeWritten) {
1029       // all bytes were written successfully, return
1030       break;
1031     }
1032
1033     if (writeResult < 1) {
1034       [NSException raise:NSInternalInconsistencyException
1035                    format:@"writeBytes:count: returned a value < 1 in stream %@",
1036                      self];
1037       return NO;
1038     }
1039     
1040     toBeWritten -= writeResult;
1041     pos         += writeResult;
1042   }
1043   return YES;
1044 }
1045
1046 - (BOOL)mark {
1047   return NO;
1048 }
1049 - (BOOL)rewind {
1050   [self raise:@"NGStreamException" reason:@"stream doesn't support a mark"];
1051   return NO;
1052 }
1053 - (BOOL)markSupported {
1054   return NO;
1055 }
1056
1057 // convenience methods
1058
1059 - (int)readByte { // java semantics (-1 returned on EOF)
1060   int result;
1061   unsigned char c;
1062   
1063   result = [self readBytes:&c count:sizeof(unsigned char)];
1064   
1065   if (result != 1) {
1066     static Class EOFExcClass = Nil;
1067
1068     if (EOFExcClass == Nil)
1069       EOFExcClass = [NGEndOfStreamException class];
1070     
1071     if ([[self lastException] isKindOfClass:EOFExcClass])
1072       [self resetLastException];
1073     
1074     return -1;
1075   }
1076   return (int)c;
1077 }
1078
1079 /* description */
1080
1081 - (NSString *)modeDescription {
1082   NSString *result = @"<unknown>";
1083   
1084   switch ([self mode]) {
1085     case NGStreamMode_undefined: result = @"<closed>"; break;
1086     case NGStreamMode_readOnly:  result = @"r";        break;
1087     case NGStreamMode_writeOnly: result = @"w";        break;
1088     case NGStreamMode_readWrite: result = @"rw";       break;
1089     default:
1090       [[[NGUnknownStreamModeException alloc] initWithStream:self] raise];
1091       break;
1092   }
1093   return result;
1094 }
1095
1096 - (NSString *)description {
1097   NSMutableString *d = [NSMutableString stringWithCapacity:64];
1098
1099   [d appendFormat:@"<%@[0x%p]: mode=%@ address=%@",
1100        NSStringFromClass([self class]),
1101        (unsigned)self,
1102        [self modeDescription], [self localAddress]];
1103
1104   if ([self isConnected])
1105     [d appendFormat:@" connectedTo=%@", [self remoteAddress]];
1106
1107   if ([self sendTimeout] != 0.0) 
1108     [d appendFormat:@" send-timeout=%4.3fs", [self sendTimeout]];
1109   if ([self receiveTimeout] != 0.0) 
1110     [d appendFormat:@" receive-timeout=%4.3fs", [self receiveTimeout]];
1111
1112   [d appendString:@">"];
1113   return d;
1114 }
1115
1116 @end /* NGActiveSocket */
1117
1118 @implementation NGActiveSocket(DataMethods)
1119
1120 - (NSData *)readDataOfLength:(unsigned int)_length {
1121   unsigned readCount;
1122   char buf[_length];
1123
1124   if (_length == 0) return [NSData data];
1125
1126   readCount = [self readBytes:buf count:_length];
1127   return [NSData dataWithBytes:buf length:readCount];
1128 }
1129
1130 - (NSData *)safeReadDataOfLength:(unsigned int)_length {
1131   char buf[_length];
1132
1133   if (_length == 0) return [NSData data];
1134   [self safeReadBytes:buf count:_length];
1135   return [NSData dataWithBytes:buf length:_length];
1136 }
1137
1138 - (unsigned int)writeData:(NSData *)_data {
1139   return [self writeBytes:[_data bytes] count:[_data length]];
1140 }
1141 - (BOOL)safeWriteData:(NSData *)_data {
1142   return [self safeWriteBytes:[_data bytes] count:[_data length]];
1143 }
1144
1145 @end /* NGActiveSocket(DataMethods) */
1146
1147 #include <NGStreams/NGBufferedStream.h>
1148
1149 @implementation NGBufferedStream(FastSocketForwarders)
1150
1151 - (BOOL)isConnected {
1152   return [(id)self->source isConnected];
1153 }
1154 - (int)fileDescriptor {
1155   return [(NSFileHandle *)self->source fileDescriptor];
1156 }
1157
1158 @end /* NGBufferedStream(FastSocketForwarders) */