5 // Created by Jens Alfke on 5/18/08.
6 // Copyright 2008 Jens Alfke. All rights reserved.
9 #import "TCP_Internal.h"
14 #import "ExceptionUtils.h"
17 #if TARGET_OS_IPHONE && !defined(__SEC_TYPES__)
18 // SecureTransport.h is missing on iPhone, with its SSL constants:
20 errSSLClosedAbort = -9806, /* connection closed via error */
26 NSString* const TCPErrorDomain = @"TCP";
29 @interface TCPConnection ()
30 @property TCPConnectionStatus status;
31 @property (retain) IPAddress *address;
32 - (BOOL) _checkIfClosed;
37 @implementation TCPConnection
40 static NSMutableArray *sAllConnections;
43 - (Class) readerClass {return [TCPReader class];}
44 - (Class) writerClass {return [TCPWriter class];}
47 - (id) _initWithAddress: (IPAddress*)address
48 inputStream: (NSInputStream*)input
49 outputStream: (NSOutputStream*)output
53 if( !input || !output ) {
54 LogTo(TCP,@"Failed to create %@: addr=%@, in=%@, out=%@",
55 self.class,address,input,output);
59 _address = [address copy];
60 _reader = [[[self readerClass] alloc] initWithConnection: self stream: input];
61 _writer = [[[self writerClass] alloc] initWithConnection: self stream: output];
62 LogTo(TCP,@"%@ initialized, address=%@",self,address);
69 - (id) initToAddress: (IPAddress*)address
71 NSInputStream *input = nil;
72 NSOutputStream *output = nil;
74 // +getStreamsToHost: is missing for some stupid reason on iPhone. Grrrrrrrrrr.
75 CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)address.hostname, address.port,
76 (CFReadStreamRef*)&input, (CFWriteStreamRef*)&output);
78 [(id)CFMakeCollectable(input) autorelease];
80 [(id)CFMakeCollectable(output) autorelease];
82 [NSStream getStreamsToHost: [NSHost hostWithAddress: address.ipv4name]
85 outputStream: &output];
87 return [self _initWithAddress: address inputStream: input outputStream: output];
90 - (id) initToNetService: (NSNetService*)service
92 IPAddress *address = nil;
94 NSOutputStream *output;
95 if( [service getInputStream: &input outputStream: &output] ) {
96 NSArray *addresses = service.addresses;
97 if( addresses.count > 0 )
98 address = [[[IPAddress alloc] initWithSockAddr: [[addresses objectAtIndex: 0] bytes]] autorelease];
103 return [self _initWithAddress: address inputStream: input outputStream: output];
107 - (id) initIncomingFromSocket: (CFSocketNativeHandle)socket
108 listener: (TCPListener*)listener
110 CFReadStreamRef readStream = NULL;
111 CFWriteStreamRef writeStream = NULL;
112 CFStreamCreatePairWithSocket(kCFAllocatorDefault, socket, &readStream, &writeStream);
113 self = [self _initWithAddress: [IPAddress addressOfSocket: socket]
114 inputStream: (NSInputStream*)CFMakeCollectable(readStream)
115 outputStream: (NSOutputStream*)CFMakeCollectable(writeStream)];
118 _server = [listener retain];
119 CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
120 CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
122 if(readStream) CFRelease(readStream);
123 if(writeStream) CFRelease(writeStream);
130 LogTo(TCP,@"DEALLOC %@",self);
138 - (NSString*) description
140 return $sprintf(@"%@[%@ %@]",self.class,(_isIncoming ?@"from" :@"to"),_address);
144 @synthesize address=_address, isIncoming=_isIncoming, status=_status, delegate=_delegate,
145 reader=_reader, writer=_writer, server=_server, openTimeout=_openTimeout;
154 - (NSString*) actualSecurityLevel
156 return _reader.securityLevel;
160 - (NSArray*) peerSSLCerts
162 return _reader.peerSSLCerts ?: _writer.peerSSLCerts;
166 - (void) _setStreamProperty: (id)value forKey: (NSString*)key
168 [_reader setProperty: value forKey: (CFStringRef)key];
169 [_writer setProperty: value forKey: (CFStringRef)key];
174 #pragma mark OPENING / CLOSING:
179 if( _status<=kTCP_Closed && _reader ) {
180 _reader.SSLProperties = _sslProperties;
181 _writer.SSLProperties = _sslProperties;
184 if( ! [sAllConnections my_containsObjectIdenticalTo: self] )
185 [sAllConnections addObject: self];
186 self.status = kTCP_Opening;
187 if( _openTimeout > 0 )
188 [self performSelector: @selector(_openTimeoutExpired) withObject: nil afterDelay: _openTimeout];
192 - (void) _stopOpenTimer
194 [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_openTimeoutExpired) object: nil];
197 - (void) _openTimeoutExpired
199 if( _status == kTCP_Opening ) {
200 LogTo(TCP,@"%@: timed out waiting to open",self);
201 [self _stream: _reader gotError: [NSError errorWithDomain: NSPOSIXErrorDomain
202 code: ETIMEDOUT userInfo: nil]];
209 if( _status > kTCP_Closed ) {
210 LogTo(TCP,@"%@ disconnecting",self);
211 [_writer disconnect];
212 [_reader disconnect];
213 self.status = kTCP_Disconnected;
215 [self _stopOpenTimer];
221 [self closeWithTimeout: 60.0];
224 - (void) closeWithTimeout: (NSTimeInterval)timeout
226 [self _stopOpenTimer];
227 if( _status == kTCP_Opening ) {
228 LogTo(TCP,@"%@ canceling open",self);
230 } else if( _status == kTCP_Open ) {
231 LogTo(TCP,@"%@ closing",self);
232 self.status = kTCP_Closing;
235 if( ! [self _checkIfClosed] ) {
238 else if( timeout != INFINITY )
239 [self performSelector: @selector(_closeTimeoutExpired)
240 withObject: nil afterDelay: timeout];
246 - (void) _closeTimeoutExpired
248 if( _status==kTCP_Closing )
252 - (void) _stopCloseTimer
254 [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_closeTimeoutExpired) object: nil];
259 if( _status == kTCP_Closing ) {
260 LogTo(TCP,@"%@: _unclose!",self);
263 [self _stopCloseTimer];
264 self.status = kTCP_Open;
269 /** Subclasses can override this to customize what happens when -close is called. */
277 - (BOOL) _checkIfClosed
279 if( _status == kTCP_Closing && _writer==nil && _reader==nil ) {
287 // called by my streams when they close (after my -close is called)
290 [[self retain] autorelease];
291 if( _status != kTCP_Closed && _status != kTCP_Disconnected ) {
292 LogTo(TCP,@"%@ is now closed",self);
293 TCPConnectionStatus prevStatus = _status;
294 self.status = (prevStatus==kTCP_Closing ?kTCP_Closed :kTCP_Disconnected);
295 if( prevStatus==kTCP_Opening )
296 [self tellDelegate: @selector(connection:failedToOpen:) withObject: self.error];
298 [self tellDelegate: @selector(connectionDidClose:) withObject: nil];
300 [self _stopCloseTimer];
301 [self _stopOpenTimer];
302 [sAllConnections removeObjectIdenticalTo: self];
306 + (void) closeAllWithTimeout: (NSTimeInterval)timeout
308 NSArray *connections = [sAllConnections copy];
309 for( TCPConnection *conn in connections )
310 [conn closeWithTimeout: timeout];
311 [connections release];
314 + (void) waitTillAllClosed
316 while( sAllConnections.count ) {
317 if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
318 beforeDate: [NSDate distantFuture]] )
325 #pragma mark STREAM CALLBACKS:
328 - (void) _streamOpened: (TCPStream*)stream
331 self.address = stream.peerAddress;
332 if( _status==kTCP_Opening && _reader.isOpen && _writer.isOpen ) {
333 LogTo(TCP,@"%@ opened; address=%@",self,_address);
334 [self _stopOpenTimer];
335 self.status = kTCP_Open;
336 [self tellDelegate: @selector(connectionDidOpen:) withObject: nil];
341 - (BOOL) _streamPeerCertAvailable: (TCPStream*)stream
344 if( ! _checkedPeerCert ) {
346 _checkedPeerCert = YES;
347 if( stream.securityLevel != nil ) {
348 NSArray *certs = stream.peerSSLCerts;
349 if( ! certs && ! _isIncoming )
350 allow = NO; // Server MUST have a cert!
352 SecCertificateRef cert = certs.count ?(SecCertificateRef)[certs objectAtIndex:0] :NULL;
353 LogTo(TCP,@"%@: Peer cert = %@",self,[TCPEndpoint describeCert: cert]);
354 if( [_delegate respondsToSelector: @selector(connection:authorizeSSLPeer:)] )
355 allow = [_delegate connection: self authorizeSSLPeer: cert];
358 }@catch( NSException *x ) {
359 MYReportException(x,@"TCPConnection _streamPeerCertAvailable");
360 _checkedPeerCert = NO;
364 [self _stream: stream
365 gotError: [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
366 code: errSSLClosedAbort
373 - (void) _stream: (TCPStream*)stream gotError: (NSError*)error
375 LogTo(TCP,@"%@ got %@ on %@",self,error,stream.class);
377 [[self retain] autorelease];
378 setObj(&_error,error);
379 [_reader disconnect];
380 [_writer disconnect];
384 - (void) _streamGotEOF: (TCPStream*)stream
386 LogTo(TCP,@"%@ got EOF on %@",self,stream);
388 if( _status == kTCP_Closing ) {
389 [self _streamCanClose: stream];
390 [self _checkIfClosed];
392 [self _stream: stream
393 gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
398 // Called as soon as a stream is ready to close, after its -close method has been called.
399 - (void) _streamCanClose: (TCPStream*)stream
401 if( ! _reader.isActive && !_writer.isActive ) {
402 LogTo(TCPVerbose,@"Both streams are ready to close now!");
403 [_reader disconnect];
404 [_writer disconnect];
409 // Called after I called -close on a stream and it finished closing:
410 - (void) _streamDisconnected: (TCPStream*)stream
412 LogTo(TCP,@"%@: disconnected %@",self,stream);
413 if( stream == _reader )
414 setObj(&_reader,nil);
415 else if( stream == _writer )
416 setObj(&_writer,nil);
419 if( !_reader.isOpen && !_writer.isOpen )
428 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
430 Redistribution and use in source and binary forms, with or without modification, are permitted
431 provided that the following conditions are met:
433 * Redistributions of source code must retain the above copyright notice, this list of conditions
434 and the following disclaimer.
435 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
436 and the following disclaimer in the documentation and/or other materials provided with the
439 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
440 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
441 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
442 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
443 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
444 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
445 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
446 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.