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