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 "NGPassiveSocket.h"
23#include "NGSocketExceptions.h"
24#include "NGActiveSocket.h"
25#include "NGSocket+private.h"
26
27#if defined(__APPLE__)
28#  include <sys/types.h>
29#  include <sys/socket.h>
30#endif
31
32#if HAVE_SYS_ERRNO_H || defined(__APPLE__)
33#  include <sys/errno.h>
34#endif
35
36#include "common.h"
37
38@interface NGActiveSocket(privateMethods)
39
40- (id)_initWithDescriptor:(int)_fd
41  localAddress:(id<NGSocketAddress>)_local
42  remoteAddress:(id<NGSocketAddress>)_remote;
43
44@end
45
46@implementation NGPassiveSocket
47
48+ (id)socketBoundToAddress:(id<NGSocketAddress>)_address {
49  volatile id sock;
50
51  sock = [[[self alloc] initWithDomain:[_address domain]] autorelease];
52  [sock bindToAddress:_address];
53  return sock;
54}
55
56- (id)initWithDomain:(id<NGSocketDomain>)_domain { // designated initializer
57  if ((self = [super initWithDomain:_domain])) {
58    backlogSize = -1; // -1 means 'not listening'
59
60    if ([NSThread isMultiThreaded])
61      acceptLock = [[NSLock allocWithZone:[self zone]] init];
62    else {
63      acceptLock = nil;
64      [[NSNotificationCenter defaultCenter]
65                             addObserver:self
66                             selector:@selector(taskNowMultiThreaded:)
67                             name:NSWillBecomeMultiThreadedNotification
68                             object:nil];
69    }
70
71    if (self->fd != NGInvalidSocketDescriptor) {
72      int i_yes = 1;
73
74      if (setsockopt(self->fd, SOL_SOCKET, SO_REUSEADDR,
75                     (void *)&i_yes, sizeof(int)) != 0) {
76        NSLog(@"WARNING: could not set SO_REUSEADDR option for socket %@: %s",
77              self, strerror(errno));
78      }
79    }
80  }
81  return self;
82}
83
84- (void)dealloc {
85  [[NSNotificationCenter defaultCenter]
86                         removeObserver:self
87                         name:NSWillBecomeMultiThreadedNotification
88                         object:nil];
89
90  [self->acceptLock release];
91  [super dealloc];
92}
93
94- (void)taskNowMultiThreaded:(NSNotification *)_notification {
95  if (acceptLock == nil) acceptLock = [[NSLock alloc] init];
96}
97
98// accessors
99
100- (BOOL)isListening {
101  return (backlogSize != -1);
102}
103- (BOOL)isOpen {
104  return [self isListening];
105}
106
107- (id<NGSocketAddress>)localAddress {
108  return localAddress;
109}
110
111- (int)socketType {
112  return SOCK_STREAM;
113}
114
115/* operations */
116
117#if defined(WIN32) && !defined(__CYGWIN32__)
118- (NSString *)reasonForLastError {
119  int errorCode = WSAGetLastError();
120
121  switch (errorCode) {
122    case WSAEBADF:
123      return @"not a valid socket descriptor";
124    case WSAENOTSOCK:
125      return @"descriptor is not a socket descriptor";
126    case WSAEOPNOTSUPP:
127      return @"socket does not support listen";
128    case WSAEINTR:
129      return @"interrupted by signal";
130    case WSAEMFILE:
131      return @"descriptor table is full";
132
133    default:
134      return [NSString stringWithCString:strerror(errorCode)];
135  }
136}
137#else
138- (NSString *)reasonForLastError {
139  int errorCode = errno;
140
141  switch (errorCode) {
142    case EBADF:
143      return @"not a valid socket descriptor";
144    case ENOTSOCK:
145      return @"descriptor is not a socket descriptor";
146    case EOPNOTSUPP:
147      return @"socket does not support listen";
148    case EINTR:
149      return @"interrupted by signal";
150    case EMFILE:
151      return @"descriptor table is full";
152    case EPROTONOSUPPORT:
153      return @"The protocol is not supported by the address family or "
154             @"implementation";
155    case EPROTOTYPE:
156      return @"The socket type is not supported by the protocol";
157
158    default:
159      return [NSString stringWithCString:strerror(errorCode)];
160  }
161}
162#endif
163
164- (BOOL)listenWithBacklog:(int)_backlogSize {
165  // throws
166  //   NGSocketIsAlreadyListeningException  when the socket is in the listen state
167  //   NGCouldNotListenException            when the listen call failed
168
169  if ([self isListening]) {
170    [[[NGSocketIsAlreadyListeningException alloc]
171              initWithReason:@"already called listen" socket:self] raise];
172    return NO;
173  }
174
175  if (listen([self fileDescriptor], _backlogSize) != 0) {
176    NSString *reason;
177    reason = [self reasonForLastError];
178    reason = [@"Could not listen: %@" stringByAppendingString:reason];
179
180    [[[NGCouldNotListenException alloc]
181              initWithReason:reason socket:self] raise];
182    return NO;
183  }
184
185  /* set backlog size (and mark socket as 'listening') */
186  self->backlogSize = _backlogSize;
187  return YES;
188}
189
190- (id<NGActiveSocket>)accept {
191  // throws
192  //   NGCouldNotAcceptException  when the socket is not listening
193  //   NGCouldNotAcceptException  when the accept call failed
194
195  id<NGActiveSocket> socket;
196  *(&socket) = nil;
197
198  if (![self isListening]) {
199    [[[NGCouldNotAcceptException alloc]
200              initWithReason:@"socket is not listening" socket:self] raise];
201  }
202
203  SYNCHRONIZED(self->acceptLock) {
204    id<NGSocketAddress> local  = nil;
205    id<NGSocketAddress> remote = nil;
206    socklen_t len;
207    char *data;
208    int  newFd = NGInvalidSocketDescriptor;
209
210    len   = [[self domain] addressRepresentationSize];
211    data = calloc(1, len + 1);
212
213    if ((newFd = accept(fd, (void *)data, &len)) == -1) {
214      // call failed
215      NSString *reason = nil;
216      reason = [self reasonForLastError];
217      reason = [@"Could not accept: " stringByAppendingString:reason];
218
219      [[[NGCouldNotAcceptException alloc]
220                initWithReason:reason socket:self] raise];
221    }
222
223    /* produce remote socket address object */
224    remote = [[self domain] addressWithRepresentation:(void *)data
225                            size:len];
226
227    // getsockname if wildcard-IP-bind to get local IP address assigned
228    // to the connection
229    len = [[self domain] addressRepresentationSize];
230    if (getsockname(newFd, (void *)data, &len) != 0) { // function is MT-safe
231      [[[NGSocketException alloc]
232                initWithReason:@"could not get local socket name" socket:self]
233                raise];
234    }
235    local = [[self domain] addressWithRepresentation:(void *)data size:len];
236
237    if (data) {
238      free(data);
239      data = NULL;
240    }
241
242    socket = [[NGActiveSocket alloc]
243                              _initWithDescriptor:newFd
244                              localAddress:local
245                              remoteAddress:remote];
246    socket = [socket autorelease];
247  }
248  END_SYNCHRONIZED;
249  return socket;
250}
251
252// description
253
254- (NSString *)description {
255  return [NSString stringWithFormat:@"<PassiveSocket: address=%@>",
256                     [self localAddress]];
257}
258
259@end /* NGPassiveSocket */
260